package org.esa.snap.rcp.statistics;
import org.jfree.chart.ChartMouseEvent;
import org.jfree.chart.ChartMouseListener;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.panel.AbstractOverlay;
import org.jfree.chart.panel.Overlay;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.data.xy.XYDataset;
import org.jfree.ui.RectangleEdge;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
/**
* @author Norman Fomferra
*/
public class XYPlotMarker implements ChartMouseListener {
private final ChartPanel chartPanel;
private final Listener listener;
private XYDataset xyDataset;
private int seriesIndex;
private ShapeOverlay overlay;
private Paint fillPaint;
private Paint outlinePaint;
private Stroke outlineStroke;
private double markerSize;
public XYPlotMarker(ChartPanel chartPanel, Listener listener) {
this.chartPanel = chartPanel;
this.listener = listener;
fillPaint = new Color(255, 255, 255, 127);
outlinePaint = new Color(50, 50, 50, 200);
outlineStroke = new BasicStroke(1.5F);
markerSize = 20;
}
public double getMarkerSize() {
return markerSize;
}
public void setMarkerSize(double markerSize) {
this.markerSize = markerSize;
}
public Paint getFillPaint() {
return fillPaint;
}
public void setFillPaint(Paint fillPaint) {
this.fillPaint = fillPaint;
}
public Paint getOutlinePaint() {
return outlinePaint;
}
public void setOutlinePaint(Paint outlinePaint) {
this.outlinePaint = outlinePaint;
}
public void setInvisible() {
removeDataset();
removeOverlay();
}
private void removeDataset() {
xyDataset = null;
seriesIndex = -1;
}
@Override
public void chartMouseClicked(ChartMouseEvent event) {
removeDataset();
final boolean overlayRemoved = removeOverlay();
if (overlayRemoved) {
listener.pointDeselected();
return;
}
XYPlot plot = chartPanel.getChart().getXYPlot();
Rectangle2D dataArea = chartPanel.getScreenDataArea();
Point point = event.getTrigger().getPoint();
if (dataArea.contains(point)) {
PlotOrientation orientation = plot.getOrientation();
RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(
plot.getDomainAxisLocation(), orientation);
RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(
plot.getRangeAxisLocation(), orientation);
double mx = plot.getDomainAxis().java2DToValue(point.x, dataArea, domainEdge);
double my = plot.getRangeAxis().java2DToValue(point.y, dataArea, rangeEdge);
int datasetCount = chartPanel.getChart().getXYPlot().getDatasetCount();
double minDist = Double.POSITIVE_INFINITY;
for (int datasetIndex = 0; datasetIndex < datasetCount; datasetIndex++) {
XYDataset xyDataset = chartPanel.getChart().getXYPlot().getDataset(datasetIndex);
if (xyDataset != null) {
int seriesCount = xyDataset.getSeriesCount();
for (int seriesIndex = 0; seriesIndex < seriesCount; seriesIndex++) {
int itemCount = xyDataset.getItemCount(seriesIndex);
for (int itemIndex = 0; itemIndex < itemCount; itemIndex++) {
double x = xyDataset.getXValue(seriesIndex, itemIndex);
double y = xyDataset.getYValue(seriesIndex, itemIndex);
double dist = (x - mx) * (x - mx) + (y - my) * (y - my);
if (dist < minDist) {
minDist = dist;
this.xyDataset = xyDataset;
this.seriesIndex = seriesIndex;
}
}
}
}
}
}
if (xyDataset != null) {
updatePoint(event);
}
}
@Override
public void chartMouseMoved(ChartMouseEvent event) {
updatePoint(event);
}
public interface Listener {
void pointSelected(XYDataset xyDataset, int seriesIndex, Point2D dataPoint);
void pointDeselected();
}
private void updatePoint(ChartMouseEvent event) {
if (xyDataset == null
|| xyDataset.getSeriesCount() == 0
// This situation appears, if the dataset has changed while the overlay is still visible
|| (seriesIndex < 0 || seriesIndex >= xyDataset.getSeriesCount())) {
if (removeOverlay()) {
// FIXME - exception here:
// listener.pointDeselected();
/*
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at ProductSceneView.getRaster(ProductSceneView.java:511)
at ProductSceneView.getProduct(ProductSceneView.java:468)
at org.esa.snap.visat.toolviews.nav.CursorSynchronizer.removePPL(CursorSynchronizer.java:133)
at org.esa.snap.visat.toolviews.nav.CursorSynchronizer.clearPsvOverlayMap(CursorSynchronizer.java:116)
at org.esa.snap.visat.toolviews.nav.CursorSynchronizer.setEnabled(CursorSynchronizer.java:67)
at org.esa.snap.visat.toolviews.stat.ProfilePlotPanel$3.pointDeselected(ProfilePlotPanel.java:211)
*/
}
return;
}
addOverlay();
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 xView = event.getTrigger().getPoint().x;
double xData = plot.getDomainAxis().java2DToValue(xView, dataArea, domainEdge);
int itemCount = xyDataset.getItemCount(seriesIndex);
for (int i = 0; i < itemCount - 1; i++) {
double x1 = xyDataset.getXValue(seriesIndex, i);
double x2 = xyDataset.getXValue(seriesIndex, i + 1);
if (xData >= x1 && xData <= x2) {
double y1 = xyDataset.getYValue(seriesIndex, i);
double y2 = xyDataset.getYValue(seriesIndex, i + 1);
double yData = y1 + (xData - x1) * (y2 - y1) / (x2 - x1);
double yView = plot.getRangeAxis().valueToJava2D(yData, dataArea, rangeEdge);
Point2D.Double viewPoint = new Point2D.Double(xView, yView);
Point2D.Double dataPoint = new Point2D.Double(xData, yData);
overlay.setPoint(viewPoint, dataPoint);
listener.pointSelected(xyDataset, seriesIndex, dataPoint);
break;
}
}
}
private boolean addOverlay() {
if (overlay == null) {
overlay = new ShapeOverlay();
chartPanel.addOverlay(overlay);
return true;
}
return false;
}
private boolean removeOverlay() {
if (overlay != null) {
chartPanel.removeOverlay(overlay);
overlay = null;
return true;
}
return false;
}
private class ShapeOverlay extends AbstractOverlay implements Overlay {
Point2D viewPoint;
Point2D dataPoint;
public void setPoint(Point2D viewPoint, Point2D dataPoint) {
if (this.viewPoint == null || !this.viewPoint.equals(dataPoint)) {
this.viewPoint = viewPoint;
this.dataPoint = dataPoint;
fireOverlayChanged();
}
}
@Override
public void paintOverlay(Graphics2D g2, ChartPanel chartPanel) {
if (viewPoint != null) {
Shape oldClip = g2.getClip();
Rectangle2D dataArea = chartPanel.getScreenDataArea();
g2.setClip(dataArea);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Ellipse2D.Double ellipse = new Ellipse2D.Double(viewPoint.getX() - 0.5 * markerSize,
viewPoint.getY() - 0.5 * markerSize,
markerSize,
markerSize);
g2.setPaint(fillPaint);
g2.fill(ellipse);
g2.setStroke(outlineStroke);
g2.setPaint(outlinePaint);
g2.draw(ellipse);
Rectangle2D box = new Rectangle2D.Double(dataArea.getX() + 5, dataArea.getY() + 5, 100, 52);
g2.setPaint(fillPaint);
g2.fill(box);
g2.setStroke(outlineStroke);
g2.setPaint(outlinePaint);
g2.draw(box);
g2.drawString(String.format("x = %.3f", dataPoint.getX()), (int) (dataArea.getX() + 5 + 5), (int) (dataArea.getY() + 5 + 20));
g2.drawString(String.format("y = %.3f", dataPoint.getY()), (int) (dataArea.getX() + 5 + 5), (int) (dataArea.getY() + 5 + 40));
g2.setClip(oldClip);
}
}
}
}