/*
* Plot.java
*
* Copyright (c) 2002-2015 Alexei Drummond, Andrew Rambaut and Marc Suchard
*
* This file is part of BEAST.
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership and licensing.
*
* BEAST is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* BEAST is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with BEAST; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301 USA
*/
package dr.app.gui.chart;
import dr.stats.Variate;
import java.awt.*;
import java.awt.geom.*;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Plot.java
* <p/>
* Description: Provides an interface for plots. Plots are elements of a
* chart that renders some data in a particular style. Multiple plots can
* be added to the same chart to get complex results.
*
* @author Andrew Rambaut
* @author Alexei Drummond
* @version $Id: Plot.java,v 1.23 2006/01/03 10:26:20 rambaut Exp $
*/
public interface Plot {
// These constants are used for automatic scaling to select exactly
// where the axis starts and stops.
static public final int NO_MARK = 0;
static public final int POINT_MARK = 1;
static public final int CROSS_MARK = 2;
static public final int PLUS_MARK = 3;
static public final int CIRCLE_MARK = 4;
static public final int SQUARE_MARK = 5;
static public final int DIAMOND_MARK = 6;
/**
* Set axes
*/
void setAxes(Axis xAxis, Axis yAxis);
/**
* Resets axis ranges (if new data has been added)
*/
void resetAxes();
/**
* Set data
*/
void setData(List<Double> xData, List<Double> yData);
/**
* Set data
*/
void setData(Variate.N xData, Variate.N yData);
/**
* Set line style
*/
void setLineStyle(Stroke lineStroke, Paint linePaint);
/**
* Set line stroke
*/
void setLineStroke(Stroke lineStroke);
/**
* Set line color
*/
void setLineColor(Paint linePaint);
/**
* Get line color
*/
Paint getLineColor();
/**
* Get line stroke
*/
Stroke getLineStroke();
/**
* Set mark style
*/
void setMarkStyle(int markType, double markSize, Stroke markStroke,
Paint markPaint, Paint markFillPaint);
/**
* Set mark style
*/
void setMarkStyle(Shape mark, Stroke markStroke,
Paint markPaint, Paint markFillPaint);
/**
* Paint actual plot
*/
void paintPlot(Graphics2D g2, double xScale, double yScale,
double xOffset, double yOffset);
/**
* Set name
*/
void setName(String name);
/**
* Get name
*/
String getName();
/**
* A point on the plot has been clicked
*/
void pointClicked(Point2D point, boolean isShiftDown);
void selectPoint(int index, boolean addToSelection);
void selectPoints(Rectangle2D dragRectangle, boolean addToSelection);
void clearSelection();
void setSelectedPoints(final Collection<Integer> selectedPoints);
Set<Integer> getSelectedPoints();
Variate getXData();
Variate getYData();
public interface Listener {
void pointClicked(double x, double y, boolean isShiftDown);
void markClicked(int index, double x, double y, boolean isShiftDown);
void selectionChanged(Set<Integer> selectedPoints);
void rangeXSelected(double lower, double upper);
void rangeYSelected(double lower, double upper);
void rangeXYSelected(double lowerX, double lowerY, double upperX, double upperY);
}
public class Adaptor implements Listener {
public void pointClicked(double x, double y, boolean isShiftDown) {
}
public void markClicked(int index, double x, double y, boolean isShiftDown) {
}
public void selectionChanged(final Set<Integer> selectedPoints) {
}
public void rangeXSelected(double lower, double upper) {
}
public void rangeYSelected(double lower, double upper) {
}
public void rangeXYSelected(double lowerX, double lowerY, double upperX, double upperY) {
}
}
/**
* AbstractPlot.java
* <p/>
* Description: An abstract base class for plots
*/
public abstract class AbstractPlot implements Plot {
protected Axis xAxis, yAxis;
protected Variate.N xData = null;
protected Variate.N yData = null;
protected List<Color> colours = null;
protected Shape mark;
protected Stroke lineStroke = new BasicStroke(1.5f);
protected Paint linePaint = Color.black;
protected Stroke markStroke = new BasicStroke(0.5f);
protected Paint markPaint = Color.black;
protected Paint markFillPaint = Color.black;
private final Rectangle2D bounds = null;
protected double xScale, yScale, xOffset, yOffset;
private String name;
private Set<Integer> selectedPoints = new HashSet<Integer>();
/**
* Constructor
*/
public AbstractPlot() {
}
/**
* Constructor
*/
public AbstractPlot(Variate.N xData, Variate.N yData) {
setData(xData, yData);
}
/**
* Constructor
*/
public AbstractPlot(List<Double> xData, List<Double> yData) {
setData(xData, yData);
}
/**
* Set data
*/
public void setData(List<Double> xData, List<Double> yData) {
Variate.D xd = new Variate.D(xData);
Variate.D yd = new Variate.D(yData);
this.xData = xd;
this.yData = yd;
}
/**
* Set data
*/
public void setData(Variate.N xData, Variate.N yData) {
this.xData = xData;
this.yData = yData;
}
/**
* Set data
*/
public void setColours(List<Color> colours) {
this.colours = colours;
}
/**
* Set axes
*/
public void setAxes(Axis xAxis, Axis yAxis) {
this.xAxis = xAxis;
this.yAxis = yAxis;
setupAxis(xAxis, yAxis, xData, yData);
}
/**
* Resets axis ranges (if new data has been added)
*/
public void resetAxes() {
setupAxis(xAxis, yAxis, xData, yData);
}
/**
* Set up the axis with some data
*/
public void setupAxis(Axis xAxis, Axis yAxis, Variate xData, Variate yData) {
if (xData != null) {
if (xAxis instanceof LogAxis) {
double minValue = java.lang.Double.POSITIVE_INFINITY;
for (int i = 0; i < xData.getCount(); i++) {
double value = (Double) xData.get(i);
if (value > 0.0 && value < minValue)
minValue = value;
}
xAxis.addRange(minValue, (Double) xData.getMax());
} else {
xAxis.addRange((Double) xData.getMin(), (Double) xData.getMax());
}
}
if (yData != null) {
if (yAxis instanceof LogAxis) {
double minValue = java.lang.Double.POSITIVE_INFINITY;
for (int i = 0; i < yData.getCount(); i++) {
double value = (Double) yData.get(i);
if (value > 0.0 && value < minValue)
minValue = value;
}
yAxis.addRange(minValue, (Double) yData.getMax());
} else {
yAxis.addRange((Double) yData.getMin(), (Double) yData.getMax());
}
}
}
/**
* Set line style
*/
public void setLineStyle(Stroke lineStroke, Paint linePaint) {
this.lineStroke = lineStroke;
this.linePaint = linePaint;
}
/**
* Set line stroke
*/
public void setLineStroke(Stroke lineStroke) {
this.lineStroke = lineStroke;
}
/**
* Set line color
*/
public void setLineColor(Paint linePaint) {
this.linePaint = linePaint;
}
public final Paint getLineColor() {
return linePaint;
}
public final Stroke getLineStroke() {
return lineStroke;
}
public final void setName(String name) {
this.name = name;
}
public final String getName() {
return name;
}
/**
* Set mark style
*/
public void setMarkStyle(int markType, double markSize, Stroke markStroke,
Paint markPaint, Paint markFillPaint) {
float w = (float) (markSize / 2.0);
GeneralPath path;
switch (markType) {
case POINT_MARK:
path = new GeneralPath();
path.moveTo(0, 0);
path.lineTo(0, 0);
setMarkStyle(path, markStroke, markPaint, markFillPaint);
break;
case CROSS_MARK:
path = new GeneralPath();
path.moveTo(-w, -w);
path.lineTo(w, w);
path.moveTo(w, -w);
path.lineTo(-w, w);
setMarkStyle(path, markStroke, markPaint, markFillPaint);
break;
case PLUS_MARK:
path = new GeneralPath();
path.moveTo(-w, 0);
path.lineTo(w, 0);
path.moveTo(0, -w);
path.lineTo(0, w);
setMarkStyle(path, markStroke, markPaint, markFillPaint);
break;
case CIRCLE_MARK:
setMarkStyle(new Ellipse2D.Double(0.0, 0.0, markSize, markSize), markStroke, markPaint, markFillPaint);
break;
case SQUARE_MARK:
setMarkStyle(new Rectangle2D.Double(-w, -w, markSize, markSize), markStroke, markPaint, markFillPaint);
break;
case DIAMOND_MARK:
path = new GeneralPath();
path.moveTo(0, -w);
path.lineTo(w, 0);
path.lineTo(0, w);
path.lineTo(-w, 0);
path.closePath();
setMarkStyle(path, markStroke, markPaint, markFillPaint);
break;
}
}
/**
* Set mark style
*/
public void setMarkStyle(Shape mark, Stroke markStroke,
Paint markPaint, Paint markFillPaint) {
this.mark = mark;
this.markStroke = markStroke;
this.markPaint = markPaint;
this.markFillPaint = markFillPaint;
}
public Variate getXData() {
return xData;
}
public Variate getYData() {
return yData;
}
/**
* Transform a chart co-ordinates into a drawing co-ordinates
*/
protected double transformX(double value) {
double tx = xAxis.transform(value);
if (tx == Double.NaN || tx == Double.NEGATIVE_INFINITY) {
return Double.NEGATIVE_INFINITY;
}
return ((tx - xAxis.transform(xAxis.getMinAxis())) * xScale) + xOffset;
}
/**
* Transform a chart co-ordinates into a drawing co-ordinates
*/
protected double transformY(double value) {
double ty = yAxis.transform(value);
if (ty == Double.NaN || ty == Double.NEGATIVE_INFINITY) {
return Double.NEGATIVE_INFINITY;
}
return ((ty - yAxis.transform(yAxis.getMinAxis())) * yScale) + yOffset;
}
/**
* Transform a drawing co-ordinate into a chart co-ordinate
*/
protected double untransformX(double value) {
return xAxis.untransform(
xAxis.transform(xAxis.getMinAxis()) + ((value - xOffset) / xScale));
}
/**
* Transform a drawing co-ordinate into a chart co-ordinate
*/
protected double untransformY(double value) {
return yAxis.untransform(
yAxis.transform(yAxis.getMinAxis()) + ((value - yOffset) / yScale));
}
/**
* Draw a line transforming co-ordinates to each axis
*/
protected void drawLine(Graphics2D g2, double x1, double y1, double x2, double y2) {
Line2D line = new Line2D.Double(transformX(x1), transformY(y1),
transformX(x2), transformY(y2));
g2.draw(line);
}
/**
* Draw a rectangle transforming co-ordinates to each axis
*/
protected void drawRect(Graphics2D g2, double x1, double y1, double x2, double y2) {
float tx1 = (float) transformX(x1);
float ty1 = (float) transformY(y1);
float tx2 = (float) transformX(x2);
float ty2 = (float) transformY(y2);
GeneralPath path = new GeneralPath();
path.moveTo(tx1, ty1);
path.lineTo(tx1, ty2);
path.lineTo(tx2, ty2);
path.lineTo(tx2, ty1);
path.closePath();
// Rectangle2D rect = new Rectangle2D.Double(x, y, w, h);
g2.draw(path);
}
/**
* Fill a rectangle transforming co-ordinates to each axis
*/
protected void fillRect(Graphics2D g2, double x1, double y1, double x2, double y2) {
float tx1 = (float) transformX(x1);
float ty1 = (float) transformY(y1);
float tx2 = (float) transformX(x2);
float ty2 = (float) transformY(y2);
GeneralPath path = new GeneralPath();
path.moveTo(tx1, ty1);
path.lineTo(tx1, ty2);
path.lineTo(tx2, ty2);
path.lineTo(tx2, ty1);
path.closePath();
// Rectangle2D rect = new Rectangle2D.Double(x, y, w, h);
g2.fill(path);
}
/**
* Paint actual plot
*/
public void paintPlot(Graphics2D g2, double xScale, double yScale,
double xOffset, double yOffset) {
if (xAxis == null || yAxis == null)
return;
this.xScale = xScale;
this.yScale = yScale;
this.xOffset = xOffset;
this.yOffset = yOffset;
// variable is assigned to itself
//this.bounds = bounds;
if (xData != null && yData != null && xData.getCount() > 0)
paintData(g2, xData, yData);
}
/**
* Paint data series
*/
abstract protected void paintData(Graphics2D g2, Variate.N xData, Variate.N yData);
/**
* A point on the plot has been clicked
*/
public void pointClicked(Point2D point, boolean isShiftDown) {
double x = untransformX(point.getX());
double y = untransformY(point.getY());
firePointClickedEvent(x, y, isShiftDown);
}
public void selectPoints(final Rectangle2D dragRectangle, final boolean addToSelection) {
if (dragRectangle == null) {
return;
}
if (!addToSelection) {
selectedPoints.clear();
}
double x0 = untransformX(dragRectangle.getX());
double y0 = untransformY(dragRectangle.getY() + dragRectangle.getHeight());
double x1 = untransformX(dragRectangle.getX() + dragRectangle.getWidth());
double y1 = untransformY(dragRectangle.getY());
for (int i = 0; i < xData.getCount(); i ++) {
double x = (Double) xData.get(i);
double y = (Double) yData.get(i);
if (x >= x0 && x <= x1 && y >= y0 && y <= y1) {
selectedPoints.add(i);
}
}
fireSelectionChanged();
}
public void selectPoint(final int index, final boolean addToSelection) {
if (!addToSelection) {
selectedPoints.clear();
}
selectedPoints.add(index);
fireSelectionChanged();
}
public void clearSelection() {
selectedPoints.clear();
fireSelectionChanged();
}
public void setSelectedPoints(final Collection<Integer> selectedPoints) {
this.selectedPoints.clear();
this.selectedPoints.addAll(selectedPoints);
}
public Set<Integer> getSelectedPoints() {
return selectedPoints;
}
// Listeners
private final java.util.Vector<Listener> listeners = new java.util.Vector<Listener>();
/**
* Add a plot listener
*/
public void addListener(Listener listener) {
listeners.add(listener);
}
/**
* Tells plot listeners that a point has been clicked.
*/
protected void firePointClickedEvent(double x, double y, boolean isShiftDown) {
for (int i = 0; i < listeners.size(); i++) {
final Listener listener = listeners.elementAt(i);
listener.pointClicked(x, y, isShiftDown);
}
}
/**
* Tells plot listeners that a point has been clicked.
*/
protected void fireMarkClickedEvent(int index, double x, double y, boolean isShiftDown) {
for (int i=0; i < listeners.size(); i++) {
final Listener listener = listeners.elementAt(i);
listener.markClicked(index, x, y, isShiftDown);
}
}
protected void fireSelectionChanged() {
for (int i = 0; i < listeners.size(); i++) {
final Listener listener = listeners.elementAt(i);
listener.selectionChanged(selectedPoints);
}
}
}
}