|
@@ -0,0 +1,900 @@
|
|
|
+package ui.view.inspector;
|
|
|
+
|
|
|
+import java.awt.BasicStroke;
|
|
|
+import java.awt.Color;
|
|
|
+import java.awt.Cursor;
|
|
|
+import java.awt.Graphics;
|
|
|
+import java.awt.Graphics2D;
|
|
|
+import java.awt.RenderingHints;
|
|
|
+import java.awt.event.ComponentEvent;
|
|
|
+import java.awt.event.ComponentListener;
|
|
|
+import java.awt.event.MouseEvent;
|
|
|
+import java.awt.event.MouseListener;
|
|
|
+import java.awt.event.MouseMotionListener;
|
|
|
+import java.awt.geom.Path2D;
|
|
|
+import java.awt.geom.Point2D;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.LinkedList;
|
|
|
+import java.util.ListIterator;
|
|
|
+import java.util.Optional;
|
|
|
+
|
|
|
+import javax.swing.JPanel;
|
|
|
+
|
|
|
+import classes.HolonElement;
|
|
|
+import interfaces.GraphEditable;
|
|
|
+import interfaces.GraphEditable.GraphType;
|
|
|
+import interfaces.LocalMode;
|
|
|
+import interfaces.TimelineDependent;
|
|
|
+import ui.controller.Control;
|
|
|
+import ui.model.Model;
|
|
|
+import utility.Vector2Int;
|
|
|
+
|
|
|
+/**
|
|
|
+ * This Class represents a Graph where the User can model the behavior of
|
|
|
+ * elements and switches over time.
|
|
|
+ *
|
|
|
+ * @author Tom Troppmann
|
|
|
+ */
|
|
|
+public class UnitGraph extends JPanel implements MouseListener, MouseMotionListener, ComponentListener {
|
|
|
+
|
|
|
+ // Normal Settings
|
|
|
+ private static final int border = 4;
|
|
|
+ private static final int clickThreshholdSquared = 25;
|
|
|
+
|
|
|
+ // Display Settings
|
|
|
+ /**
|
|
|
+ * The size of a dot in the graph. It should be at least 1.
|
|
|
+ */
|
|
|
+ private static final int dotSize = 8;
|
|
|
+
|
|
|
+ /** The Color of a dot in the graph. */
|
|
|
+ private static final Color dotColor = Color.blue;
|
|
|
+ private static final Color editDotColor = new Color(255, 119, 0);
|
|
|
+ private static final Color[] seriesColorArray = { Color.blue, Color.cyan, Color.black, Color.green, Color.gray,
|
|
|
+ Color.magenta, Color.yellow, Color.PINK, Color.red };
|
|
|
+
|
|
|
+ // Intern Variables
|
|
|
+ private class Series {
|
|
|
+ public LinkedList<UnitGraphPoint> points = new LinkedList<UnitGraphPoint>();
|
|
|
+ public TimelineDependent element;
|
|
|
+ public GraphType type;
|
|
|
+ public Color color;
|
|
|
+ }
|
|
|
+
|
|
|
+ private ArrayList<Series> seriesList = new ArrayList<Series>();
|
|
|
+ private Vector2Int editPosition;
|
|
|
+ private Optional<Series> actualSeries;
|
|
|
+ private boolean editMode = false;
|
|
|
+
|
|
|
+ private enum EditPointType {
|
|
|
+ Normal, StartPoint, EndPoint
|
|
|
+ };
|
|
|
+
|
|
|
+ private Model model;
|
|
|
+ private int widthWithBorder, heightWithBorder;
|
|
|
+
|
|
|
+ private EditPointType editPointType;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Constructor.
|
|
|
+ *
|
|
|
+ * @param model the Model
|
|
|
+ * @param control the Controller
|
|
|
+ */
|
|
|
+ public UnitGraph(Control control) {
|
|
|
+ setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
|
|
|
+ this.model = control.getModel();
|
|
|
+ this.setBackground(Color.WHITE);
|
|
|
+
|
|
|
+ this.addMouseListener(this);
|
|
|
+ this.addMouseMotionListener(this);
|
|
|
+ this.addComponentListener(this);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * When the UnitGraph should represent a new GraphEditable Element. Its Updates
|
|
|
+ * the Graph and give access to the Element.
|
|
|
+ *
|
|
|
+ * @param element
|
|
|
+ */
|
|
|
+ public void addNewSeries(TimelineDependent element) {
|
|
|
+ Series series = new Series();
|
|
|
+ overrideUnitGraph(series, element.getStateGraph());
|
|
|
+ series.element = element;
|
|
|
+ series.type = element.getGraphType();
|
|
|
+ series.color = seriesColorArray[element.hashCode() % seriesColorArray.length];
|
|
|
+ seriesList.add(series);
|
|
|
+ repaint();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void clearSeries() {
|
|
|
+ seriesList.clear();
|
|
|
+ repaint();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Paints the Graph, the Grid, the actual Line from the currentIteration
|
|
|
+ *
|
|
|
+ * @param g Graphics
|
|
|
+ */
|
|
|
+ public void paintComponent(Graphics g) {
|
|
|
+ super.paintComponent(g);
|
|
|
+ Graphics2D g2D = (Graphics2D) g;
|
|
|
+ drawGrid(g2D);
|
|
|
+ g2D.setColor(Color.BLACK);
|
|
|
+ g2D.setRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
|
|
|
+ g2D.setStroke(new BasicStroke(2f));
|
|
|
+ drawUnitGraph(g2D);
|
|
|
+ g2D.setColor(dotColor);
|
|
|
+ if (editMode) {
|
|
|
+ drawUnitGraphPointsReleased(g2D);
|
|
|
+ } else {
|
|
|
+ drawUnitGraphPoints(g2D);
|
|
|
+ }
|
|
|
+ g2D.setColor(dotColor);
|
|
|
+ g2D.setStroke(new BasicStroke(1));
|
|
|
+ drawCurrentIterartionLine(g2D);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Draw Methods only to let the User see the changes. Nothing its saved here or
|
|
|
+ // changed.
|
|
|
+ /**
|
|
|
+ * Helper Method to draw the UnitGraphPanel.
|
|
|
+ * {@link UnitGraph#paintComponent(Graphics)}
|
|
|
+ * <p>
|
|
|
+ * This Methods draws the UnitGraph whether its a boolGraph or a doubleGraph.
|
|
|
+ *
|
|
|
+ * @param g to draw.
|
|
|
+ */
|
|
|
+ private void drawUnitGraph(Graphics2D g) {
|
|
|
+ boolean drawSnappingHintFlag = false;
|
|
|
+ for (Series series : this.seriesList) {
|
|
|
+ g.setColor(series.color);
|
|
|
+ switch (series.type) {
|
|
|
+ case boolGraph:
|
|
|
+ if (editMode) {
|
|
|
+ drawBoolGraphInEditMode(g, series);
|
|
|
+ drawSnappingHintFlag = true;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ drawBoolGraph(g, series);
|
|
|
+ break;
|
|
|
+ case doubleGraph:
|
|
|
+ if (editMode)
|
|
|
+ drawDoubleGraphInEditMode(g, series);
|
|
|
+ else
|
|
|
+ drawDoubleGraph(g, series);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ throw new UnsupportedOperationException();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if(drawSnappingHintFlag) {
|
|
|
+ drawSnappingHint(g);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Helper Method to draw the UnitGraphPanel.
|
|
|
+ * {@link UnitGraph#paintComponent(Graphics)}
|
|
|
+ * <p>
|
|
|
+ * This Methods draws the UnitGraphPoints of the UnitGraph.
|
|
|
+ *
|
|
|
+ * @param g to draw.
|
|
|
+ */
|
|
|
+ private void drawUnitGraphPoints(Graphics2D g) {
|
|
|
+ g.setColor(dotColor);
|
|
|
+ for (Series series : seriesList) {
|
|
|
+ for (UnitGraphPoint p : series.points) {
|
|
|
+ drawDot(g, p.displayedPosition);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Helper Method to draw the UnitGraphPanel.
|
|
|
+ * {@link UnitGraph#paintComponent(Graphics)}
|
|
|
+ * <p>
|
|
|
+ * This Methods draws the UnitGraphPoints of the UnitGraph when its in EditMode.
|
|
|
+ *
|
|
|
+ * @param g to draw.
|
|
|
+ */
|
|
|
+ private void drawUnitGraphPointsReleased(Graphics2D g) {
|
|
|
+ drawUnitGraphPoints(g);
|
|
|
+ g.setColor(editDotColor);
|
|
|
+ drawDot(g, editPosition);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Helper Method to draw the UnitGraphPanel.
|
|
|
+ * {@link UnitGraph#paintComponent(Graphics)}
|
|
|
+ * <p>
|
|
|
+ * This Methods draws the Grid on the Canvas.
|
|
|
+ *
|
|
|
+ * @param g2D to draw.
|
|
|
+ */
|
|
|
+ private void drawGrid(Graphics2D g2D) {
|
|
|
+ g2D.setStroke(new BasicStroke(1));
|
|
|
+ g2D.setColor(Color.lightGray);
|
|
|
+ int amountOfLines = 10;
|
|
|
+ int width = widthWithBorder + 2 * border;
|
|
|
+ int height = heightWithBorder;
|
|
|
+ for (int i = 0; i <= amountOfLines; i++) {
|
|
|
+ int linehieght = (int) (((double) i / (double) amountOfLines) * (double) height) + border;
|
|
|
+ g2D.drawLine(0, linehieght, width, linehieght);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Helper Method to draw the UnitGraphPanel.
|
|
|
+ * {@link UnitGraph#paintComponent(Graphics)}
|
|
|
+ * <p>
|
|
|
+ * This Method draws the CurrentIterationLine.
|
|
|
+ *
|
|
|
+ * @param g2D to draw.
|
|
|
+ */
|
|
|
+ private void drawCurrentIterartionLine(Graphics2D g) {
|
|
|
+ int cur = model.getCurrentIteration();
|
|
|
+ int max = model.getMaxIterations();
|
|
|
+ if(isLocalPeriedDifferentInSeries()){
|
|
|
+ for(Series series: seriesList) {
|
|
|
+ double where;
|
|
|
+ if (!series.element.isUsingLocalPeriod()) {
|
|
|
+ where = ((double) cur) / ((double) max);
|
|
|
+ } else {
|
|
|
+ int lPeriod = series.element.getLocalPeriod();
|
|
|
+ where = ((double) cur % lPeriod) / ((double) lPeriod);
|
|
|
+ }
|
|
|
+ Vector2Int oben = new Vector2Int(border + (int) (where * widthWithBorder), 0);
|
|
|
+ Vector2Int unten = new Vector2Int(border + (int) (where * widthWithBorder), 2 * border + heightWithBorder);
|
|
|
+ g.setColor(series.color);
|
|
|
+ drawLine(g, oben, unten);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ double where;
|
|
|
+ if (!isUsingLocalPeriod()) {
|
|
|
+ where = ((double) cur) / ((double) max);
|
|
|
+ } else {
|
|
|
+ int lPeriod = getFirstLocalPeriod();
|
|
|
+ where = ((double) cur % lPeriod) / ((double) lPeriod);
|
|
|
+ }
|
|
|
+ Vector2Int oben = new Vector2Int(border + (int) (where * widthWithBorder), 0);
|
|
|
+ Vector2Int unten = new Vector2Int(border + (int) (where * widthWithBorder), 2 * border + heightWithBorder);
|
|
|
+ g.setColor(dotColor);
|
|
|
+ drawLine(g, oben, unten);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Helper Method to draw the UnitGraphPanel.
|
|
|
+ * {@link UnitGraph#paintComponent(Graphics)}
|
|
|
+ * <p>
|
|
|
+ * This Method draws a line between two Positions on the Canvas.
|
|
|
+ *
|
|
|
+ * @param g2D to draw.
|
|
|
+ * @param start the Position of one end of the line to draw.
|
|
|
+ * @param end the other Ends Position of the Line to draw.
|
|
|
+ */
|
|
|
+ private void drawLine(Graphics2D g, Vector2Int start, Vector2Int end) {
|
|
|
+ Path2D.Double path = new Path2D.Double();
|
|
|
+ path.moveTo(start.getX(), start.getY());
|
|
|
+ path.lineTo(end.getX(), end.getY());
|
|
|
+ g.draw(path);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Helper Method to draw the UnitGraphPanel.
|
|
|
+ * {@link UnitGraph#paintComponent(Graphics)}
|
|
|
+ * <p>
|
|
|
+ * Initialize a Cubic BezierCurve.
|
|
|
+ *
|
|
|
+ * @param start The Position to start the Curve.
|
|
|
+ */
|
|
|
+ private Path2D.Double initBezier(Vector2Int start) {
|
|
|
+ // Good Source for basic understanding for Bezier Curves
|
|
|
+ // http://www.theappguruz.com/blog/bezier-curve-in-games
|
|
|
+ Path2D.Double path = new Path2D.Double();
|
|
|
+ path.moveTo(start.getX(), start.getY());
|
|
|
+ return path;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Helper Method to draw the UnitGraphPanel.
|
|
|
+ * {@link UnitGraph#paintComponent(Graphics)}
|
|
|
+ * <p>
|
|
|
+ * Calculate the Path of a the Cubic BezierCurve with the special controlPoints
|
|
|
+ * to make the wanted behavior.
|
|
|
+ *
|
|
|
+ * @param path the path of the Bezier.
|
|
|
+ * @param actaul the actual Position of the Path.
|
|
|
+ * @param target the end Position of the Curve.
|
|
|
+ */
|
|
|
+ private void curveTo(Path2D.Double path, Vector2Int actual, Vector2Int target) {
|
|
|
+ double mitte = (actual.getX() + target.getX()) * 0.5;
|
|
|
+ path.curveTo(mitte, actual.getY(), mitte, target.getY(), target.getX(), target.getY());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Helper Method to draw the UnitGraphPanel.
|
|
|
+ * {@link UnitGraph#paintComponent(Graphics)}
|
|
|
+ * <p>
|
|
|
+ * Draws a Dot at a Position.
|
|
|
+ *
|
|
|
+ * @param g to draw.
|
|
|
+ * @param p the position of the Dot.
|
|
|
+ */
|
|
|
+ private void drawDot(Graphics2D g, Vector2Int p) {
|
|
|
+ g.fillOval(p.getX() - dotSize / 2, p.getY() - dotSize / 2, dotSize, dotSize);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Helper Method to draw the UnitGraphPanel.
|
|
|
+ * {@link UnitGraph#paintComponent(Graphics)}
|
|
|
+ * <p>
|
|
|
+ * This Method draws the UnitGraph as BoolGraph.
|
|
|
+ *
|
|
|
+ * @param g2D to draw.
|
|
|
+ */
|
|
|
+ private void drawBoolGraph(Graphics2D g, Series series) {
|
|
|
+ if (series.points.size() <= 1)
|
|
|
+ return;
|
|
|
+ LinkedList<Vector2Int> cornerPoints = new LinkedList<Vector2Int>();
|
|
|
+ ListIterator<UnitGraphPoint> iter = series.points.listIterator();
|
|
|
+ Vector2Int actual = series.points.getFirst().displayedPosition;
|
|
|
+ Path2D.Double path = new Path2D.Double();
|
|
|
+ path.moveTo(actual.getX(), actual.getY());
|
|
|
+ while (iter.hasNext()) {
|
|
|
+ Vector2Int target = iter.next().displayedPosition;
|
|
|
+ // BooleanConnection
|
|
|
+ path.lineTo(target.getX(), actual.getY()); // line to corner
|
|
|
+ cornerPoints.add(new Vector2Int(target.getX(), actual.getY())); // save corner
|
|
|
+ path.lineTo(target.getX(), target.getY()); // line to next Point
|
|
|
+
|
|
|
+ actual = target;
|
|
|
+ }
|
|
|
+ g.draw(path);
|
|
|
+ // Draw the Points on the Corner that dont exist in Data but should be visual
|
|
|
+ g.setColor(dotColor);
|
|
|
+ for (Vector2Int p : cornerPoints) {
|
|
|
+ drawDot(g, p);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Helper Method to draw the UnitGraphPanel.
|
|
|
+ * {@link UnitGraph#paintComponent(Graphics)}
|
|
|
+ * <p>
|
|
|
+ * This Method draws the UnitGraph as BoolGraph in EditMode.
|
|
|
+ *
|
|
|
+ * @param g2D to draw.
|
|
|
+ */
|
|
|
+ private void drawBoolGraphInEditMode(Graphics2D g, Series series) {
|
|
|
+ LinkedList<Vector2Int> before = new LinkedList<Vector2Int>();
|
|
|
+ LinkedList<Vector2Int> after = new LinkedList<Vector2Int>();
|
|
|
+ for (UnitGraphPoint p : series.points) {
|
|
|
+ if (p.displayedPosition.getX() < editPosition.getX())
|
|
|
+ before.add(p.displayedPosition);
|
|
|
+ else
|
|
|
+ after.add(p.displayedPosition);
|
|
|
+ }
|
|
|
+ g.setColor(series.color);
|
|
|
+ drawBoolGraphFromList(g, before);
|
|
|
+ g.setColor(series.color);
|
|
|
+ drawBoolGraphFromList(g, after);
|
|
|
+ // EditGraph
|
|
|
+ LinkedList<Vector2Int> middle = new LinkedList<Vector2Int>();
|
|
|
+ if (!before.isEmpty())
|
|
|
+ middle.add(before.getLast());
|
|
|
+ middle.add(editPosition);
|
|
|
+ if (!after.isEmpty())
|
|
|
+ middle.add(after.getFirst());
|
|
|
+
|
|
|
+ g.setColor(editDotColor);
|
|
|
+ drawBoolGraphFromList(g, middle);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Helper Method to draw the UnitGraphPanel.
|
|
|
+ * {@link UnitGraph#paintComponent(Graphics)}
|
|
|
+ * <p>
|
|
|
+ * This Method draws a red Hint to signal the User the snapping of the hovered
|
|
|
+ * Point under the Cursor in EditMode.
|
|
|
+ *
|
|
|
+ * @param g2D to draw.
|
|
|
+ */
|
|
|
+ private void drawSnappingHint(Graphics2D g) {
|
|
|
+ // ColorHint
|
|
|
+ g.setColor(Color.RED);
|
|
|
+ // Threshhold Line
|
|
|
+ final float dash1[] = { 10.0f };
|
|
|
+ final BasicStroke dashed = new BasicStroke(2.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, dash1,
|
|
|
+ 0.0f);
|
|
|
+ g.setStroke(dashed);
|
|
|
+
|
|
|
+ int halfheight = border + heightWithBorder / 2;
|
|
|
+ g.drawLine(0, halfheight, widthWithBorder + 2 * border, halfheight);
|
|
|
+ // Threshhold Text
|
|
|
+ g.drawString("Snapping Threshold", 10, halfheight - 2);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Helper Method to draw the UnitGraphPanel.
|
|
|
+ * {@link UnitGraph#paintComponent(Graphics)}
|
|
|
+ * <p>
|
|
|
+ * This Method draws a partial Graph from a Position List as BoolGraph.
|
|
|
+ *
|
|
|
+ * @param g2D to draw.
|
|
|
+ * @param list the PositionList to draw a BoolGraph
|
|
|
+ */
|
|
|
+ private void drawBoolGraphFromList(Graphics2D g, LinkedList<Vector2Int> list) {
|
|
|
+ if (list.size() <= 1)
|
|
|
+ return;
|
|
|
+ ListIterator<Vector2Int> iter = list.listIterator();
|
|
|
+ LinkedList<Vector2Int> cornerPoints = new LinkedList<Vector2Int>();
|
|
|
+ Vector2Int actual = list.getFirst();
|
|
|
+ Path2D.Double path = new Path2D.Double();
|
|
|
+ path.moveTo(actual.getX(), actual.getY());
|
|
|
+ while (iter.hasNext()) {
|
|
|
+ Vector2Int target = iter.next();
|
|
|
+ // BooleanConnection
|
|
|
+ path.lineTo(target.getX(), actual.getY()); // line to corner
|
|
|
+ cornerPoints.add(new Vector2Int(target.getX(), actual.getY())); // save corner
|
|
|
+ path.lineTo(target.getX(), target.getY()); // line to next Point
|
|
|
+ actual = target;
|
|
|
+ }
|
|
|
+ g.draw(path);
|
|
|
+ g.setColor(dotColor);
|
|
|
+ for (Vector2Int p : cornerPoints) {
|
|
|
+ drawDot(g, p);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Helper Method to draw the UnitGraphPanel.
|
|
|
+ * {@link UnitGraph#paintComponent(Graphics)}
|
|
|
+ * <p>
|
|
|
+ * This Method draws the UnitGraph as DoubleGraph.
|
|
|
+ *
|
|
|
+ * @param g2D to draw.
|
|
|
+ */
|
|
|
+ private void drawDoubleGraph(Graphics2D g, Series series) {
|
|
|
+ if (series.points.isEmpty())
|
|
|
+ return;
|
|
|
+ ListIterator<UnitGraphPoint> iter = series.points.listIterator();
|
|
|
+ Vector2Int actual = iter.next().displayedPosition;
|
|
|
+ Path2D.Double path = this.initBezier(actual);
|
|
|
+ while (iter.hasNext()) {
|
|
|
+ Vector2Int target = iter.next().displayedPosition;
|
|
|
+ this.curveTo(path, actual, target);
|
|
|
+ actual = target;
|
|
|
+ }
|
|
|
+ g.draw(path);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Helper Method to draw the UnitGraphPanel.
|
|
|
+ * {@link UnitGraph#paintComponent(Graphics)}
|
|
|
+ * <p>
|
|
|
+ * This Method draws the UnitGraph as DoubleGraph in EditMode.
|
|
|
+ *
|
|
|
+ * @param g2D to draw.
|
|
|
+ */
|
|
|
+ private void drawDoubleGraphInEditMode(Graphics2D g, Series series) {
|
|
|
+ LinkedList<Vector2Int> before = new LinkedList<Vector2Int>();
|
|
|
+ LinkedList<Vector2Int> after = new LinkedList<Vector2Int>();
|
|
|
+ for (UnitGraphPoint p : series.points) {
|
|
|
+ if (p.displayedPosition.getX() < editPosition.getX())
|
|
|
+ before.add(p.displayedPosition);
|
|
|
+ else
|
|
|
+ after.add(p.displayedPosition);
|
|
|
+ }
|
|
|
+ drawUnitGraphFromList(g, before);
|
|
|
+ drawUnitGraphFromList(g, after);
|
|
|
+ // EditGraph
|
|
|
+ LinkedList<Vector2Int> middle = new LinkedList<Vector2Int>();
|
|
|
+ if (!before.isEmpty())
|
|
|
+ middle.add(before.getLast());
|
|
|
+ middle.add(editPosition);
|
|
|
+ if (!after.isEmpty())
|
|
|
+ middle.add(after.getFirst());
|
|
|
+
|
|
|
+ g.setColor(editDotColor);
|
|
|
+ drawUnitGraphFromList(g, middle);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Helper Method to draw the UnitGraphPanel.
|
|
|
+ * {@link UnitGraph#paintComponent(Graphics)}
|
|
|
+ * <p>
|
|
|
+ * This Method draws a partial Graph from a Position List as DoubleGraph.
|
|
|
+ *
|
|
|
+ * @param g2D to draw.
|
|
|
+ * @param list the PositionList to draw a DoubleGraph
|
|
|
+ */
|
|
|
+ private void drawUnitGraphFromList(Graphics2D g, LinkedList<Vector2Int> list) {
|
|
|
+ if (list.size() <= 1)
|
|
|
+ return;
|
|
|
+ ListIterator<Vector2Int> iter = list.listIterator();
|
|
|
+ Vector2Int actual = list.getFirst();
|
|
|
+ Path2D.Double path = this.initBezier(actual);
|
|
|
+ while (iter.hasNext()) {
|
|
|
+ Vector2Int target = iter.next();
|
|
|
+ curveTo(path, actual, target);
|
|
|
+ actual = target;
|
|
|
+ }
|
|
|
+ g.draw(path);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Under the hood functions to calculate and function the
|
|
|
+ /**
|
|
|
+ * A unitgraphpoint have a x and y position to store the data of a graph point.
|
|
|
+ * Also it have a displayposition to store the Position of the GraphPoints on
|
|
|
+ * the Canvas. e.g. when the canvas has 500 width and 200 height a GraphPoint
|
|
|
+ * with the X=0.5 and Y=1.0 should have a displayposition of (250,3) when border
|
|
|
+ * is 3.
|
|
|
+ */
|
|
|
+ private void updateRepresentativePositions() {
|
|
|
+ for (Series series : seriesList) {
|
|
|
+ for (UnitGraphPoint p : series.points) {
|
|
|
+ p.calcDisplayedPosition(border, widthWithBorder, heightWithBorder);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Takes a List of GraphPoints and convert it to the actual UnitGraphPoints with
|
|
|
+ * displayposition in the {@link #seriesList}
|
|
|
+ *
|
|
|
+ * @param stateCurve the list of GraphPoint
|
|
|
+ */
|
|
|
+ private void overrideUnitGraph(Series series, LinkedList<Point2D.Double> stateCurve) {
|
|
|
+ series.points.clear();
|
|
|
+ for (Point2D.Double p : stateCurve) {
|
|
|
+ UnitGraphPoint point = new UnitGraphPoint(p);
|
|
|
+ point.calcDisplayedPosition(border, widthWithBorder, heightWithBorder);
|
|
|
+ series.points.add(point);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * When the PanelSize Change the width and height to calculate the drawings have
|
|
|
+ * to be adjusted.
|
|
|
+ */
|
|
|
+ private void calculateWidthHeight() {
|
|
|
+ widthWithBorder = this.getWidth() - 2 * border;
|
|
|
+ heightWithBorder = this.getHeight() - 2 * border;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Save the actualGraphPoint List to the GraphEditable Element.
|
|
|
+ */
|
|
|
+ private void saveGraph() {
|
|
|
+ for (Series series : seriesList) {
|
|
|
+ LinkedList<Point2D.Double> actual = series.element.getStateGraph();
|
|
|
+ actual.clear();
|
|
|
+ for (UnitGraphPoint p : series.points) {
|
|
|
+ actual.add(p.getPoint());
|
|
|
+ }
|
|
|
+ series.element.sampleGraph();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Remove a UnitGraphPoint from the UnitGraphPoint list ({@link #seriesList}
|
|
|
+ * when its near a given Position.
|
|
|
+ *
|
|
|
+ * @param mPosition
|
|
|
+ */
|
|
|
+ private void removePointNearPosition(Series series, Vector2Int mPosition) {
|
|
|
+ ListIterator<UnitGraphPoint> iter = series.points.listIterator();
|
|
|
+ while (iter.hasNext()) {
|
|
|
+ if (near(mPosition, iter.next().displayedPosition, series.type)) {
|
|
|
+ iter.remove();
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private void removePointsNearPosition(Vector2Int mPosition) {
|
|
|
+ for (Series series : seriesList) {
|
|
|
+ removePointNearPosition(series, mPosition);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private Optional<Series> detectSeries(Vector2Int mPosition) {
|
|
|
+ return seriesList.stream().min((a, b) -> {
|
|
|
+ float minDistanceA = a.points.stream().map(point -> point.displayedPosition.getSquaredDistance(mPosition))
|
|
|
+ .min(Float::compare).get();
|
|
|
+ float minDistanceB = b.points.stream().map(point -> point.displayedPosition.getSquaredDistance(mPosition))
|
|
|
+ .min(Float::compare).get();
|
|
|
+ return Float.compare(minDistanceA, minDistanceB);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Determine if the Point is a StartPoint , EndPoint or a NormalPoint a.k.a. in
|
|
|
+ * between Points.
|
|
|
+ *
|
|
|
+ * @param mPosition The Position to check.
|
|
|
+ */
|
|
|
+ private EditPointType detectStartEndPoint(Series series, Vector2Int mPosition) {
|
|
|
+ UnitGraphPoint first = series.points.getFirst();
|
|
|
+ UnitGraphPoint last = series.points.getLast();
|
|
|
+ if (near(mPosition, first.displayedPosition, series.type))
|
|
|
+ return EditPointType.StartPoint;
|
|
|
+ else if (near(mPosition, last.displayedPosition, series.type))
|
|
|
+ return EditPointType.EndPoint;
|
|
|
+ else
|
|
|
+ return EditPointType.Normal;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Determine if a Point is near the Cursor (depends on Mode what near means). To
|
|
|
+ * detect if it should grab the Point or create a new Point.
|
|
|
+ *
|
|
|
+ * @param actual
|
|
|
+ * @param target
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private boolean near(Vector2Int actual, Vector2Int target, GraphType graphType) {
|
|
|
+ switch (graphType) {
|
|
|
+ case boolGraph: // Distance only with X
|
|
|
+ int xDis = target.getX() - actual.getX();
|
|
|
+ return xDis * xDis < clickThreshholdSquared;
|
|
|
+ case doubleGraph:
|
|
|
+ return actual.getSquaredDistance(target) < clickThreshholdSquared;
|
|
|
+ default:
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * When the Mouse Drag a Point it updates each time the position.
|
|
|
+ *
|
|
|
+ * @param newPosition
|
|
|
+ */
|
|
|
+ private void updateEditPointPosition(Vector2Int newPosition, EditPointType editPointType, GraphType graphType) {
|
|
|
+ // make it in the bounds of the UnitGraph no Point out of the Border
|
|
|
+ Vector2Int currentPosition = setInBounds(newPosition);
|
|
|
+ if (editPointType != EditPointType.Normal) {
|
|
|
+ attachToBorder(currentPosition, editPointType);
|
|
|
+ }
|
|
|
+ if (graphType == GraphType.boolGraph) {
|
|
|
+ snapBoolean(currentPosition);
|
|
|
+ }
|
|
|
+ editPosition = currentPosition;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * No Point on the UnitGraph should exit the UnitGraph.
|
|
|
+ *
|
|
|
+ * @param p the Position
|
|
|
+ * @return the updated Position.
|
|
|
+ */
|
|
|
+ private Vector2Int setInBounds(Vector2Int p) {
|
|
|
+ p.clampX(border, border + widthWithBorder);
|
|
|
+ p.clampY(border, border + heightWithBorder);
|
|
|
+ return p;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * For Switches the Point have to be Snap to the Top or the Bottem.
|
|
|
+ *
|
|
|
+ * @param p the Position
|
|
|
+ * @return the updated Position.
|
|
|
+ */
|
|
|
+ private Vector2Int snapBoolean(Vector2Int p) {
|
|
|
+ if (p.getY() < border + heightWithBorder / 2) {
|
|
|
+ p.setY(border);
|
|
|
+ } else {
|
|
|
+ p.setY(border + heightWithBorder);
|
|
|
+ }
|
|
|
+ return p;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The First Point has to be at 0(LeftSide) and Last Point has to be at
|
|
|
+ * 1(RightSide).
|
|
|
+ *
|
|
|
+ * @param p the Position
|
|
|
+ * @return the updated Position.
|
|
|
+ */
|
|
|
+ private Vector2Int attachToBorder(Vector2Int p, EditPointType editPointType) {
|
|
|
+ switch (editPointType) {
|
|
|
+ case StartPoint:
|
|
|
+ p.setX(border);
|
|
|
+ break;
|
|
|
+ case EndPoint:
|
|
|
+ p.setX(border + widthWithBorder);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ return p;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Insert a Position in the UnitGraphList at the right order. Its sorted based
|
|
|
+ * on the xValues.
|
|
|
+ *
|
|
|
+ * @param pos The new UnitGraphPoints Position
|
|
|
+ */
|
|
|
+ private void insertNewGraphPoint(Series series, Vector2Int pos) {
|
|
|
+ setInBounds(pos);
|
|
|
+ ListIterator<UnitGraphPoint> iter = series.points.listIterator();
|
|
|
+ while (iter.hasNext()) {
|
|
|
+ Vector2Int tempPosition = iter.next().displayedPosition;
|
|
|
+ if (pos.getX() <= tempPosition.getX()) {
|
|
|
+ // previous to go back a position to make the new point before the the Position
|
|
|
+ // with greater X
|
|
|
+ iter.previous();
|
|
|
+ iter.add(generateUnitGraphPoint(pos));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!iter.hasNext()) // if behind last point
|
|
|
+ {
|
|
|
+ iter.add(generateUnitGraphPoint(pos));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Generate a UnitGraphPoint from a normal Position in the UnitGraph.
|
|
|
+ *
|
|
|
+ * @param pos the normal pos with xValues from 0..Width and yValues from
|
|
|
+ * 0..Height
|
|
|
+ * @return a UnitGraphPoint
|
|
|
+ */
|
|
|
+ private UnitGraphPoint generateUnitGraphPoint(Vector2Int pos) {
|
|
|
+ UnitGraphPoint temp = new UnitGraphPoint((double) (pos.getX() - border) / (double) widthWithBorder,
|
|
|
+ 1 - (double) (pos.getY() - border) / (double) heightWithBorder, true);
|
|
|
+ temp.displayedPosition = pos;
|
|
|
+ return temp;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Update the Point Position
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void mouseDragged(MouseEvent e) {
|
|
|
+ actualSeries.ifPresent(series -> {
|
|
|
+ updateEditPointPosition(new Vector2Int(e.getPoint().x, e.getPoint().y), this.editPointType, series.type);
|
|
|
+ repaint();
|
|
|
+ });
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void mouseMoved(MouseEvent e) {
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void mouseClicked(MouseEvent e) {
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void mouseEntered(MouseEvent e) {
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void mouseExited(MouseEvent e) {
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The First Step. When LeftMouseButton its checks if a point is to grab under
|
|
|
+ * the cursor or create a new Point. Then enter EditMode. When RightMouseButton
|
|
|
+ * its delete a point if its under the Curser.
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void mousePressed(MouseEvent e) {
|
|
|
+ Vector2Int mPosition = new Vector2Int(e.getPoint().x, e.getPoint().y);
|
|
|
+ actualSeries = detectSeries(mPosition);
|
|
|
+ actualSeries.ifPresent(series -> {
|
|
|
+ if (e.getButton() == MouseEvent.BUTTON3) {
|
|
|
+ // RightMouseButtonEvent
|
|
|
+ editPointType = detectStartEndPoint(series, mPosition);
|
|
|
+ if (editPointType == EditPointType.Normal) {
|
|
|
+ removePointsNearPosition(mPosition);
|
|
|
+ repaint();
|
|
|
+ }
|
|
|
+ editMode = false;
|
|
|
+
|
|
|
+ } else if (e.getButton() == MouseEvent.BUTTON1) {
|
|
|
+ // LeftMouseButtonEvent
|
|
|
+ editPointType = detectStartEndPoint(series, mPosition);
|
|
|
+ removePointsNearPosition(mPosition);
|
|
|
+ updateEditPointPosition(mPosition, editPointType, series.type);
|
|
|
+ editMode = true;
|
|
|
+ repaint();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * The last step to save the Changes. Its insert the Hovering Point and exit
|
|
|
+ * EditMode.
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public void mouseReleased(MouseEvent e) {
|
|
|
+ if (editMode && actualSeries.isPresent()) {
|
|
|
+ for (Series series : seriesList) {
|
|
|
+ this.insertNewGraphPoint(series, editPosition);
|
|
|
+ }
|
|
|
+ editMode = false;
|
|
|
+ repaint();
|
|
|
+ }
|
|
|
+ saveGraph();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * When the Component is Resized.
|
|
|
+ *
|
|
|
+ * @param e ComponentEvent
|
|
|
+ */
|
|
|
+ public void componentResized(ComponentEvent e) {
|
|
|
+ calculateWidthHeight();
|
|
|
+ updateRepresentativePositions();
|
|
|
+ repaint();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void componentHidden(ComponentEvent e) {
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void componentMoved(ComponentEvent e) {
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void componentShown(ComponentEvent e) {
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Resets the graph to normal.
|
|
|
+ */
|
|
|
+ public void reset() {
|
|
|
+ for (Series series : seriesList) {
|
|
|
+ series.element.reset();
|
|
|
+ overrideUnitGraph(series, series.element.getStateGraph());
|
|
|
+ }
|
|
|
+ repaint();
|
|
|
+ }
|
|
|
+
|
|
|
+ // LocalMode access methods...
|
|
|
+ // To access a element from the GUI for the LocalMode
|
|
|
+ public void setUseLocalPeriod(boolean state) {
|
|
|
+ for (Series series : seriesList) {
|
|
|
+ series.element.setUseLocalPeriod(state);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setLocalPeriod(int localLength) {
|
|
|
+ for (Series series : seriesList) {
|
|
|
+ series.element.setLocalPeriod(localLength);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean isLocalPeriedDifferentInSeries() {
|
|
|
+ return seriesList.stream().map(series -> series.element.getLocalPeriod()).distinct().count() > 1;
|
|
|
+ }
|
|
|
+ public int getFirstLocalPeriod() {
|
|
|
+ int period = 0;
|
|
|
+ //seriesList.stream().findFirst().ifPresentOrElse(series -> return series.element.getLocalPeriod(), () -> period = LocalMode.STANDARD_GRAPH_ACCURACY);
|
|
|
+ return period;
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean isUsingLocalPeriod() {
|
|
|
+ return seriesList.stream().anyMatch(series -> series.element.isUsingLocalPeriod());
|
|
|
+ }
|
|
|
+
|
|
|
+}
|