UnitGraph.java 30 KB

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