UnitGraph.java 28 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.ui.controller.Control;
  27. import holeg.model.Model;
  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 clickThreshholdSquared = 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. // Intern Variables
  56. private class Series {
  57. public LinkedList<UnitGraphPoint> points = new LinkedList<UnitGraphPoint>();
  58. public TimelineDependent element;
  59. public GraphType type;
  60. public Color color;
  61. }
  62. private final ArrayList<Series> seriesList = new ArrayList<>();
  63. private Vec2i editPosition;
  64. private Optional<Series> actualSeries;
  65. private class GlobalCurve {
  66. public LinkedList<UnitGraphPoint> points = new LinkedList<UnitGraphPoint>();
  67. public float minEnergy;
  68. public float maxEnergy;
  69. public LinkedList<UnitGraphPoint> zeroLinePoints = new LinkedList<UnitGraphPoint>();
  70. }
  71. private Optional<GlobalCurve> globalCurve = Optional.empty();
  72. private boolean editMode = false;
  73. private Set<HolonElement> elements;
  74. private enum EditPointType {
  75. Normal, StartPoint, EndPoint
  76. };
  77. private Model model;
  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.model = control.getModel();
  87. this.setBackground(Color.WHITE);
  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. float[] sample = new float[model.getMaxIterations()];
  122. // sample energy
  123. for (HolonElement element : elements) {
  124. for (int i = 0; i < model.getMaxIterations(); i++) {
  125. sample[i] += element.calculateExpectedEnergyAtTimeStep(i);
  126. }
  127. }
  128. // sample curve
  129. for (int i = 0; i < model.getMaxIterations(); i++) {
  130. curve.points.add(new UnitGraphPoint((double) i / (double)model.getMaxIterations(),
  131. Maths.invLerp(curve.minEnergy, curve.maxEnergy, sample[i]), false));
  132. }
  133. // update displayPosition
  134. for (UnitGraphPoint p : curve.points) {
  135. p.calcDisplayedPosition(border, widthWithBorder, heightWithBorder);
  136. }
  137. double zeroLineYPos = Maths.invLerp(curve.minEnergy, curve.maxEnergy, 0.0);
  138. curve.zeroLinePoints.add(new UnitGraphPoint(0.0,zeroLineYPos, false));
  139. curve.zeroLinePoints.add(new UnitGraphPoint(1.0,zeroLineYPos, false));
  140. for (UnitGraphPoint p : curve.zeroLinePoints) {
  141. p.calcDisplayedPosition(border, widthWithBorder, heightWithBorder);
  142. }
  143. // set global curve
  144. this.globalCurve = Optional.of(curve);
  145. this.elements = elements;
  146. }
  147. private void updateGlobalCurve() {
  148. setGlobalCurve(this.elements);
  149. }
  150. /**
  151. * Paints the Graph, the Grid, the actual Line from the currentIteration
  152. *
  153. * @param g Graphics
  154. */
  155. public void paintComponent(Graphics g) {
  156. super.paintComponent(g);
  157. Graphics2D g2d = (Graphics2D) g;
  158. drawGrid(g2d);
  159. g2d.setColor(Color.BLACK);
  160. g2d.setRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
  161. g2d.setStroke(new BasicStroke(2f));
  162. drawUnitGraph(g2d);
  163. g2d.setColor(dotColor);
  164. if (editMode) {
  165. drawUnitGraphPointsReleased(g2d);
  166. } else {
  167. drawUnitGraphPoints(g2d);
  168. }
  169. g2d.setColor(dotColor);
  170. g2d.setStroke(new BasicStroke(1));
  171. drawCurrentIterartionLine(g2d);
  172. g2d.setStroke(new BasicStroke(1, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_ROUND, 1.0f, new float[]{6}, 3));
  173. this.globalCurve.ifPresent(curve -> {
  174. g2d.setColor(globalCurveColor);
  175. drawDoubleGraph(g2d, curve.points);
  176. g2d.setColor(zeroLineColor);
  177. g2d.setStroke(new BasicStroke(1));
  178. drawDoubleGraph(g2d, curve.zeroLinePoints);
  179. });
  180. }
  181. // Draw Methods only to let the User see the changes. Nothing its saved here or
  182. // changed.
  183. /**
  184. * Helper Method to draw the UnitGraphPanel.
  185. * {@link UnitGraph#paintComponent(Graphics)}
  186. * <p>
  187. * This Methods draws the UnitGraph whether its a boolGraph or a doubleGraph.
  188. *
  189. * @param g to draw.
  190. */
  191. private void drawUnitGraph(Graphics2D g) {
  192. boolean drawSnappingHintFlag = false;
  193. for (Series series : this.seriesList) {
  194. g.setColor(series.color);
  195. switch (series.type) {
  196. case boolGraph:
  197. if (editMode) {
  198. drawBoolGraphInEditMode(g, series);
  199. drawSnappingHintFlag = true;
  200. } else
  201. drawBoolGraph(g, series);
  202. break;
  203. case doubleGraph:
  204. if (editMode)
  205. drawDoubleGraphInEditMode(g, series);
  206. else
  207. drawDoubleGraph(g, series.points);
  208. break;
  209. default:
  210. throw new UnsupportedOperationException();
  211. }
  212. }
  213. if (drawSnappingHintFlag) {
  214. drawSnappingHint(g);
  215. }
  216. }
  217. /**
  218. * Helper Method to draw the UnitGraphPanel.
  219. * {@link UnitGraph#paintComponent(Graphics)}
  220. * <p>
  221. * This Methods draws the UnitGraphPoints of the UnitGraph.
  222. *
  223. * @param g to draw.
  224. */
  225. private void drawUnitGraphPoints(Graphics2D g) {
  226. g.setColor(dotColor);
  227. for (Series series : seriesList) {
  228. for (UnitGraphPoint p : series.points) {
  229. drawDot(g, p.displayedPosition);
  230. }
  231. }
  232. }
  233. /**
  234. * Helper Method to draw the UnitGraphPanel.
  235. * {@link UnitGraph#paintComponent(Graphics)}
  236. * <p>
  237. * This Methods draws the UnitGraphPoints of the UnitGraph when its in EditMode.
  238. *
  239. * @param g to draw.
  240. */
  241. private void drawUnitGraphPointsReleased(Graphics2D g) {
  242. drawUnitGraphPoints(g);
  243. g.setColor(editDotColor);
  244. drawDot(g, editPosition);
  245. }
  246. /**
  247. * Helper Method to draw the UnitGraphPanel.
  248. * {@link UnitGraph#paintComponent(Graphics)}
  249. * <p>
  250. * This Methods draws the Grid on the Canvas.
  251. *
  252. * @param g to draw.
  253. */
  254. private void drawGrid(Graphics2D g) {
  255. g.setStroke(new BasicStroke(1));
  256. g.setColor(Color.lightGray);
  257. int amountOfLines = 10;
  258. int width = widthWithBorder + 2 * border;
  259. int height = heightWithBorder;
  260. for (int i = 0; i <= amountOfLines; i++) {
  261. int linehieght = (int) (((double) i / (double) amountOfLines) * (double) height) + border;
  262. g.drawLine(0, linehieght, width, linehieght);
  263. }
  264. }
  265. /**
  266. * Helper Method to draw the UnitGraphPanel.
  267. * {@link UnitGraph#paintComponent(Graphics)}
  268. * <p>
  269. * This Method draws the CurrentIterationLine.
  270. *
  271. * @param g to draw.
  272. */
  273. private void drawCurrentIterartionLine(Graphics2D g) {
  274. int cur = model.getCurrentIteration();
  275. int max = model.getMaxIterations();
  276. if (isLocalPeriedDifferentInSeries()) {
  277. for (Series series : seriesList) {
  278. double where = switch(series.element.getPeriod().getType()){
  279. case Local -> {
  280. int interval = series.element.getPeriod().getInterval();
  281. yield ((double) cur % interval) / ((double) interval);
  282. }
  283. case Global -> ((double) cur) / ((double) max);
  284. };
  285. Vec2i oben = new Vec2i(border + (int) (where * widthWithBorder), 0);
  286. Vec2i unten = new Vec2i(border + (int) (where * widthWithBorder),
  287. 2 * border + heightWithBorder);
  288. g.setColor(series.color);
  289. drawLine(g, oben, unten);
  290. }
  291. } else {
  292. double where;
  293. if (!isUsingLocalPeriod()) {
  294. where = ((double) cur) / ((double) max);
  295. } else {
  296. int lPeriod = getFirstLocalPeriod();
  297. where = ((double) cur % lPeriod) / ((double) lPeriod);
  298. }
  299. Vec2i oben = new Vec2i(border + (int) (where * widthWithBorder), 0);
  300. Vec2i unten = new Vec2i(border + (int) (where * widthWithBorder), 2 * border + heightWithBorder);
  301. g.setColor(dotColor);
  302. drawLine(g, oben, unten);
  303. }
  304. }
  305. /**
  306. * Helper Method to draw the UnitGraphPanel.
  307. * {@link UnitGraph#paintComponent(Graphics)}
  308. * <p>
  309. * This Method draws a line between two Positions on the Canvas.
  310. *
  311. * @param g to draw.
  312. * @param start the Position of one end of the line to draw.
  313. * @param end the other Ends Position of the Line to draw.
  314. */
  315. private void drawLine(Graphics2D g, Vec2i start, Vec2i end) {
  316. Path2D.Double path = new Path2D.Double();
  317. path.moveTo(start.getX(), start.getY());
  318. path.lineTo(end.getX(), end.getY());
  319. g.draw(path);
  320. }
  321. /**
  322. * Helper Method to draw the UnitGraphPanel.
  323. * {@link UnitGraph#paintComponent(Graphics)}
  324. * <p>
  325. * Initialize a Cubic BezierCurve.
  326. *
  327. * @param start The Position to start the Curve.
  328. */
  329. private Path2D.Double initBezier(Vec2i start) {
  330. // Good Source for basic understanding for Bezier Curves
  331. // http://www.theappguruz.com/blog/bezier-curve-in-games
  332. Path2D.Double path = new Path2D.Double();
  333. path.moveTo(start.getX(), start.getY());
  334. return path;
  335. }
  336. /**
  337. * Helper Method to draw the UnitGraphPanel.
  338. * {@link UnitGraph#paintComponent(Graphics)}
  339. * <p>
  340. * Calculate the Path of a the Cubic BezierCurve with the special controlPoints
  341. * to make the wanted behavior.
  342. *
  343. * @param path the path of the Bezier.
  344. * @param actual the actual Position of the Path.
  345. * @param target the end Position of the Curve.
  346. */
  347. private void curveTo(Path2D.Double path, Vec2i actual, Vec2i target) {
  348. double mitte = (actual.getX() + target.getX()) * 0.5;
  349. path.curveTo(mitte, actual.getY(), mitte, target.getY(), target.getX(), target.getY());
  350. }
  351. /**
  352. * Helper Method to draw the UnitGraphPanel.
  353. * {@link UnitGraph#paintComponent(Graphics)}
  354. * <p>
  355. * Draws a Dot at a Position.
  356. *
  357. * @param g to draw.
  358. * @param p the position of the Dot.
  359. */
  360. private void drawDot(Graphics2D g, Vec2i p) {
  361. g.fillOval(p.getX() - dotSize / 2, p.getY() - dotSize / 2, dotSize, dotSize);
  362. }
  363. /**
  364. * Helper Method to draw the UnitGraphPanel.
  365. * {@link UnitGraph#paintComponent(Graphics)}
  366. * <p>
  367. * This Method draws the UnitGraph as BoolGraph.
  368. *
  369. * @param g to draw.
  370. */
  371. private void drawBoolGraph(Graphics2D g, Series series) {
  372. if (series.points.size() <= 1)
  373. return;
  374. LinkedList<Vec2i> cornerPoints = new LinkedList<Vec2i>();
  375. ListIterator<UnitGraphPoint> iter = series.points.listIterator();
  376. Vec2i actual = series.points.getFirst().displayedPosition;
  377. Path2D.Double path = new Path2D.Double();
  378. path.moveTo(actual.getX(), actual.getY());
  379. while (iter.hasNext()) {
  380. Vec2i target = iter.next().displayedPosition;
  381. // BooleanConnection
  382. path.lineTo(target.getX(), actual.getY()); // line to corner
  383. cornerPoints.add(new Vec2i(target.getX(), actual.getY())); // save corner
  384. path.lineTo(target.getX(), target.getY()); // line to next Point
  385. actual = target;
  386. }
  387. g.draw(path);
  388. // Draw the Points on the Corner that dont exist in Data but should be visual
  389. g.setColor(dotColor);
  390. for (Vec2i p : cornerPoints) {
  391. drawDot(g, p);
  392. }
  393. }
  394. /**
  395. * Helper Method to draw the UnitGraphPanel.
  396. * {@link UnitGraph#paintComponent(Graphics)}
  397. * <p>
  398. * This Method draws the UnitGraph as BoolGraph in EditMode.
  399. *
  400. * @param g to draw.
  401. */
  402. private void drawBoolGraphInEditMode(Graphics2D g, Series series) {
  403. LinkedList<Vec2i> before = new LinkedList<Vec2i>();
  404. LinkedList<Vec2i> after = new LinkedList<Vec2i>();
  405. for (UnitGraphPoint p : series.points) {
  406. if (p.displayedPosition.getX() < editPosition.getX())
  407. before.add(p.displayedPosition);
  408. else
  409. after.add(p.displayedPosition);
  410. }
  411. g.setColor(series.color);
  412. drawBoolGraphFromList(g, before);
  413. g.setColor(series.color);
  414. drawBoolGraphFromList(g, after);
  415. // EditGraph
  416. LinkedList<Vec2i> middle = new LinkedList<Vec2i>();
  417. if (!before.isEmpty())
  418. middle.add(before.getLast());
  419. middle.add(editPosition);
  420. if (!after.isEmpty())
  421. middle.add(after.getFirst());
  422. g.setColor(editDotColor);
  423. drawBoolGraphFromList(g, middle);
  424. }
  425. /**
  426. * Helper Method to draw the UnitGraphPanel.
  427. * {@link UnitGraph#paintComponent(Graphics)}
  428. * <p>
  429. * This Method draws a red Hint to signal the User the snapping of the hovered
  430. * Point under the Cursor in EditMode.
  431. *
  432. * @param g to draw.
  433. */
  434. private void drawSnappingHint(Graphics2D g) {
  435. // ColorHint
  436. g.setColor(Color.RED);
  437. // Threshhold Line
  438. final float dash1[] = { 10.0f };
  439. final BasicStroke dashed = new BasicStroke(2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, dash1,
  440. 0.0f);
  441. g.setStroke(dashed);
  442. int halfheight = border + heightWithBorder / 2;
  443. g.drawLine(0, halfheight, widthWithBorder + 2 * border, halfheight);
  444. // Threshhold Text
  445. g.drawString("Snapping Threshold", 10, halfheight - 2);
  446. }
  447. /**
  448. * Helper Method to draw the UnitGraphPanel.
  449. * {@link UnitGraph#paintComponent(Graphics)}
  450. * <p>
  451. * This Method draws a partial Graph from a Position List as BoolGraph.
  452. *
  453. * @param g to draw.
  454. * @param list the PositionList to draw a BoolGraph
  455. */
  456. private void drawBoolGraphFromList(Graphics2D g, LinkedList<Vec2i> list) {
  457. if (list.size() <= 1)
  458. return;
  459. ListIterator<Vec2i> iter = list.listIterator();
  460. LinkedList<Vec2i> cornerPoints = new LinkedList<Vec2i>();
  461. Vec2i actual = list.getFirst();
  462. Path2D.Double path = new Path2D.Double();
  463. path.moveTo(actual.getX(), actual.getY());
  464. while (iter.hasNext()) {
  465. Vec2i target = iter.next();
  466. // BooleanConnection
  467. path.lineTo(target.getX(), actual.getY()); // line to corner
  468. cornerPoints.add(new Vec2i(target.getX(), actual.getY())); // save corner
  469. path.lineTo(target.getX(), target.getY()); // line to next Point
  470. actual = target;
  471. }
  472. g.draw(path);
  473. g.setColor(dotColor);
  474. for (Vec2i p : cornerPoints) {
  475. drawDot(g, p);
  476. }
  477. }
  478. /**
  479. * Helper Method to draw the UnitGraphPanel.
  480. * {@link UnitGraph#paintComponent(Graphics)}
  481. * <p>
  482. * This Method draws the UnitGraph as DoubleGraph.
  483. *
  484. * @param g to draw.
  485. */
  486. private void drawDoubleGraph(Graphics2D g, List<UnitGraphPoint> points) {
  487. if (points.isEmpty())
  488. return;
  489. ListIterator<UnitGraphPoint> iter = points.listIterator();
  490. Vec2i actual = iter.next().displayedPosition;
  491. Path2D.Double path = this.initBezier(actual);
  492. while (iter.hasNext()) {
  493. Vec2i target = iter.next().displayedPosition;
  494. this.curveTo(path, actual, target);
  495. actual = target;
  496. }
  497. g.draw(path);
  498. }
  499. /**
  500. * Helper Method to draw the UnitGraphPanel.
  501. * {@link UnitGraph#paintComponent(Graphics)}
  502. * <p>
  503. * This Method draws the UnitGraph as DoubleGraph in EditMode.
  504. *
  505. * @param g to draw.
  506. */
  507. private void drawDoubleGraphInEditMode(Graphics2D g, Series series) {
  508. LinkedList<Vec2i> before = new LinkedList<Vec2i>();
  509. LinkedList<Vec2i> after = new LinkedList<Vec2i>();
  510. for (UnitGraphPoint p : series.points) {
  511. if (p.displayedPosition.getX() < editPosition.getX())
  512. before.add(p.displayedPosition);
  513. else
  514. after.add(p.displayedPosition);
  515. }
  516. drawUnitGraphFromList(g, before);
  517. drawUnitGraphFromList(g, after);
  518. // EditGraph
  519. LinkedList<Vec2i> middle = new LinkedList<Vec2i>();
  520. if (!before.isEmpty())
  521. middle.add(before.getLast());
  522. middle.add(editPosition);
  523. if (!after.isEmpty())
  524. middle.add(after.getFirst());
  525. g.setColor(editDotColor);
  526. drawUnitGraphFromList(g, middle);
  527. }
  528. /**
  529. * Helper Method to draw the UnitGraphPanel.
  530. * {@link UnitGraph#paintComponent(Graphics)}
  531. * <p>
  532. * This Method draws a partial Graph from a Position List as DoubleGraph.
  533. *
  534. * @param g to draw.
  535. * @param list the PositionList to draw a DoubleGraph
  536. */
  537. private void drawUnitGraphFromList(Graphics2D g, LinkedList<Vec2i> list) {
  538. if (list.size() <= 1)
  539. return;
  540. ListIterator<Vec2i> iter = list.listIterator();
  541. Vec2i actual = list.getFirst();
  542. Path2D.Double path = this.initBezier(actual);
  543. while (iter.hasNext()) {
  544. Vec2i target = iter.next();
  545. curveTo(path, actual, target);
  546. actual = target;
  547. }
  548. g.draw(path);
  549. }
  550. // Under the hood functions to calculate and function the
  551. /**
  552. * A unitgraphpoint have a x and y position to store the data of a graph point.
  553. * Also it have a displayposition to store the Position of the GraphPoints on
  554. * the Canvas. e.g. when the canvas has 500 width and 200 height a GraphPoint
  555. * with the X=0.5 and Y=1.0 should have a displayposition of (250,3) when border
  556. * is 3.
  557. */
  558. private void updateRepresentativePositions() {
  559. for (Series series : seriesList) {
  560. for (UnitGraphPoint p : series.points) {
  561. p.calcDisplayedPosition(border, widthWithBorder, heightWithBorder);
  562. }
  563. }
  564. this.globalCurve.ifPresent(curve -> {
  565. for (UnitGraphPoint p : curve.points) {
  566. p.calcDisplayedPosition(border, widthWithBorder, heightWithBorder);
  567. }
  568. for (UnitGraphPoint p : curve.zeroLinePoints) {
  569. p.calcDisplayedPosition(border, widthWithBorder, heightWithBorder);
  570. }
  571. });
  572. }
  573. /**
  574. * Takes a List of GraphPoints and convert it to the actual UnitGraphPoints with
  575. * displayposition in the {@link #seriesList}
  576. *
  577. * @param stateCurve the list of GraphPoint
  578. */
  579. private void overrideUnitGraph(Series series, LinkedList<Vec2f> stateCurve) {
  580. series.points.clear();
  581. for (Vec2f p : stateCurve) {
  582. UnitGraphPoint point = new UnitGraphPoint(p);
  583. point.calcDisplayedPosition(border, widthWithBorder, heightWithBorder);
  584. series.points.add(point);
  585. }
  586. }
  587. /**
  588. * When the PanelSize Change the width and height to calculate the drawings have
  589. * to be adjusted.
  590. */
  591. private void calculateWidthHeight() {
  592. widthWithBorder = this.getWidth() - 2 * border;
  593. heightWithBorder = this.getHeight() - 2 * border;
  594. }
  595. /**
  596. * Save the actualGraphPoint List to the GraphEditable Element.
  597. */
  598. private void saveGraph() {
  599. for (Series series : seriesList) {
  600. LinkedList<Vec2f> actual = series.element.getStateGraph();
  601. actual.clear();
  602. for (UnitGraphPoint p : series.points) {
  603. actual.add(p.getPoint());
  604. }
  605. series.element.sampleGraph();
  606. }
  607. }
  608. /**
  609. * Remove a UnitGraphPoint from the UnitGraphPoint list ({@link #seriesList}
  610. * when its near a given Position.
  611. *
  612. * @param mPosition
  613. */
  614. private void removePointNearPosition(Series series, Vec2i mPosition) {
  615. ListIterator<UnitGraphPoint> iter = series.points.listIterator();
  616. while (iter.hasNext()) {
  617. if (near(mPosition, iter.next().displayedPosition, series.type)) {
  618. iter.remove();
  619. break;
  620. }
  621. }
  622. }
  623. private void removePointsNearPosition(Vec2i mPosition) {
  624. for (Series series : seriesList) {
  625. removePointNearPosition(series, mPosition);
  626. }
  627. }
  628. private Optional<Series> detectSeries(Vec2i mPosition) {
  629. return seriesList.stream().min((a, b) -> {
  630. float minDistanceA = a.points.stream().map(point -> point.displayedPosition.getSquaredDistance(mPosition))
  631. .min(Float::compare).get();
  632. float minDistanceB = b.points.stream().map(point -> point.displayedPosition.getSquaredDistance(mPosition))
  633. .min(Float::compare).get();
  634. return Float.compare(minDistanceA, minDistanceB);
  635. });
  636. }
  637. /**
  638. * Determine if the Point is a StartPoint , EndPoint or a NormalPoint a.k.a. in
  639. * between Points.
  640. *
  641. * @param mPosition The Position to check.
  642. */
  643. private EditPointType detectStartEndPoint(Series series, Vec2i mPosition) {
  644. UnitGraphPoint first = series.points.getFirst();
  645. UnitGraphPoint last = series.points.getLast();
  646. if (near(mPosition, first.displayedPosition, series.type))
  647. return EditPointType.StartPoint;
  648. else if (near(mPosition, last.displayedPosition, series.type))
  649. return EditPointType.EndPoint;
  650. else
  651. return EditPointType.Normal;
  652. }
  653. /**
  654. * Determine if a Point is near the Cursor (depends on Mode what near means). To
  655. * detect if it should grab the Point or create a new Point.
  656. *
  657. * @param actual
  658. * @param target
  659. * @return
  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 < clickThreshholdSquared;
  666. case doubleGraph:
  667. return actual.getSquaredDistance(target) < clickThreshholdSquared;
  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. 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. actualSeries = detectSeries(mPosition);
  801. actualSeries.ifPresent(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.isPresent()) {
  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. }