UnitGraph.java 29 KB

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