UnitGraph.java 28 KB

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