UnitGraph.java 30 KB

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