package org.esa.snap.rcp.statistics;
import com.bc.ceres.core.Assert;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.annotations.XYShapeAnnotation;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.ui.RectangleEdge;
import java.awt.Color;
import java.awt.Paint;
import java.awt.Point;
import java.awt.Shape;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import static java.lang.Math.abs;
import static java.lang.Math.min;
/**
* @author Norman Fomferra
*/
public class PlotAreaSelectionTool extends MouseAdapter {
public enum AreaType {
/**
* Shape is an instance of {@link java.awt.geom.Rectangle2D}, only X-coordinates are valid.
*/
X_RANGE,
/**
* Shape is an instance of {@link java.awt.geom.Rectangle2D}, only Y-coordinates are valid.
*/
Y_RANGE,
/**
* Shape is an instance of {@link java.awt.geom.Rectangle2D}.
*/
RECTANGLE,
/**
* Shape is an instance of {@link java.awt.geom.Ellipse2D}.
*/
ELLIPSE,
}
public interface Action {
void areaSelected(AreaType areaType, Shape shape);
}
private final ChartPanel chartPanel;
private final Action action;
private Point point1;
private Point point2;
private SelectedArea selectedArea;
private double triggerDistance;
private Color fillPaint;
private AreaType areaType;
public PlotAreaSelectionTool(ChartPanel chartPanel, Action action) {
this.chartPanel = chartPanel;
this.action = action;
triggerDistance = 4;
fillPaint = new Color(0, 0, 255, 50);
areaType = AreaType.ELLIPSE;
}
public void install() {
chartPanel.addMouseListener(this);
chartPanel.addMouseMotionListener(this);
chartPanel.setMouseZoomable(false);
}
public void uninstall() {
chartPanel.removeMouseListener(this);
chartPanel.removeMouseMotionListener(this);
chartPanel.setMouseZoomable(true);
}
public AreaType getAreaType() {
return areaType;
}
public void setAreaType(AreaType areaType) {
Assert.notNull(areaType, "areaType");
this.areaType = areaType;
}
public double getTriggerDistance() {
return triggerDistance;
}
public void setTriggerDistance(double triggerDistance) {
this.triggerDistance = triggerDistance;
}
public Color getFillPaint() {
return fillPaint;
}
public void setFillPaint(Color fillPaint) {
Assert.notNull(fillPaint, "fillPaint");
this.fillPaint = fillPaint;
}
@Override
public void mousePressed(MouseEvent event) {
if (!isButton1(event)) {
return;
}
point1 = event.getPoint();
point2 = null;
}
@Override
public void mouseReleased(MouseEvent event) {
if (!isButton1(event)) {
return;
}
if (selectedArea == null) {
return;
}
// Make sure, action is only triggered if a new area has been selected
if (point1 == null || point2 == null) {
return;
}
action.areaSelected(areaType, selectedArea.getShape());
// Ready for a new area to be selected, but the existing area remains visible
point1 = null;
point2 = null;
}
@Override
public void mouseDragged(MouseEvent event) {
if (point1 == null) {
return;
}
if (point2 == null) {
// first drag event after mousePressed -->
// then we must check against triggerDistance
Point p2 = event.getPoint();
if (Point.distanceSq(point1.getX(), point1.getY(), p2.getX(), p2.getY()) >= triggerDistance * triggerDistance) {
point2 = p2;
updateAnnotation();
}
} else {
// already dragging, just update
point2 = event.getPoint();
updateAnnotation();
}
}
private void updateAnnotation() {
removeAnnotation();
addAnnotation();
}
private void addAnnotation() {
selectedArea = new SelectedArea(createShape(), fillPaint);
chartPanel.getChart().getXYPlot().addAnnotation(selectedArea);
}
public void removeAnnotation() {
if (selectedArea != null) {
chartPanel.getChart().getXYPlot().removeAnnotation(selectedArea);
selectedArea = null;
}
}
private boolean isButton1(MouseEvent event) {
return event.getButton() == MouseEvent.BUTTON1;
}
private Shape createShape() {
XYPlot plot = chartPanel.getChart().getXYPlot();
Rectangle2D dataArea = chartPanel.getScreenDataArea();
PlotOrientation orientation = plot.getOrientation();
RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(plot.getDomainAxisLocation(), orientation);
RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(plot.getRangeAxisLocation(), orientation);
double vx1 = areaType == AreaType.Y_RANGE ? dataArea.getX() : point1.x;
double vy1 = areaType == AreaType.X_RANGE ? dataArea.getY() : point1.y;
double vx2 = areaType == AreaType.Y_RANGE ? dataArea.getX() + dataArea.getWidth() : point2.x;
double vy2 = areaType == AreaType.X_RANGE ? dataArea.getY() + dataArea.getHeight() : point2.y;
double x1 = plot.getDomainAxis().java2DToValue(vx1, dataArea, domainEdge);
double x2 = plot.getDomainAxis().java2DToValue(vx2, dataArea, domainEdge);
double y1 = plot.getRangeAxis().java2DToValue(vy1, dataArea, rangeEdge);
double y2 = plot.getRangeAxis().java2DToValue(vy2, dataArea, rangeEdge);
double dx = abs(x2 - x1);
double dy = abs(y2 - y1);
final Shape shape;
if (areaType == AreaType.ELLIPSE) {
shape = new Ellipse2D.Double(x1 - dx, y1 - dy, 2.0 * dx, 2.0 * dy);
} else if (areaType == AreaType.RECTANGLE) {
shape = new Rectangle2D.Double(x1 - dx, y1 - dy, 2.0 * dx, 2.0 * dy);
} else if (areaType == AreaType.X_RANGE || areaType == AreaType.Y_RANGE) {
shape = new Rectangle2D.Double(min(x1, x2), min(y1, y2), dx, dy);
} else {
throw new IllegalStateException("areaType = " + areaType);
}
return shape;
}
private static class SelectedArea extends XYShapeAnnotation {
private final Shape shape;
private SelectedArea(Shape shape, Paint fillPaint) {
super(shape, null, null, fillPaint);
this.shape = shape;
}
// Base class does not off this method :-(
public Shape getShape() {
return shape;
}
}
}