package holeg.ui.view.inspector; import holeg.interfaces.GraphEditable.GraphType; import holeg.interfaces.LocalMode; import holeg.interfaces.TimelineDependent; import holeg.model.HolonElement; import holeg.model.Model; import holeg.preferences.ColorPreference; import holeg.ui.controller.Control; import holeg.utility.listener.ResizeListener; import holeg.utility.math.Maths; import holeg.utility.math.vector.Vec2f; import holeg.utility.math.vector.Vec2i; 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.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Optional; import java.util.Set; import java.util.logging.Logger; import javax.swing.JPanel; /** * This Class represents a Graph where the User can model the behavior of elements and switches over * time. */ public class UnitGraph extends JPanel implements MouseListener, MouseMotionListener{ private static final Logger log = Logger.getLogger(UnitGraph.class.getName()); // Normal Settings private static final int border = 4; private static final int clickThresholdSquared = 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 final Control control; private final ArrayList seriesList = new ArrayList<>(); private Vec2i editPosition; private Series actualSeries = null; private GlobalCurve globalCurve = null; private boolean editMode = false; private Set elements; private int widthWithBorder, heightWithBorder; private EditPointType editPointType; /** * Constructor. * * @param control the Controller */ public UnitGraph(Control control) { setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR)); this.setBackground(Color.WHITE); this.control = control; this.addMouseListener(this); this.addMouseMotionListener(this); this.addComponentListener((ResizeListener) e -> { calculateWidthHeight(); updateRepresentativePositions(); repaint(); }); control.OnCanvasUpdate.addListener(this::repaint); } /** * When the UnitGraph should represent a new GraphEditable Element. Its Updates the Graph and give * access to the Element. * * @param element a series */ public void addNewSeries(TimelineDependent element) { Series series = new Series(); overrideUnitGraph(series, element.getStateGraph()); series.element = element; series.type = element.getGraphType(); series.color = ColorPreference.UnitGraph.SeriesColorArray[element.hashCode() % ColorPreference.UnitGraph.SeriesColorArray.length]; seriesList.add(series); repaint(); } /** * Removes all series from the graph. */ public void clearSeries() { seriesList.clear(); repaint(); } public void setGlobalCurve(Set elements) { if (elements == null || elements.isEmpty()) { this.globalCurve = null; return; } GlobalCurve curve = new GlobalCurve(); curve.maxEnergy = elements.stream().map(HolonElement::getEnergy).filter(energy -> energy > 0) .reduce(0.0f, Float::sum); curve.minEnergy = elements.stream().map(HolonElement::getEnergy).filter(energy -> energy < 0) .reduce(0.0f, Float::sum); Model model = control.getModel(); float[] sample = new float[model.getMaxIterations()]; // sample energy for (HolonElement element : elements) { for (int i = 0; i < model.getMaxIterations(); i++) { sample[i] += element.calculateExpectedEnergyAtTimeStep(i); } } // sample curve for (int i = 0; i < model.getMaxIterations(); i++) { curve.points.add(new UnitGraphPoint((double) i / (double) model.getMaxIterations(), Maths.invLerp(curve.minEnergy, curve.maxEnergy, sample[i]), false)); } // update displayPosition for (UnitGraphPoint p : curve.points) { p.calcDisplayedPosition(border, widthWithBorder, heightWithBorder); } double zeroLineYPos = Maths.invLerp(curve.minEnergy, curve.maxEnergy, 0.0); curve.zeroLinePoints.add(new UnitGraphPoint(0.0, zeroLineYPos, false)); curve.zeroLinePoints.add(new UnitGraphPoint(1.0, zeroLineYPos, false)); for (UnitGraphPoint p : curve.zeroLinePoints) { p.calcDisplayedPosition(border, widthWithBorder, heightWithBorder); } // set global curve this.globalCurve = curve; this.elements = elements; } private void updateGlobalCurve() { setGlobalCurve(this.elements); } /** * 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(ColorPreference.UnitGraph.DotColor); if (editMode) { drawUnitGraphPointsReleased(g2d); } else { drawUnitGraphPoints(g2d); } g2d.setColor(ColorPreference.UnitGraph.DotColor); g2d.setStroke(new BasicStroke(1)); drawCurrentIterationLine(g2d); g2d.setStroke( new BasicStroke(1, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_ROUND, 1.0f, new float[]{6}, 3)); Optional.ofNullable(this.globalCurve).ifPresent(curve -> { g2d.setColor(ColorPreference.UnitGraph.GlobalCurveColor); drawDoubleGraph(g2d, curve.points); g2d.setColor(ColorPreference.UnitGraph.ZeroLineColor); g2d.setStroke(new BasicStroke(1)); drawDoubleGraph(g2d, curve.zeroLinePoints); }); } /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* 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.points); } break; default: throw new UnsupportedOperationException(); } } if (drawSnappingHintFlag) { drawSnappingHint(g); } } /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* This Methods draws the UnitGraphPoints of the UnitGraph. * * @param g to draw. */ private void drawUnitGraphPoints(Graphics2D g) { g.setColor(ColorPreference.UnitGraph.DotColor); for (Series series : seriesList) { for (UnitGraphPoint p : series.points) { drawDot(g, p.displayedPosition); } } } /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* 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(ColorPreference.UnitGraph.EditDotColor); drawDot(g, editPosition); } // 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)} *

* This Methods draws the Grid on the Canvas. * * @param g to draw. */ private void drawGrid(Graphics2D g) { g.setStroke(new BasicStroke(1)); g.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; g.drawLine(0, linehieght, width, linehieght); } } /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* This Method draws the CurrentIterationLine. * * @param g to draw. */ private void drawCurrentIterationLine(Graphics2D g) { Model model = control.getModel(); int cur = model.getCurrentIteration(); int max = model.getMaxIterations(); if (isLocalPeriedDifferentInSeries()) { for (Series series : seriesList) { double where = switch (series.element.getPeriod().getType()) { case Local -> { int interval = series.element.getPeriod().getInterval(); yield ((double) cur % interval) / ((double) interval); } case Global -> ((double) cur) / ((double) max); }; Vec2i oben = new Vec2i(border + (int) (where * widthWithBorder), 0); Vec2i unten = new Vec2i(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); } Vec2i upPoint = new Vec2i(border + (int) (where * widthWithBorder), 0); Vec2i downPoint = new Vec2i(border + (int) (where * widthWithBorder), 2 * border + heightWithBorder); g.setColor(ColorPreference.UnitGraph.DotColor); drawLine(g, upPoint, downPoint); } } /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* This Method draws a line between two Positions on the Canvas. * * @param g 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, Vec2i start, Vec2i 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)} *

* Initialize a Cubic BezierCurve. * * @param start The Position to start the Curve. */ private Path2D.Double initBezier(Vec2i 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)} *

* 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 actual the actual Position of the Path. * @param target the end Position of the Curve. */ private void curveTo(Path2D.Double path, Vec2i actual, Vec2i 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)} *

* Draws a Dot at a Position. * * @param g to draw. * @param p the position of the Dot. */ private void drawDot(Graphics2D g, Vec2i p) { g.fillOval(p.getX() - dotSize / 2, p.getY() - dotSize / 2, dotSize, dotSize); } /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* This Method draws the UnitGraph as BoolGraph. * * @param g to draw. */ private void drawBoolGraph(Graphics2D g, Series series) { if (series.points.size() <= 1) { return; } LinkedList cornerPoints = new LinkedList<>(); ListIterator iter = series.points.listIterator(); Vec2i actual = series.points.getFirst().displayedPosition; Path2D.Double path = new Path2D.Double(); path.moveTo(actual.getX(), actual.getY()); while (iter.hasNext()) { Vec2i target = iter.next().displayedPosition; // BooleanConnection path.lineTo(target.getX(), actual.getY()); // line to corner cornerPoints.add(new Vec2i(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(ColorPreference.UnitGraph.DotColor); for (Vec2i p : cornerPoints) { drawDot(g, p); } } /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* This Method draws the UnitGraph as BoolGraph in EditMode. * * @param g to draw. */ private void drawBoolGraphInEditMode(Graphics2D g, Series series) { LinkedList before = new LinkedList<>(); LinkedList after = new LinkedList<>(); 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 middle = new LinkedList<>(); if (!before.isEmpty()) { middle.add(before.getLast()); } middle.add(editPosition); if (!after.isEmpty()) { middle.add(after.getFirst()); } g.setColor(ColorPreference.UnitGraph.EditDotColor); drawBoolGraphFromList(g, middle); } /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* This Method draws a red Hint to signal the User the snapping of the hovered Point under the * Cursor in EditMode. * * @param g 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)} *

* This Method draws a partial Graph from a Position List as BoolGraph. * * @param g to draw. * @param list the PositionList to draw a BoolGraph */ private void drawBoolGraphFromList(Graphics2D g, LinkedList list) { if (list.size() <= 1) { return; } ListIterator iter = list.listIterator(); LinkedList cornerPoints = new LinkedList<>(); Vec2i actual = list.getFirst(); Path2D.Double path = new Path2D.Double(); path.moveTo(actual.getX(), actual.getY()); while (iter.hasNext()) { Vec2i target = iter.next(); // BooleanConnection path.lineTo(target.getX(), actual.getY()); // line to corner cornerPoints.add(new Vec2i(target.getX(), actual.getY())); // save corner path.lineTo(target.getX(), target.getY()); // line to next Point actual = target; } g.draw(path); g.setColor(ColorPreference.UnitGraph.DotColor); for (Vec2i p : cornerPoints) { drawDot(g, p); } } /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* This Method draws the UnitGraph as DoubleGraph. * * @param g to draw. */ private void drawDoubleGraph(Graphics2D g, List points) { if (points.isEmpty()) { return; } ListIterator iter = points.listIterator(); Vec2i actual = iter.next().displayedPosition; Path2D.Double path = this.initBezier(actual); while (iter.hasNext()) { Vec2i target = iter.next().displayedPosition; this.curveTo(path, actual, target); actual = target; } g.draw(path); } /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* This Method draws the UnitGraph as DoubleGraph in EditMode. * * @param g to draw. */ private void drawDoubleGraphInEditMode(Graphics2D g, Series series) { LinkedList before = new LinkedList<>(); LinkedList after = new LinkedList<>(); 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 middle = new LinkedList<>(); if (!before.isEmpty()) { middle.add(before.getLast()); } middle.add(editPosition); if (!after.isEmpty()) { middle.add(after.getFirst()); } g.setColor(ColorPreference.UnitGraph.EditDotColor); drawUnitGraphFromList(g, middle); } /** * Helper Method to draw the UnitGraphPanel. {@link UnitGraph#paintComponent(Graphics)} *

* This Method draws a partial Graph from a Position List as DoubleGraph. * * @param g to draw. * @param list the PositionList to draw a DoubleGraph */ private void drawUnitGraphFromList(Graphics2D g, LinkedList list) { if (list.size() <= 1) { return; } ListIterator iter = list.listIterator(); Vec2i actual = list.getFirst(); Path2D.Double path = this.initBezier(actual); while (iter.hasNext()) { Vec2i target = iter.next(); curveTo(path, actual, target); actual = target; } g.draw(path); } /** * 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); } } Optional.ofNullable(this.globalCurve).ifPresent(curve -> { for (UnitGraphPoint p : curve.points) { p.calcDisplayedPosition(border, widthWithBorder, heightWithBorder); } for (UnitGraphPoint p : curve.zeroLinePoints) { 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 stateCurve) { series.points.clear(); for (Vec2f 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; } // Under the hood functions to calculate and function the /** * Save the actualGraphPoint List to the GraphEditable Element. */ private void saveGraph() { for (Series series : seriesList) { LinkedList 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 the position */ private void removePointNearPosition(Series series, Vec2i mPosition) { ListIterator iter = series.points.listIterator(); while (iter.hasNext()) { if (near(mPosition, iter.next().displayedPosition, series.type)) { iter.remove(); break; } } } private void removePointsNearPosition(Vec2i mPosition) { for (Series series : seriesList) { removePointNearPosition(series, mPosition); } } private Optional detectSeries(Vec2i 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, Vec2i 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. */ private boolean near(Vec2i actual, Vec2i target, GraphType graphType) { switch (graphType) { case boolGraph: // Distance only with X int xDis = target.getX() - actual.getX(); return xDis * xDis < clickThresholdSquared; case doubleGraph: return actual.getSquaredDistance(target) < clickThresholdSquared; default: return false; } } /** * When the Mouse Drag a Point it updates each time the position. */ private void updateEditPointPosition(Vec2i newPosition, EditPointType editPointType, GraphType graphType) { // make it in the bounds of the UnitGraph no Point out of the Border Vec2i 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 Vec2i setInBounds(Vec2i 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 */ private void snapBoolean(Vec2i p) { if (p.getY() < border + heightWithBorder / 2) { p.setY(border); } else { p.setY(border + heightWithBorder); } } /** * The First Point has to be at 0(LeftSide) and Last Point has to be at 1(RightSide). * * @param p the Position */ private void attachToBorder(Vec2i p, EditPointType editPointType) { switch (editPointType) { case StartPoint -> p.setX(border); case EndPoint -> p.setX(border + widthWithBorder); } } /** * 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, Vec2i pos) { setInBounds(pos); ListIterator iter = series.points.listIterator(); while (iter.hasNext()) { Vec2i 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(Vec2i 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) { Optional.ofNullable(this.actualSeries).ifPresent(series -> { updateEditPointPosition(new Vec2i(e.getPoint().x, e.getPoint().y), this.editPointType, series.type); updateGlobalCurve(); 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) { Vec2i mPosition = new Vec2i(e.getPoint().x, e.getPoint().y); detectSeries(mPosition).ifPresent(series -> { actualSeries = 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 != null) { for (Series series : seriesList) { this.insertNewGraphPoint(series, editPosition); } editMode = false; } saveGraph(); updateGlobalCurve(); repaint(); } /** * Resets the graph to normal. */ public void reset() { for (Series series : seriesList) { series.element.reset(); overrideUnitGraph(series, series.element.getStateGraph()); } repaint(); } /** * set the local period of all selected series elements. * @param period the period */ public void setPeriod(LocalMode.Period period) { for (Series series : seriesList) { series.element.setPeriod(new LocalMode.Period(period)); } } /** * Check if selected series are different from another. * @return true or false */ public boolean isLocalPeriedDifferentInSeries() { return seriesList.stream().map(series -> series.element.getPeriod().getInterval()).distinct() .count() > 1; } /** * Returns the local period of the first series * @return the first series local period */ public int getFirstLocalPeriod() { return seriesList.isEmpty() ? 0 : seriesList.get(0).element.getPeriod().getInterval(); } /** * Check if using the local period of the series. * @return true or false */ public boolean isUsingLocalPeriod() { return seriesList.stream().anyMatch( series -> series.element.getPeriod().getType() != LocalMode.Period.PeriodType.Global); } private enum EditPointType { Normal, StartPoint, EndPoint } // Intern Variables private static class Series { public LinkedList points = new LinkedList<>(); public TimelineDependent element; public GraphType type; public Color color; } private static class GlobalCurve { public LinkedList points = new LinkedList<>(); public float minEnergy; public float maxEnergy; public LinkedList zeroLinePoints = new LinkedList<>(); } }