UnitGraph.java 29 KB

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