Skip to content

Commit

Permalink
adding lines to the ChartPanel
Browse files Browse the repository at this point in the history
  • Loading branch information
dzmipt committed Nov 16, 2024
1 parent 891c99f commit 0230456
Show file tree
Hide file tree
Showing 3 changed files with 247 additions and 0 deletions.
1 change: 1 addition & 0 deletions notes.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
96 changes: 96 additions & 0 deletions src/studio/ui/chart/ChartPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {

Expand All @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<list.size(); index++) {
Line line = (Line) list.get(index);
if (line.isSelected()) selectedIndex = index;
double d = line.distanceSqr(p.x, p.y);
if (d < LINE_SELECTION_SENSITIVITY && d < shortestDist) {
shortestDist = d;
newSelectedIndex = index;
}
}

Line line = null;
if (selectedIndex != -1) {
line = (Line)list.get(selectedIndex);
line.setSelected(false);
}
if (newSelectedIndex != -1) {
line = selectedLine = (Line) list.get(newSelectedIndex);
selectedLine.setSelected(true);
} else {
selectedLine = null;
}

if (selectedIndex != newSelectedIndex) {
chartChanged(new AnnotationChangeEvent(this, line));
}
}

private void addLine(MouseEvent e) {
XYPlot plot = getChart().getXYPlot();
Point2D.Double p = toPlot(e.getPoint());
if (newLine == null) {
newLine = new Line(this, p);
plot.addAnnotation(newLine);
} else {
newLine.addPoint(p);
chartChanged(new AnnotationChangeEvent(this, newLine));
newLine = null;
}
}

public Point fromPlot(Point2D.Double p) {
XYPlot plot = getChart().getXYPlot();
Rectangle2D dataArea = getScreenDataArea();
double x = plot.getDomainAxis().valueToJava2D(p.x, dataArea, plot.getDomainAxisEdge());
double y = plot.getRangeAxis().valueToJava2D(p.y, dataArea, plot.getRangeAxisEdge());
return new Point((int)x, (int)y);

}

public Point2D.Double toPlot(Point p) {
XYPlot plot = getChart().getXYPlot();
Rectangle2D dataArea = getScreenDataArea();
double x = plot.getDomainAxis().java2DToValue(p.x, dataArea, plot.getDomainAxisEdge());
double y = plot.getRangeAxis().java2DToValue(p.y, dataArea, plot.getRangeAxisEdge());
return new Point2D.Double(x, y);
}

}
150 changes: 150 additions & 0 deletions src/studio/ui/chart/Line.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package studio.ui.chart;

import org.jfree.chart.annotations.AbstractAnnotation;
import org.jfree.chart.annotations.XYAnnotation;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.PlotRenderingInfo;
import org.jfree.chart.plot.XYPlot;

import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;

public class Line extends AbstractAnnotation implements XYAnnotation {

private final ChartPanel chartPanel;
private Point2D.Double p0, p1;

private Point screenP0, screenP1;
private boolean selected = false;
private boolean visible = false;
private boolean init = false;

public Line(ChartPanel chartPanel, Point2D.Double p0) {
this.chartPanel = chartPanel;
this.p0 = p0;
XYPlot plot = chartPanel.getChart().getXYPlot();
plot.getDomainAxis().addChangeListener(e -> 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<Point2D.Double> 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<d1) {
screenP0 = p;
} else {
screenP1 = p;
}

p0 = chartPanel.toPlot(screenP0);
p1 = chartPanel.toPlot(screenP1);
refresh();
}

public void setSelected(boolean selected) {
this.selected = selected;
}

public boolean isSelected() {
return selected;
}

@Override
public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea, ValueAxis domainAxis, ValueAxis rangeAxis, int rendererIndex, PlotRenderingInfo info) {
if (!init) {
Point p = chartPanel.fromPlot(p0);
Shape shape = new Ellipse2D.Double(p.x-2, p.y-2, 4,4);

g2.setColor(Color.black);
g2.fill(shape);
return;
}

if (!visible) return;

g2.setColor(Color.black);
g2.setStroke(new BasicStroke(selected ? 3 :1));
g2.drawLine(screenP0.x, screenP0.y, screenP1.x, screenP1.y);

}
}

0 comments on commit 0230456

Please sign in to comment.