UnitGraph.java 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968
  1. package holeg.ui.view.inspector;
  2. import java.awt.BasicStroke;
  3. import java.awt.Color;
  4. import java.awt.Cursor;
  5. import java.awt.Graphics;
  6. import java.awt.Graphics2D;
  7. import java.awt.RenderingHints;
  8. import java.awt.event.ComponentEvent;
  9. import java.awt.event.ComponentListener;
  10. import java.awt.event.MouseEvent;
  11. import java.awt.event.MouseListener;
  12. import java.awt.event.MouseMotionListener;
  13. import java.awt.geom.Path2D;
  14. import java.util.ArrayList;
  15. import java.util.LinkedList;
  16. import java.util.List;
  17. import java.util.ListIterator;
  18. import java.util.Optional;
  19. import java.util.Set;
  20. import java.util.logging.Logger;
  21. import javax.swing.JPanel;
  22. import holeg.interfaces.LocalMode;
  23. import holeg.interfaces.TimelineDependent;
  24. import holeg.interfaces.GraphEditable.GraphType;
  25. import holeg.model.HolonElement;
  26. import holeg.model.Model;
  27. import holeg.ui.controller.Control;
  28. import holeg.utility.math.Maths;
  29. import holeg.utility.math.vector.Vec2f;
  30. import holeg.utility.math.vector.Vec2i;
  31. /**
  32. * This Class represents a Graph where the User can model the behavior of
  33. * elements and switches over time.
  34. *
  35. * @author Tom Troppmann
  36. */
  37. public class UnitGraph extends JPanel implements MouseListener, MouseMotionListener, ComponentListener {
  38. private static final Logger log = Logger.getLogger(UnitGraph.class.getName());
  39. // Normal Settings
  40. private static final int border = 4;
  41. private static final int clickThresholdSquared = 25;
  42. // Display Settings
  43. /**
  44. * The size of a dot in the graph. It should be at least 1.
  45. */
  46. private static final int dotSize = 8;
  47. /** The Color of a dot in the graph. */
  48. //TODO(Tom2022-01-25): export them to ColorPreferences
  49. private static final Color dotColor = Color.blue;
  50. private static final Color editDotColor = new Color(255, 119, 0);
  51. private static final Color[] seriesColorArray = { Color.blue, Color.cyan, Color.black, Color.green, Color.gray,
  52. Color.magenta, Color.yellow, Color.PINK, Color.red };
  53. private static final Color globalCurveColor = new Color(255, 30, 30);
  54. private static final Color zeroLineColor = new Color(255, 153, 153);
  55. private final Control control;
  56. // Intern Variables
  57. private static class Series {
  58. public LinkedList<UnitGraphPoint> points = new LinkedList<UnitGraphPoint>();
  59. public TimelineDependent element;
  60. public GraphType type;
  61. public Color color;
  62. }
  63. private final ArrayList<Series> seriesList = new ArrayList<>();
  64. private Vec2i editPosition;
  65. private Series actualSeries = null;
  66. private static class GlobalCurve {
  67. public LinkedList<UnitGraphPoint> points = new LinkedList<UnitGraphPoint>();
  68. public float minEnergy;
  69. public float maxEnergy;
  70. public LinkedList<UnitGraphPoint> zeroLinePoints = new LinkedList<UnitGraphPoint>();
  71. }
  72. private GlobalCurve globalCurve = null;
  73. private boolean editMode = false;
  74. private Set<HolonElement> elements;
  75. private enum EditPointType {
  76. Normal, StartPoint, EndPoint
  77. };
  78. private int widthWithBorder, heightWithBorder;
  79. private EditPointType editPointType;
  80. /**
  81. * Constructor.
  82. * @param control the Controller
  83. */
  84. public UnitGraph(Control control) {
  85. setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
  86. this.setBackground(Color.WHITE);
  87. this.control = control;
  88. this.addMouseListener(this);
  89. this.addMouseMotionListener(this);
  90. this.addComponentListener(this);
  91. control.OnCanvasUpdate.addListener(this::repaint);
  92. }
  93. /**
  94. * When the UnitGraph should represent a new GraphEditable Element. Its Updates
  95. * the Graph and give access to the Element.
  96. *
  97. * @param element
  98. */
  99. public void addNewSeries(TimelineDependent element) {
  100. Series series = new Series();
  101. overrideUnitGraph(series, element.getStateGraph());
  102. series.element = element;
  103. series.type = element.getGraphType();
  104. series.color = seriesColorArray[element.hashCode() % seriesColorArray.length];
  105. seriesList.add(series);
  106. repaint();
  107. }
  108. public void clearSeries() {
  109. seriesList.clear();
  110. repaint();
  111. }
  112. public void setGlobalCurve(Set<HolonElement> elements) {
  113. if( elements == null || elements.isEmpty()) {
  114. this.globalCurve = null;
  115. return;
  116. }
  117. GlobalCurve curve = new GlobalCurve();
  118. curve.maxEnergy = elements.stream().map(HolonElement::getEnergy).filter(energy -> energy > 0).reduce(0.0f,
  119. Float::sum);
  120. curve.minEnergy = elements.stream().map(HolonElement::getEnergy).filter(energy -> energy < 0).reduce(0.0f,
  121. Float::sum);
  122. Model model = control.getModel();
  123. float[] sample = new float[model.getMaxIterations()];
  124. // sample energy
  125. for (HolonElement element : elements) {
  126. for (int i = 0; i < model.getMaxIterations(); i++) {
  127. sample[i] += element.calculateExpectedEnergyAtTimeStep(i);
  128. }
  129. }
  130. // sample curve
  131. for (int i = 0; i < model.getMaxIterations(); i++) {
  132. curve.points.add(new UnitGraphPoint((double) i / (double)model.getMaxIterations(),
  133. Maths.invLerp(curve.minEnergy, curve.maxEnergy, sample[i]), false));
  134. }
  135. // update displayPosition
  136. for (UnitGraphPoint p : curve.points) {
  137. p.calcDisplayedPosition(border, widthWithBorder, heightWithBorder);
  138. }
  139. double zeroLineYPos = Maths.invLerp(curve.minEnergy, curve.maxEnergy, 0.0);
  140. curve.zeroLinePoints.add(new UnitGraphPoint(0.0,zeroLineYPos, false));
  141. curve.zeroLinePoints.add(new UnitGraphPoint(1.0,zeroLineYPos, false));
  142. for (UnitGraphPoint p : curve.zeroLinePoints) {
  143. p.calcDisplayedPosition(border, widthWithBorder, heightWithBorder);
  144. }
  145. // set global curve
  146. this.globalCurve = curve;
  147. this.elements = elements;
  148. }
  149. private void updateGlobalCurve() {
  150. setGlobalCurve(this.elements);
  151. }
  152. /**
  153. * Paints the Graph, the Grid, the actual Line from the currentIteration
  154. *
  155. * @param g Graphics
  156. */
  157. public void paintComponent(Graphics g) {
  158. super.paintComponent(g);
  159. Graphics2D g2d = (Graphics2D) g;
  160. drawGrid(g2d);
  161. g2d.setColor(Color.BLACK);
  162. g2d.setRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
  163. g2d.setStroke(new BasicStroke(2f));
  164. drawUnitGraph(g2d);
  165. g2d.setColor(dotColor);
  166. if (editMode) {
  167. drawUnitGraphPointsReleased(g2d);
  168. } else {
  169. drawUnitGraphPoints(g2d);
  170. }
  171. g2d.setColor(dotColor);
  172. g2d.setStroke(new BasicStroke(1));
  173. drawCurrentIterationLine(g2d);
  174. g2d.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_ROUND, 1.0f, new float[]{6}, 3));
  175. Optional.ofNullable(this.globalCurve).ifPresent(curve -> {
  176. g2d.setColor(globalCurveColor);
  177. drawDoubleGraph(g2d, curve.points);
  178. g2d.setColor(zeroLineColor);
  179. g2d.setStroke(new BasicStroke(1));
  180. drawDoubleGraph(g2d, curve.zeroLinePoints);
  181. });
  182. }
  183. // Draw Methods only to let the User see the changes. Nothing its saved here or
  184. // changed.
  185. /**
  186. * Helper Method to draw the UnitGraphPanel.
  187. * {@link UnitGraph#paintComponent(Graphics)}
  188. * <p>
  189. * This Methods draws the UnitGraph whether its a boolGraph or a doubleGraph.
  190. *
  191. * @param g to draw.
  192. */
  193. private void drawUnitGraph(Graphics2D g) {
  194. boolean drawSnappingHintFlag = false;
  195. for (Series series : this.seriesList) {
  196. g.setColor(series.color);
  197. switch (series.type) {
  198. case boolGraph:
  199. if (editMode) {
  200. drawBoolGraphInEditMode(g, series);
  201. drawSnappingHintFlag = true;
  202. } else
  203. drawBoolGraph(g, series);
  204. break;
  205. case doubleGraph:
  206. if (editMode)
  207. drawDoubleGraphInEditMode(g, series);
  208. else
  209. drawDoubleGraph(g, series.points);
  210. break;
  211. default:
  212. throw new UnsupportedOperationException();
  213. }
  214. }
  215. if (drawSnappingHintFlag) {
  216. drawSnappingHint(g);
  217. }
  218. }
  219. /**
  220. * Helper Method to draw the UnitGraphPanel.
  221. * {@link UnitGraph#paintComponent(Graphics)}
  222. * <p>
  223. * This Methods draws the UnitGraphPoints of the UnitGraph.
  224. *
  225. * @param g to draw.
  226. */
  227. private void drawUnitGraphPoints(Graphics2D g) {
  228. g.setColor(dotColor);
  229. for (Series series : seriesList) {
  230. for (UnitGraphPoint p : series.points) {
  231. drawDot(g, p.displayedPosition);
  232. }
  233. }
  234. }
  235. /**
  236. * Helper Method to draw the UnitGraphPanel.
  237. * {@link UnitGraph#paintComponent(Graphics)}
  238. * <p>
  239. * This Methods draws the UnitGraphPoints of the UnitGraph when its in EditMode.
  240. *
  241. * @param g to draw.
  242. */
  243. private void drawUnitGraphPointsReleased(Graphics2D g) {
  244. drawUnitGraphPoints(g);
  245. g.setColor(editDotColor);
  246. drawDot(g, editPosition);
  247. }
  248. /**
  249. * Helper Method to draw the UnitGraphPanel.
  250. * {@link UnitGraph#paintComponent(Graphics)}
  251. * <p>
  252. * This Methods draws the Grid on the Canvas.
  253. *
  254. * @param g to draw.
  255. */
  256. private void drawGrid(Graphics2D g) {
  257. g.setStroke(new BasicStroke(1));
  258. g.setColor(Color.lightGray);
  259. int amountOfLines = 10;
  260. int width = widthWithBorder + 2 * border;
  261. int height = heightWithBorder;
  262. for (int i = 0; i <= amountOfLines; i++) {
  263. int linehieght = (int) (((double) i / (double) amountOfLines) * (double) height) + border;
  264. g.drawLine(0, linehieght, width, linehieght);
  265. }
  266. }
  267. /**
  268. * Helper Method to draw the UnitGraphPanel.
  269. * {@link UnitGraph#paintComponent(Graphics)}
  270. * <p>
  271. * This Method draws the CurrentIterationLine.
  272. *
  273. * @param g to draw.
  274. */
  275. private void drawCurrentIterationLine(Graphics2D g) {
  276. Model model = control.getModel();
  277. int cur = model.getCurrentIteration();
  278. int max = model.getMaxIterations();
  279. if (isLocalPeriedDifferentInSeries()) {
  280. for (Series series : seriesList) {
  281. double where = switch(series.element.getPeriod().getType()){
  282. case Local -> {
  283. int interval = series.element.getPeriod().getInterval();
  284. yield ((double) cur % interval) / ((double) interval);
  285. }
  286. case Global -> ((double) cur) / ((double) max);
  287. };
  288. Vec2i oben = new Vec2i(border + (int) (where * widthWithBorder), 0);
  289. Vec2i unten = new Vec2i(border + (int) (where * widthWithBorder),
  290. 2 * border + heightWithBorder);
  291. g.setColor(series.color);
  292. drawLine(g, oben, unten);
  293. }
  294. } else {
  295. double where;
  296. if (!isUsingLocalPeriod()) {
  297. where = ((double) cur) / ((double) max);
  298. } else {
  299. int lPeriod = getFirstLocalPeriod();
  300. where = ((double) cur % lPeriod) / ((double) lPeriod);
  301. }
  302. Vec2i oben = new Vec2i(border + (int) (where * widthWithBorder), 0);
  303. Vec2i unten = new Vec2i(border + (int) (where * widthWithBorder), 2 * border + heightWithBorder);
  304. g.setColor(dotColor);
  305. drawLine(g, oben, unten);
  306. }
  307. }
  308. /**
  309. * Helper Method to draw the UnitGraphPanel.
  310. * {@link UnitGraph#paintComponent(Graphics)}
  311. * <p>
  312. * This Method draws a line between two Positions on the Canvas.
  313. *
  314. * @param g to draw.
  315. * @param start the Position of one end of the line to draw.
  316. * @param end the other Ends Position of the Line to draw.
  317. */
  318. private void drawLine(Graphics2D g, Vec2i start, Vec2i end) {
  319. Path2D.Double path = new Path2D.Double();
  320. path.moveTo(start.getX(), start.getY());
  321. path.lineTo(end.getX(), end.getY());
  322. g.draw(path);
  323. }
  324. /**
  325. * Helper Method to draw the UnitGraphPanel.
  326. * {@link UnitGraph#paintComponent(Graphics)}
  327. * <p>
  328. * Initialize a Cubic BezierCurve.
  329. *
  330. * @param start The Position to start the Curve.
  331. */
  332. private Path2D.Double initBezier(Vec2i start) {
  333. // Good Source for basic understanding for Bezier Curves
  334. // http://www.theappguruz.com/blog/bezier-curve-in-games
  335. Path2D.Double path = new Path2D.Double();
  336. path.moveTo(start.getX(), start.getY());
  337. return path;
  338. }
  339. /**
  340. * Helper Method to draw the UnitGraphPanel.
  341. * {@link UnitGraph#paintComponent(Graphics)}
  342. * <p>
  343. * Calculate the Path of a the Cubic BezierCurve with the special controlPoints
  344. * to make the wanted behavior.
  345. *
  346. * @param path the path of the Bezier.
  347. * @param actual the actual Position of the Path.
  348. * @param target the end Position of the Curve.
  349. */
  350. private void curveTo(Path2D.Double path, Vec2i actual, Vec2i target) {
  351. double mitte = (actual.getX() + target.getX()) * 0.5;
  352. path.curveTo(mitte, actual.getY(), mitte, target.getY(), target.getX(), target.getY());
  353. }
  354. /**
  355. * Helper Method to draw the UnitGraphPanel.
  356. * {@link UnitGraph#paintComponent(Graphics)}
  357. * <p>
  358. * Draws a Dot at a Position.
  359. *
  360. * @param g to draw.
  361. * @param p the position of the Dot.
  362. */
  363. private void drawDot(Graphics2D g, Vec2i p) {
  364. g.fillOval(p.getX() - dotSize / 2, p.getY() - dotSize / 2, dotSize, dotSize);
  365. }
  366. /**
  367. * Helper Method to draw the UnitGraphPanel.
  368. * {@link UnitGraph#paintComponent(Graphics)}
  369. * <p>
  370. * This Method draws the UnitGraph as BoolGraph.
  371. *
  372. * @param g to draw.
  373. */
  374. private void drawBoolGraph(Graphics2D g, Series series) {
  375. if (series.points.size() <= 1)
  376. return;
  377. LinkedList<Vec2i> cornerPoints = new LinkedList<Vec2i>();
  378. ListIterator<UnitGraphPoint> iter = series.points.listIterator();
  379. Vec2i actual = series.points.getFirst().displayedPosition;
  380. Path2D.Double path = new Path2D.Double();
  381. path.moveTo(actual.getX(), actual.getY());
  382. while (iter.hasNext()) {
  383. Vec2i target = iter.next().displayedPosition;
  384. // BooleanConnection
  385. path.lineTo(target.getX(), actual.getY()); // line to corner
  386. cornerPoints.add(new Vec2i(target.getX(), actual.getY())); // save corner
  387. path.lineTo(target.getX(), target.getY()); // line to next Point
  388. actual = target;
  389. }
  390. g.draw(path);
  391. // Draw the Points on the Corner that dont exist in Data but should be visual
  392. g.setColor(dotColor);
  393. for (Vec2i p : cornerPoints) {
  394. drawDot(g, p);
  395. }
  396. }
  397. /**
  398. * Helper Method to draw the UnitGraphPanel.
  399. * {@link UnitGraph#paintComponent(Graphics)}
  400. * <p>
  401. * This Method draws the UnitGraph as BoolGraph in EditMode.
  402. *
  403. * @param g to draw.
  404. */
  405. private void drawBoolGraphInEditMode(Graphics2D g, Series series) {
  406. LinkedList<Vec2i> before = new LinkedList<Vec2i>();
  407. LinkedList<Vec2i> after = new LinkedList<Vec2i>();
  408. for (UnitGraphPoint p : series.points) {
  409. if (p.displayedPosition.getX() < editPosition.getX())
  410. before.add(p.displayedPosition);
  411. else
  412. after.add(p.displayedPosition);
  413. }
  414. g.setColor(series.color);
  415. drawBoolGraphFromList(g, before);
  416. g.setColor(series.color);
  417. drawBoolGraphFromList(g, after);
  418. // EditGraph
  419. LinkedList<Vec2i> middle = new LinkedList<Vec2i>();
  420. if (!before.isEmpty())
  421. middle.add(before.getLast());
  422. middle.add(editPosition);
  423. if (!after.isEmpty())
  424. middle.add(after.getFirst());
  425. g.setColor(editDotColor);
  426. drawBoolGraphFromList(g, middle);
  427. }
  428. /**
  429. * Helper Method to draw the UnitGraphPanel.
  430. * {@link UnitGraph#paintComponent(Graphics)}
  431. * <p>
  432. * This Method draws a red Hint to signal the User the snapping of the hovered
  433. * Point under the Cursor in EditMode.
  434. *
  435. * @param g to draw.
  436. */
  437. private void drawSnappingHint(Graphics2D g) {
  438. // ColorHint
  439. g.setColor(Color.RED);
  440. // Threshhold Line
  441. final float dash1[] = { 10.0f };
  442. final BasicStroke dashed = new BasicStroke(2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, dash1,
  443. 0.0f);
  444. g.setStroke(dashed);
  445. int halfheight = border + heightWithBorder / 2;
  446. g.drawLine(0, halfheight, widthWithBorder + 2 * border, halfheight);
  447. // Threshhold Text
  448. g.drawString("Snapping Threshold", 10, halfheight - 2);
  449. }
  450. /**
  451. * Helper Method to draw the UnitGraphPanel.
  452. * {@link UnitGraph#paintComponent(Graphics)}
  453. * <p>
  454. * This Method draws a partial Graph from a Position List as BoolGraph.
  455. *
  456. * @param g to draw.
  457. * @param list the PositionList to draw a BoolGraph
  458. */
  459. private void drawBoolGraphFromList(Graphics2D g, LinkedList<Vec2i> list) {
  460. if (list.size() <= 1)
  461. return;
  462. ListIterator<Vec2i> iter = list.listIterator();
  463. LinkedList<Vec2i> cornerPoints = new LinkedList<Vec2i>();
  464. Vec2i actual = list.getFirst();
  465. Path2D.Double path = new Path2D.Double();
  466. path.moveTo(actual.getX(), actual.getY());
  467. while (iter.hasNext()) {
  468. Vec2i target = iter.next();
  469. // BooleanConnection
  470. path.lineTo(target.getX(), actual.getY()); // line to corner
  471. cornerPoints.add(new Vec2i(target.getX(), actual.getY())); // save corner
  472. path.lineTo(target.getX(), target.getY()); // line to next Point
  473. actual = target;
  474. }
  475. g.draw(path);
  476. g.setColor(dotColor);
  477. for (Vec2i p : cornerPoints) {
  478. drawDot(g, p);
  479. }
  480. }
  481. /**
  482. * Helper Method to draw the UnitGraphPanel.
  483. * {@link UnitGraph#paintComponent(Graphics)}
  484. * <p>
  485. * This Method draws the UnitGraph as DoubleGraph.
  486. *
  487. * @param g to draw.
  488. */
  489. private void drawDoubleGraph(Graphics2D g, List<UnitGraphPoint> points) {
  490. if (points.isEmpty())
  491. return;
  492. ListIterator<UnitGraphPoint> iter = points.listIterator();
  493. Vec2i actual = iter.next().displayedPosition;
  494. Path2D.Double path = this.initBezier(actual);
  495. while (iter.hasNext()) {
  496. Vec2i target = iter.next().displayedPosition;
  497. this.curveTo(path, actual, target);
  498. actual = target;
  499. }
  500. g.draw(path);
  501. }
  502. /**
  503. * Helper Method to draw the UnitGraphPanel.
  504. * {@link UnitGraph#paintComponent(Graphics)}
  505. * <p>
  506. * This Method draws the UnitGraph as DoubleGraph in EditMode.
  507. *
  508. * @param g to draw.
  509. */
  510. private void drawDoubleGraphInEditMode(Graphics2D g, Series series) {
  511. LinkedList<Vec2i> before = new LinkedList<Vec2i>();
  512. LinkedList<Vec2i> after = new LinkedList<Vec2i>();
  513. for (UnitGraphPoint p : series.points) {
  514. if (p.displayedPosition.getX() < editPosition.getX())
  515. before.add(p.displayedPosition);
  516. else
  517. after.add(p.displayedPosition);
  518. }
  519. drawUnitGraphFromList(g, before);
  520. drawUnitGraphFromList(g, after);
  521. // EditGraph
  522. LinkedList<Vec2i> middle = new LinkedList<Vec2i>();
  523. if (!before.isEmpty())
  524. middle.add(before.getLast());
  525. middle.add(editPosition);
  526. if (!after.isEmpty())
  527. middle.add(after.getFirst());
  528. g.setColor(editDotColor);
  529. drawUnitGraphFromList(g, middle);
  530. }
  531. /**
  532. * Helper Method to draw the UnitGraphPanel.
  533. * {@link UnitGraph#paintComponent(Graphics)}
  534. * <p>
  535. * This Method draws a partial Graph from a Position List as DoubleGraph.
  536. *
  537. * @param g to draw.
  538. * @param list the PositionList to draw a DoubleGraph
  539. */
  540. private void drawUnitGraphFromList(Graphics2D g, LinkedList<Vec2i> list) {
  541. if (list.size() <= 1)
  542. return;
  543. ListIterator<Vec2i> iter = list.listIterator();
  544. Vec2i actual = list.getFirst();
  545. Path2D.Double path = this.initBezier(actual);
  546. while (iter.hasNext()) {
  547. Vec2i target = iter.next();
  548. curveTo(path, actual, target);
  549. actual = target;
  550. }
  551. g.draw(path);
  552. }
  553. // Under the hood functions to calculate and function the
  554. /**
  555. * A unitgraphpoint have a x and y position to store the data of a graph point.
  556. * Also it have a displayposition to store the Position of the GraphPoints on
  557. * the Canvas. e.g. when the canvas has 500 width and 200 height a GraphPoint
  558. * with the X=0.5 and Y=1.0 should have a displayposition of (250,3) when border
  559. * is 3.
  560. */
  561. private void updateRepresentativePositions() {
  562. for (Series series : seriesList) {
  563. for (UnitGraphPoint p : series.points) {
  564. p.calcDisplayedPosition(border, widthWithBorder, heightWithBorder);
  565. }
  566. }
  567. Optional.ofNullable(this.globalCurve).ifPresent(curve -> {
  568. for (UnitGraphPoint p : curve.points) {
  569. p.calcDisplayedPosition(border, widthWithBorder, heightWithBorder);
  570. }
  571. for (UnitGraphPoint p : curve.zeroLinePoints) {
  572. p.calcDisplayedPosition(border, widthWithBorder, heightWithBorder);
  573. }
  574. });
  575. }
  576. /**
  577. * Takes a List of GraphPoints and convert it to the actual UnitGraphPoints with
  578. * displayposition in the {@link #seriesList}
  579. *
  580. * @param stateCurve the list of GraphPoint
  581. */
  582. private void overrideUnitGraph(Series series, LinkedList<Vec2f> stateCurve) {
  583. series.points.clear();
  584. for (Vec2f p : stateCurve) {
  585. UnitGraphPoint point = new UnitGraphPoint(p);
  586. point.calcDisplayedPosition(border, widthWithBorder, heightWithBorder);
  587. series.points.add(point);
  588. }
  589. }
  590. /**
  591. * When the PanelSize Change the width and height to calculate the drawings have
  592. * to be adjusted.
  593. */
  594. private void calculateWidthHeight() {
  595. widthWithBorder = this.getWidth() - 2 * border;
  596. heightWithBorder = this.getHeight() - 2 * border;
  597. }
  598. /**
  599. * Save the actualGraphPoint List to the GraphEditable Element.
  600. */
  601. private void saveGraph() {
  602. for (Series series : seriesList) {
  603. LinkedList<Vec2f> actual = series.element.getStateGraph();
  604. actual.clear();
  605. for (UnitGraphPoint p : series.points) {
  606. actual.add(p.getPoint());
  607. }
  608. series.element.sampleGraph();
  609. }
  610. }
  611. /**
  612. * Remove a UnitGraphPoint from the UnitGraphPoint list ({@link #seriesList}
  613. * when its near a given Position.
  614. *
  615. * @param mPosition
  616. */
  617. private void removePointNearPosition(Series series, Vec2i mPosition) {
  618. ListIterator<UnitGraphPoint> iter = series.points.listIterator();
  619. while (iter.hasNext()) {
  620. if (near(mPosition, iter.next().displayedPosition, series.type)) {
  621. iter.remove();
  622. break;
  623. }
  624. }
  625. }
  626. private void removePointsNearPosition(Vec2i mPosition) {
  627. for (Series series : seriesList) {
  628. removePointNearPosition(series, mPosition);
  629. }
  630. }
  631. private Optional<Series> detectSeries(Vec2i mPosition) {
  632. return seriesList.stream().min((a, b) -> {
  633. float minDistanceA = a.points.stream().map(point -> point.displayedPosition.getSquaredDistance(mPosition))
  634. .min(Float::compare).get();
  635. float minDistanceB = b.points.stream().map(point -> point.displayedPosition.getSquaredDistance(mPosition))
  636. .min(Float::compare).get();
  637. return Float.compare(minDistanceA, minDistanceB);
  638. });
  639. }
  640. /**
  641. * Determine if the Point is a StartPoint , EndPoint or a NormalPoint a.k.a. in
  642. * between Points.
  643. *
  644. * @param mPosition The Position to check.
  645. */
  646. private EditPointType detectStartEndPoint(Series series, Vec2i mPosition) {
  647. UnitGraphPoint first = series.points.getFirst();
  648. UnitGraphPoint last = series.points.getLast();
  649. if (near(mPosition, first.displayedPosition, series.type))
  650. return EditPointType.StartPoint;
  651. else if (near(mPosition, last.displayedPosition, series.type))
  652. return EditPointType.EndPoint;
  653. else
  654. return EditPointType.Normal;
  655. }
  656. /**
  657. * Determine if a Point is near the Cursor (depends on Mode what near means). To
  658. * detect if it should grab the Point or create a new Point.
  659. *
  660. */
  661. private boolean near(Vec2i actual, Vec2i target, GraphType graphType) {
  662. switch (graphType) {
  663. case boolGraph: // Distance only with X
  664. int xDis = target.getX() - actual.getX();
  665. return xDis * xDis < clickThresholdSquared;
  666. case doubleGraph:
  667. return actual.getSquaredDistance(target) < clickThresholdSquared;
  668. default:
  669. return false;
  670. }
  671. }
  672. /**
  673. * When the Mouse Drag a Point it updates each time the position.
  674. *
  675. */
  676. private void updateEditPointPosition(Vec2i newPosition, EditPointType editPointType, GraphType graphType) {
  677. // make it in the bounds of the UnitGraph no Point out of the Border
  678. Vec2i currentPosition = setInBounds(newPosition);
  679. if (editPointType != EditPointType.Normal) {
  680. attachToBorder(currentPosition, editPointType);
  681. }
  682. if (graphType == GraphType.boolGraph) {
  683. snapBoolean(currentPosition);
  684. }
  685. editPosition = currentPosition;
  686. }
  687. /**
  688. * No Point on the UnitGraph should exit the UnitGraph.
  689. *
  690. * @param p the Position
  691. * @return the updated Position.
  692. */
  693. private Vec2i setInBounds(Vec2i p) {
  694. p.clampX(border, border + widthWithBorder);
  695. p.clampY(border, border + heightWithBorder);
  696. return p;
  697. }
  698. /**
  699. * For Switches the Point have to be Snap to the Top or the Bottem.
  700. *
  701. * @param p the Position
  702. * @return the updated Position.
  703. */
  704. private Vec2i snapBoolean(Vec2i p) {
  705. if (p.getY() < border + heightWithBorder / 2) {
  706. p.setY(border);
  707. } else {
  708. p.setY(border + heightWithBorder);
  709. }
  710. return p;
  711. }
  712. /**
  713. * The First Point has to be at 0(LeftSide) and Last Point has to be at
  714. * 1(RightSide).
  715. *
  716. * @param p the Position
  717. * @return the updated Position.
  718. */
  719. private Vec2i attachToBorder(Vec2i p, EditPointType editPointType) {
  720. switch (editPointType) {
  721. case StartPoint:
  722. p.setX(border);
  723. break;
  724. case EndPoint:
  725. p.setX(border + widthWithBorder);
  726. break;
  727. default:
  728. break;
  729. }
  730. return p;
  731. }
  732. /**
  733. * Insert a Position in the UnitGraphList at the right order. Its sorted based
  734. * on the xValues.
  735. *
  736. * @param pos The new UnitGraphPoints Position
  737. */
  738. private void insertNewGraphPoint(Series series, Vec2i pos) {
  739. setInBounds(pos);
  740. ListIterator<UnitGraphPoint> iter = series.points.listIterator();
  741. while (iter.hasNext()) {
  742. Vec2i tempPosition = iter.next().displayedPosition;
  743. if (pos.getX() <= tempPosition.getX()) {
  744. // previous to go back a position to make the new point before the the Position
  745. // with greater X
  746. iter.previous();
  747. iter.add(generateUnitGraphPoint(pos));
  748. break;
  749. }
  750. }
  751. if (!iter.hasNext()) // if behind last point
  752. {
  753. iter.add(generateUnitGraphPoint(pos));
  754. }
  755. }
  756. /**
  757. * Generate a UnitGraphPoint from a normal Position in the UnitGraph.
  758. *
  759. * @param pos the normal pos with xValues from 0..Width and yValues from
  760. * 0..Height
  761. * @return a UnitGraphPoint
  762. */
  763. private UnitGraphPoint generateUnitGraphPoint(Vec2i pos) {
  764. UnitGraphPoint temp = new UnitGraphPoint((double) (pos.getX() - border) / (double) widthWithBorder,
  765. 1 - (double) (pos.getY() - border) / (double) heightWithBorder, true);
  766. temp.displayedPosition = pos;
  767. return temp;
  768. }
  769. /**
  770. * Update the Point Position
  771. */
  772. @Override
  773. public void mouseDragged(MouseEvent e) {
  774. Optional.ofNullable(this.actualSeries).ifPresent(series -> {
  775. updateEditPointPosition(new Vec2i(e.getPoint().x, e.getPoint().y), this.editPointType, series.type);
  776. updateGlobalCurve();
  777. repaint();
  778. });
  779. }
  780. @Override
  781. public void mouseMoved(MouseEvent e) {
  782. }
  783. @Override
  784. public void mouseClicked(MouseEvent e) {
  785. }
  786. @Override
  787. public void mouseEntered(MouseEvent e) {
  788. }
  789. @Override
  790. public void mouseExited(MouseEvent e) {
  791. }
  792. /**
  793. * The First Step. When LeftMouseButton its checks if a point is to grab under
  794. * the cursor or create a new Point. Then enter EditMode. When RightMouseButton
  795. * its delete a point if its under the Curser.
  796. */
  797. @Override
  798. public void mousePressed(MouseEvent e) {
  799. Vec2i mPosition = new Vec2i(e.getPoint().x, e.getPoint().y);
  800. detectSeries(mPosition).ifPresent(series -> {
  801. actualSeries = series;
  802. if (e.getButton() == MouseEvent.BUTTON3) {
  803. // RightMouseButtonEvent
  804. editPointType = detectStartEndPoint(series, mPosition);
  805. if (editPointType == EditPointType.Normal) {
  806. removePointsNearPosition(mPosition);
  807. repaint();
  808. }
  809. editMode = false;
  810. } else if (e.getButton() == MouseEvent.BUTTON1) {
  811. // LeftMouseButtonEvent
  812. editPointType = detectStartEndPoint(series, mPosition);
  813. removePointsNearPosition(mPosition);
  814. updateEditPointPosition(mPosition, editPointType, series.type);
  815. editMode = true;
  816. repaint();
  817. }
  818. });
  819. }
  820. /**
  821. * The last step to save the Changes. Its insert the Hovering Point and exit
  822. * EditMode.
  823. */
  824. @Override
  825. public void mouseReleased(MouseEvent e) {
  826. if (editMode && actualSeries != null) {
  827. for (Series series : seriesList) {
  828. this.insertNewGraphPoint(series, editPosition);
  829. }
  830. editMode = false;
  831. }
  832. saveGraph();
  833. updateGlobalCurve();
  834. repaint();
  835. }
  836. /**
  837. * When the Component is Resized.
  838. *
  839. * @param e ComponentEvent
  840. */
  841. public void componentResized(ComponentEvent e) {
  842. calculateWidthHeight();
  843. updateRepresentativePositions();
  844. repaint();
  845. }
  846. @Override
  847. public void componentHidden(ComponentEvent e) {
  848. }
  849. @Override
  850. public void componentMoved(ComponentEvent e) {
  851. }
  852. @Override
  853. public void componentShown(ComponentEvent e) {
  854. }
  855. /**
  856. * Resets the graph to normal.
  857. */
  858. public void reset() {
  859. for (Series series : seriesList) {
  860. series.element.reset();
  861. overrideUnitGraph(series, series.element.getStateGraph());
  862. }
  863. repaint();
  864. }
  865. public void setPeriod(LocalMode.Period period) {
  866. for (Series series : seriesList) {
  867. series.element.setPeriod(new LocalMode.Period(period));
  868. }
  869. }
  870. public boolean isLocalPeriedDifferentInSeries() {
  871. return seriesList.stream().map(series -> series.element.getPeriod().getInterval()).distinct().count() > 1;
  872. }
  873. public int getFirstLocalPeriod() {
  874. return seriesList.isEmpty() ? 0 : seriesList.get(0).element.getPeriod().getInterval();
  875. }
  876. public boolean isUsingLocalPeriod() {
  877. return seriesList.stream().anyMatch(series -> series.element.getPeriod().getType() != LocalMode.Period.PeriodType.Global);
  878. }
  879. }