diff --git a/notes.md b/notes.md index 3e09f046..2dd3f02f 100644 --- a/notes.md +++ b/notes.md @@ -1,3 +1,4 @@ +* Lines can be added directly from the Chart panel * Adding drag&drop in the tree of servers (in the Server List dialog) * Server connect/disconnect actions from the status bar * adding selection of auth.method into the status bar diff --git a/src/studio/ui/chart/ChartPanel.java b/src/studio/ui/chart/ChartPanel.java index ba79699e..03f80000 100644 --- a/src/studio/ui/chart/ChartPanel.java +++ b/src/studio/ui/chart/ChartPanel.java @@ -3,6 +3,7 @@ import org.jfree.chart.ChartRenderingInfo; import org.jfree.chart.JFreeChart; import org.jfree.chart.axis.ValueAxis; +import org.jfree.chart.event.AnnotationChangeEvent; import org.jfree.chart.plot.*; import org.jfree.chart.ui.RectangleEdge; import studio.ui.UserAction; @@ -14,7 +15,9 @@ import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; +import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; +import java.util.List; public class ChartPanel extends studio.ui.chart.patched.ChartPanel { @@ -28,6 +31,12 @@ public class ChartPanel extends studio.ui.chart.patched.ChartPanel { private final Action zoomInAction; private final Action zoomOutAction; + private Line newLine = null; + private Line selectedLine = null; + + private static final int LINE_SELECTION_SENSITIVITY = 100; + private static final int DRAG_LINE_MASK = java.awt.Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); + public ChartPanel(JFreeChart chart) { super(chart); CrosshairOverlay crosshairOverlay = new CrosshairOverlay(); @@ -104,6 +113,7 @@ public void actionPerformed(ActionEvent event) { @Override public void mouseMoved(MouseEvent e) { + checkLineSelection(e.getPoint()); super.mouseMoved(e); Rectangle2D dataArea = getScreenDataArea(); JFreeChart chart = getChart(); @@ -182,6 +192,8 @@ public void mouseDragged(MouseEvent e) { return; } + if (checkLineDrag(e)) return; + JFreeChart chart = getChart(); ChartRenderingInfo info = getChartRenderingInfo(); // handle panning if we have a start point @@ -276,4 +288,88 @@ else if (e.isPopupTrigger()) { } + @Override + public void mouseClicked(MouseEvent event) { + addLine(event); + super.mouseClicked(event); + } + + private boolean checkLineDrag(MouseEvent event) { + if (selectedLine == null) return false; + + + if ((event.getModifiers() & DRAG_LINE_MASK) == DRAG_LINE_MASK) { + selectedLine.dragTo(event.getPoint()); + } else { + selectedLine.moveTo(toPlot(event.getPoint())); + } + + chartChanged(new AnnotationChangeEvent(this, selectedLine)); + return true; + } + + private void checkLineSelection(Point p) { + XYPlot plot = getChart().getXYPlot(); + int selectedIndex = -1; + int newSelectedIndex = -1; + double shortestDist = Double.POSITIVE_INFINITY; + + List list = plot.getAnnotations(); + for(int index = 0; index refresh()); + plot.getRangeAxis().addChangeListener(e -> refresh()); + } + + public void addPoint(Point2D.Double p1) { + if (init) throw new IllegalStateException("The line is already initialized"); + this.p1 = p1; + init = true; + refresh(); + } + + private void refresh() { + if (!init) return; + XYPlot plot = chartPanel.getChart().getXYPlot(); + double xMin = plot.getDomainAxis().getLowerBound(); + double xMax = plot.getDomainAxis().getUpperBound(); + + double yMin = plot.getRangeAxis().getLowerBound(); + double yMax = plot.getRangeAxis().getUpperBound(); + + Line2D.Double line = new Line2D.Double(p0.x, p0.y, p1.x, p1.y); + double xLow = intersectHorizontal(line, yMin); + double xUp = intersectHorizontal(line, yMax); + double yLow = intersectVertical(line, xMin); + double yUp = intersectVertical(line, xMax); + + List points = new ArrayList<>(2); + if (within(xLow, xMin, xMax)) points.add(new Point2D.Double(xLow, yMin)); + if (within(xUp, xMin, xMax)) points.add(new Point2D.Double(xUp, yMax)); + if (within(yLow, yMin, yMax)) points.add(new Point2D.Double(xMin, yLow)); + if (within(yUp, yMin, yMax)) points.add(new Point2D.Double(xMax, yUp)); + + if (points.size() == 2) { + Point2D.Double p0 = points.get(0); + Point2D.Double p1 = points.get(1); + screenP0 = chartPanel.fromPlot(p0); + screenP1 = chartPanel.fromPlot(p1); + visible = true; + } else { + visible = false; + selected = false; + } + } + + private static double intersectVertical(Line2D.Double line, double x) { + return line.y1 + (x-line.x1)*(line.y2 - line.y1) / (line.x2 - line.x1); + } + + private static double intersectHorizontal(Line2D.Double line, double y) { + return line.x1 + (y-line.y1)*(line.x2 - line.x1) / (line.y2 - line.y1); + } + + private static boolean within(double d0, double d1, double d2) { + return (d0 >= d1) && (d0 <= d2); + } + + public double distanceSqr(int x, int y) { + if (!init || !visible) return Double.POSITIVE_INFINITY; + + double s2 = x*(screenP0.y-screenP1.y) + screenP0.x*(screenP1.y-y) + screenP1.x*(y-screenP0.y); + double l2 = screenDist(screenP0, screenP1); + return s2*s2/l2; + } + + public static double screenDist(Point p1, Point p2) { + return (p1.x-p2.x)*(p1.x-p2.x) + (p1.y-p2.y)*(p1.y-p2.y); + } + + public void moveTo(Point2D.Double p) { + if (!init) return; + double x0 = p.x - (p1.x - p0.x); + double y0 = p.y - (p1.y - p0.y); + p0 = new Point2D.Double(x0, y0); + p1 = p; + refresh(); + } + + public void dragTo(Point p) { + if (!init) return; + double d0 = screenDist(p, screenP0); + double d1 = screenDist(p, screenP1); + + if (d0