/*
* GRAL: GRAphing Library for Java(R)
*
* (C) Copyright 2009-2016 Erich Seifert ,
* Michael Seifert
*
* This file is part of GRAL.
*
* GRAL 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 3 of the License, or
* (at your option) any later version.
*
* GRAL 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 GRAL. If not, see .
*/
package de.erichseifert.gral.plots;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Dimension2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import de.erichseifert.gral.data.*;
import de.erichseifert.gral.graphics.Drawable;
import de.erichseifert.gral.graphics.DrawingContext;
import de.erichseifert.gral.graphics.Insets2D;
import de.erichseifert.gral.graphics.Orientation;
import de.erichseifert.gral.navigation.Navigable;
import de.erichseifert.gral.navigation.NavigationDirection;
import de.erichseifert.gral.navigation.Navigator;
import de.erichseifert.gral.plots.areas.AreaRenderer;
import de.erichseifert.gral.plots.axes.Axis;
import de.erichseifert.gral.plots.axes.AxisListener;
import de.erichseifert.gral.plots.axes.AxisRenderer;
import de.erichseifert.gral.plots.axes.LinearRenderer2D;
import de.erichseifert.gral.plots.axes.Tick;
import de.erichseifert.gral.plots.axes.Tick.TickType;
import de.erichseifert.gral.plots.legends.AbstractLegend;
import de.erichseifert.gral.plots.legends.SeriesLegend;
import de.erichseifert.gral.plots.lines.LineRenderer;
import de.erichseifert.gral.plots.points.DefaultPointRenderer2D;
import de.erichseifert.gral.plots.points.PointData;
import de.erichseifert.gral.plots.points.PointRenderer;
import de.erichseifert.gral.util.GeometryUtils;
import de.erichseifert.gral.util.GraphicsUtils;
import de.erichseifert.gral.util.MathUtils;
import de.erichseifert.gral.util.PointND;
/**
* Class that displays data in an two dimensional coordinate system
* (x-y plot). It also serves as a base class for many other plot types.
* To create a new {@code XYPlot} simply create a new instance
* using one or more data sources. Example:
*
* DataTable data = new DataTable(Integer.class, Integer.class);
* data.add( 1, 2);
* data.add(-5, 0);
*
* XYPlot plot = new XYPlot(data);
*
*/
public class XYPlot extends AbstractPlot implements Navigable, AxisListener {
/** Version id for serialization. */
private static final long serialVersionUID = 4501074701747572783L;
/** Key for specifying the x-axis of an xy-plot. */
public static final String AXIS_X = "x"; //$NON-NLS-1$
/** Key for specifying the secondary x-axis of an xy-plot. */
public static final String AXIS_X2 = "x2"; //$NON-NLS-1$
/** Key for specifying the y-axis of an xy-plot. */
public static final String AXIS_Y = "y"; //$NON-NLS-1$
/** Key for specifying the secondary y-axis of an xy-plot. */
public static final String AXIS_Y2 = "y2"; //$NON-NLS-1$
/** Mapping from data source to point renderers. */
private final Map> pointRenderersByDataSource;
/** Mapping from data source to line renderers. */
private final Map> lineRenderersByDataSource;
/** Mapping from data source to area renderers. */
private final Map> areaRenderersByDataSource;
/** Cache for the {@code Navigator} implementation. */
private transient XYPlotNavigator navigator;
/** A flag that shows whether the navigator has been properly
initialized. */
private transient boolean navigatorInitialized;
/**
* Constants which determine the direction of zoom and pan actions.
*/
public static enum XYNavigationDirection implements NavigationDirection {
/** Value for zooming and panning horizontally. */
HORIZONTAL(XYPlot.AXIS_X, XYPlot.AXIS_X2),
/** Value for zooming and panning vertically. */
VERTICAL(XYPlot.AXIS_Y, XYPlot.AXIS_Y2),
/** Value for zooming and panning in all direction. */
ARBITRARY(XYPlot.AXIS_X, XYPlot.AXIS_Y, XYPlot.AXIS_X2, XYPlot.AXIS_Y2);
/** Names of the axes that have the same direction. */
private final String[] axesNames;
/**
* Initializes a new instance with the names of the axes that have the
* same direction.
* @param axesNames Names of the axes that have the same direction.
*/
private XYNavigationDirection(String... axesNames) {
this.axesNames = axesNames;
}
/**
* Returns the names of the axes that have the direction described by
* this object.
* @return Names of the axes that have the same direction.
*/
public String[] getAxesNames() {
return axesNames;
}
}
/**
* Navigator implementation for two-dimensional plots.
*/
public static class XYPlotNavigator extends PlotNavigator {
/**
* Initializes a new Navigator for two-dimensional plots with the
* default axes.
* @param plot Two-dimensional plot that should be controlled.
*/
public XYPlotNavigator(XYPlot plot) {
super(plot, XYNavigationDirection.ARBITRARY.getAxesNames());
}
@Override
public void setDirection(NavigationDirection direction) {
if (direction == getDirection()) {
return;
}
if (!(direction instanceof XYNavigationDirection)) {
throw new IllegalArgumentException("Unknown direction.");
}
String[] axesNames = ((XYNavigationDirection)direction).getAxesNames();
setAxes(axesNames);
super.setDirection(direction);
}
@Override
protected Number getDimensionValue(String axisName,
PointND extends Number> values) {
if (XYPlot.AXIS_Y.equals(axisName) || XYPlot.AXIS_Y2.equals(axisName)) {
return -values.get(1).doubleValue();
}
return values.get(0);
}
@Override
protected int getDimensions() {
return 2;
}
}
/**
* Class that represents the drawing area of an {@code XYPlot}.
*/
public static class XYPlotArea2D extends PlotArea {
/** Version id for serialization. */
private static final long serialVersionUID = -3673157774425536428L;
/** x-y plot this plot area is associated to. */
private final XYPlot plot;
/** Decides whether the horizontal grid lines at major ticks are drawn. */
private boolean majorGridX;
/** Decides whether the vertical grid lines at major ticks are drawn. */
private boolean majorGridY;
/** Paint to fill the grid lines at major ticks. */
private Paint majorGridColor;
/** Decides whether the horizontal grid lines at minor ticks are drawn. */
private boolean minorGridX;
/** Decides whether the vertical grid lines at minor ticks are drawn. */
private boolean minorGridY;
/** Paint to fill the grid lines at minor ticks. */
private Paint minorGridColor;
/**
* Creates a new instance with default settings and initializes it with
* a plot serving as data provider.
* @param plot Data provider.
*/
public XYPlotArea2D(XYPlot plot) {
this.plot = plot;
majorGridX = true;
majorGridY = true;
majorGridColor = new Color(0.0f, 0.0f, 0.0f, 0.1f);
minorGridX = false;
minorGridY = false;
minorGridColor = new Color(0.0f, 0.0f, 0.0f, 0.05f);
}
/**
* Draws the {@code Drawable} with the specified {@code Graphics2D}
* object.
* @param context Environment used for drawing
*/
public void draw(DrawingContext context) {
drawBackground(context);
drawGrid(context);
drawBorder(context);
drawPlot(context);
plot.drawAxes(context);
plot.drawLegend(context);
}
/**
* Draws the grid using the specified drawing context.
* @param context Environment used for drawing.
*/
protected void drawGrid(DrawingContext context) {
Graphics2D graphics = context.getGraphics();
AffineTransform txOrig = graphics.getTransform();
graphics.translate(getX(), getY());
AffineTransform txOffset = graphics.getTransform();
Rectangle2D bounds = getBounds();
// Draw gridX
if (isMajorGridX() || isMinorGridX()) {
AxisRenderer axisXRenderer = plot.getAxisRenderer(AXIS_X);
Axis axisX = plot.getAxis(AXIS_X);
if (axisXRenderer != null && axisX != null && axisX.isValid()) {
Shape shapeX = axisXRenderer.getShape();
Rectangle2D shapeBoundsX = shapeX.getBounds2D();
List ticksX = axisXRenderer.getTicks(axisX);
Line2D gridLineVert = new Line2D.Double(
-shapeBoundsX.getMinX(),
-shapeBoundsX.getMinY(),
-shapeBoundsX.getMinX(),
bounds.getHeight() - shapeBoundsX.getMinY()
);
for (Tick tick : ticksX) {
if ((tick.type == TickType.MAJOR && !isMajorGridX()) ||
(tick.type == TickType.MINOR && !isMinorGridX())) {
continue;
}
Point2D tickPoint = tick.position.getPoint2D();
if (tickPoint == null) {
continue;
}
Paint paint = majorGridColor;
if (tick.type == TickType.MINOR) {
paint = getMinorGridColor();
}
graphics.translate(tickPoint.getX(), tickPoint.getY());
GraphicsUtils.drawPaintedShape(
graphics, gridLineVert, paint, null, null);
graphics.setTransform(txOffset);
}
}
}
// Draw gridY
if (isMajorGridY() || isMinorGridY()) {
Axis axisY = plot.getAxis(AXIS_Y);
AxisRenderer axisYRenderer = plot.getAxisRenderer(AXIS_Y);
if (axisY != null && axisY.isValid() && axisYRenderer != null) {
Shape shapeY = axisYRenderer.getShape();
Rectangle2D shapeBoundsY = shapeY.getBounds2D();
List ticksY = axisYRenderer.getTicks(axisY);
Line2D gridLineHoriz = new Line2D.Double(
-shapeBoundsY.getMinX(), -shapeBoundsY.getMinY(),
bounds.getWidth() - shapeBoundsY.getMinX(), -shapeBoundsY.getMinY()
);
for (Tick tick : ticksY) {
boolean isMajorTick = tick.type == TickType.MAJOR;
boolean isMinorTick = tick.type == TickType.MINOR;
if ((isMajorTick && !isMajorGridY()) ||
(isMinorTick && !isMinorGridY())) {
continue;
}
Point2D tickPoint = tick.position.getPoint2D();
if (tickPoint == null) {
continue;
}
Paint paint = majorGridColor;
if (isMinorTick) {
paint = getMinorGridColor();
}
graphics.translate(tickPoint.getX(), tickPoint.getY());
GraphicsUtils.drawPaintedShape(
graphics, gridLineHoriz, paint, null, null);
graphics.setTransform(txOffset);
}
}
}
graphics.setTransform(txOrig);
}
@Override
protected void drawPlot(DrawingContext context) {
Graphics2D graphics = context.getGraphics();
Shape clipBoundsOld = graphics.getClip();
Insets2D clipOffset = getClippingOffset();
if (clipOffset != null) {
final double fontSize = getBaseFont().getSize2D();
// Perform clipping
Shape clipBounds = new Rectangle2D.Double(
getX() + clipOffset.getLeft()*fontSize,
getY() + clipOffset.getTop()*fontSize,
getWidth() - clipOffset.getHorizontal()*fontSize,
getHeight() - clipOffset.getVertical()*fontSize
);
// Take care of old clipping region. This is used when getting
// scrolled in a JScrollPane for example.
if (clipBoundsOld != null) {
Area clipBoundsNew = new Area(clipBoundsOld);
clipBoundsNew.intersect(new Area(clipBounds));
clipBounds = clipBoundsNew;
}
graphics.setClip(clipBounds);
}
AffineTransform txOrig = graphics.getTransform();
graphics.translate(getX(), getY());
AffineTransform txOffset = graphics.getTransform();
// Paint points and lines
for (DataSource s : plot.getVisibleData()) {
// Skip empty data source
if (s.getColumnCount() == 0) {
continue;
}
int colX = 0;
if (colX < 0 || colX >= s.getColumnCount() || !s.isColumnNumeric(colX)) {
continue;
}
int colY = 1;
if (colY < 0 || colY >= s.getColumnCount() || !s.isColumnNumeric(colY)) {
continue;
}
String[] axisNames = plot.getMapping(s);
Axis axisX = plot.getAxis(axisNames[0]);
Axis axisY = plot.getAxis(axisNames[1]);
if (!axisX.isValid() || !axisY.isValid()) {
continue;
}
AxisRenderer axisXRenderer = plot.getAxisRenderer(axisNames[0]);
AxisRenderer axisYRenderer = plot.getAxisRenderer(axisNames[1]);
List points = new LinkedList();
for (int i = 0; i < s.getRowCount(); i++) {
Row row = new Row(s, i);
Number valueX = (Number) row.get(colX);
Number valueY = (Number) row.get(colY);
PointND axisPosX = (axisXRenderer != null)
? axisXRenderer.getPosition(axisX, valueX, true, false)
: new PointND(0.0, 0.0);
PointND axisPosY = (axisYRenderer != null)
? axisYRenderer.getPosition(axisY, valueY, true, false)
: new PointND(0.0, 0.0);
if (axisPosX == null || axisPosY == null) {
continue;
}
PointND pos = new PointND(
axisPosX.get(PointND.X), axisPosY.get(PointND.Y));
PointData pointData = new PointData(
Arrays.asList(axisX, axisY),
Arrays.asList(axisXRenderer, axisYRenderer),
row, row.getIndex(), colY);
DataPoint dataPoint = new DataPoint(pointData, pos);
points.add(dataPoint);
}
List pointRenderers = new ArrayList(plot.getPointRenderers(s));
Collections.reverse(pointRenderers);
List areaRenderers = new ArrayList(plot.getAreaRenderers(s));
Collections.reverse(areaRenderers);
for (AreaRenderer areaRenderer : areaRenderers) {
Shape area = areaRenderer.getAreaShape(points);
Shape punchedArea = area;
for (PointRenderer pointRenderer : pointRenderers) {
List punchShapes = new ArrayList(points.size());
for (DataPoint point : points) {
Shape punchShape = pointRenderer.getPointShape(point.data);
punchShapes.add(punchShape);
}
punchedArea = punch(punchedArea, points, punchShapes, areaRenderer.getGap(), areaRenderer.isGapRounded());
}
Drawable drawable = areaRenderer.getArea(points, punchedArea);
drawable.draw(context);
}
List lineRenderers = new ArrayList(plot.getLineRenderers(s));
Collections.reverse(lineRenderers);
for (LineRenderer lineRenderer : lineRenderers) {
Shape line = lineRenderer.getLineShape(points);
Shape punchedLine = line;
for (PointRenderer pointRenderer : pointRenderers) {
List punchShapes = new ArrayList(points.size());
for (DataPoint point : points) {
Shape punchShape = pointRenderer.getPointShape(point.data);
punchShapes.add(punchShape);
}
punchedLine = punch(punchedLine, points, punchShapes, lineRenderer.getGap(), lineRenderer.isGapRounded());
}
Drawable drawable = lineRenderer.getLine(points, punchedLine);
drawable.draw(context);
}
if (!plot.getPointRenderers(s).isEmpty()) {
// Draw graphics
for (DataPoint point : points) {
PointND pos = point.position;
double pointX = pos.get(PointND.X);
double pointY = pos.get(PointND.Y);
graphics.translate(pointX, pointY);
for (PointRenderer pointRenderer : plot.getPointRenderers(s)) {
Shape pointShape = pointRenderer.getPointShape(point.data);
Drawable pointDrawable = pointRenderer.getPoint(point.data, pointShape);
pointDrawable.draw(context);
}
graphics.setTransform(txOffset);
}
// Draw labels
for (DataPoint point : points) {
PointND pos = point.position;
double pointX = pos.get(PointND.X);
double pointY = pos.get(PointND.Y);
graphics.translate(pointX, pointY);
for (PointRenderer pointRenderer : plot.getPointRenderers(s)) {
Shape pointShape = pointRenderer.getPointShape(point.data);
Drawable labelDrawable = pointRenderer.getValue(point.data, pointShape);
labelDrawable.draw(context);
}
graphics.setTransform(txOffset);
}
}
}
// Reset transformation (offset)
graphics.setTransform(txOrig);
if (clipOffset != null) {
// Reset clipping
graphics.setClip(clipBoundsOld);
}
}
/**
* Returns the shape from which the shapes of the specified points are subtracted.
* @param shape Shape to be modified.
* @param dataPoints Data points on the line.
* @param punchShapes Shape used for punching.
* @param gap Gap between shape and point shapes.
* @param roundedGaps {@code true} if the shape gaps are rounded.
* @return Punched shape.
*/
protected static Shape punch(Shape shape, List dataPoints, List punchShapes, double gap, boolean roundedGaps) {
if (!MathUtils.isCalculatable(gap) || gap == 0.0) {
return shape;
}
// Subtract shapes of data points from the line to yield gaps.
Area punched = new Area(shape);
for (int pointIndex = 0; pointIndex < dataPoints.size(); pointIndex++) {
DataPoint p = dataPoints.get(pointIndex);
punched = GeometryUtils.punch(punched, gap, roundedGaps,
p.position.getPoint2D(), punchShapes.get(pointIndex));
}
return punched;
}
/**
* Returns whether horizontal grid lines at major ticks along the
* x-axis are drawn.
* @return {@code true} if horizontal grid lines at major ticks along
* the x-axis are drawn, otherwise {@code false}.
*/
public boolean isMajorGridX() {
return majorGridX;
}
/**
* Sets whether horizontal grid lines at major ticks along the x-axis
* will be drawn.
* @param gridMajorX {@code true} if horizontal grid lines at major
* ticks along the x-axis should be drawn, otherwise {@code false}.
*/
public void setMajorGridX(boolean gridMajorX) {
this.majorGridX = gridMajorX;
}
/**
* Returns whether vertical grid lines at major ticks along the y-axis
* are drawn.
* @return {@code true} if vertical grid lines at major ticks along the
* y-axis are drawn, otherwise {@code false}.
*/
public boolean isMajorGridY() {
return majorGridY;
}
/**
* Sets whether vertical grid lines at major ticks along the y-axis
* will be drawn.
* @param gridMajorY {@code true} if vertical grid lines at major ticks
* along the y-axis should be drawn, otherwise {@code false}.
*/
public void setMajorGridY(boolean gridMajorY) {
this.majorGridY = gridMajorY;
}
/**
* Returns the paint which is used to paint the grid lines at major
* ticks.
* @return Paint which is used to paint the grid lines at major ticks.
*/
public Paint getMajorGridColor() {
return majorGridColor;
}
/**
* Sets the paint which will be used to paint the grid lines at major
* ticks.
* @param color Paint which should be used to paint the grid lines at
* major ticks.
*/
public void setMajorGridColor(Color color) {
this.majorGridColor = color;
}
/**
* Returns whether horizontal grid lines at minor ticks along the
* x-axis are drawn.
* @return {@code true} if horizontal grid lines at minor ticks along
* the x-axis are drawn, otherwise {@code false}.
*/
public boolean isMinorGridX() {
return minorGridX;
}
/**
* Sets whether horizontal grid lines at minor ticks along the x-axis
* will be drawn.
* @param gridMinorX {@code true} if horizontal grid lines at minor
* ticks along the x-axis should be drawn, otherwise {@code false}.
*/
public void setMinorGridX(boolean gridMinorX) {
this.minorGridX = gridMinorX;
}
/**
* Returns whether vertical grid lines at minor ticks along the y-axis
* are drawn.
* @return {@code true} if vertical grid lines at minor ticks along the
* y-axis are drawn, otherwise {@code false}.
*/
public boolean isMinorGridY() {
return minorGridY;
}
/**
* Sets whether vertical grid lines at minor ticks along the y-axis
* will be drawn.
* @param gridMinorY {@code true} if vertical grid lines at minor ticks
* along the y-axis should be drawn, otherwise {@code false}.
*/
public void setMinorGridY(boolean gridMinorY) {
this.minorGridY = gridMinorY;
}
/**
* Returns the paint which is used to paint the grid lines at minor
* ticks.
* @return Paint which is used to paint the grid lines at minor ticks.
*/
public Paint getMinorGridColor() {
return minorGridColor;
}
/**
* Sets the paint which will be used to paint the grid lines at minor
* ticks.
* @param color Paint which should be used to paint the grid lines at
* minor ticks.
*/
public void setMinorGridColor(Color color) {
this.minorGridColor = color;
}
}
/**
* Class that displays a legend in an {@code XYPlot}.
*/
public static class XYLegend extends SeriesLegend {
/** Version id for serialization. */
private static final long serialVersionUID = -4629928754001372002L;
/** Plot that contains settings and renderers. */
private final XYPlot plot;
/**
* Constructor that initializes the instance with a plot acting as a
* provider for settings and renderers.
* @param plot Plot.
*/
public XYLegend(XYPlot plot) {
this.plot = plot;
}
@Override
protected Drawable getSymbol(DataSource data) {
return new LegendSymbol(plot, data, plot.getFont(), plot.getLegend().getSymbolSize());
}
}
private static class LegendSymbol extends AbstractLegend.AbstractSymbol {
/** Source for dummy data. */
private static final DataSource DUMMY_DATA = new DummyData(2, Integer.MAX_VALUE, 0.5);
private final XYPlot plot;
private final DataSource data;
public LegendSymbol(XYPlot plot, DataSource data, Font font, Dimension2D symbolSize) {
super(font, symbolSize);
this.plot = plot;
this.data = data;
}
@Override
public void draw(DrawingContext context) {
Row symbolRow = new Row(DUMMY_DATA, 0);
Rectangle2D bounds = getBounds();
Axis axisX = new Axis(0.0, 1.0);
AxisRenderer axisRendererX = new LinearRenderer2D();
axisRendererX.setShape(new Line2D.Double(
bounds.getMinX(), bounds.getCenterY(),
bounds.getMaxX(), bounds.getCenterY()));
Axis axisY = new Axis(0.0, 1.0);
AxisRenderer axisRendererY = new LinearRenderer2D();
axisRendererY.setShape(new Line2D.Double(
bounds.getCenterX(), bounds.getMaxY(),
bounds.getCenterX(), bounds.getMinY()));
PointData pointData = new PointData(
Arrays.asList(axisX, axisY),
Arrays.asList(axisRendererX, axisRendererY),
symbolRow, symbolRow.getIndex(), 0);
DataPoint p1 = new DataPoint(
pointData,
new PointND(bounds.getMinX(), bounds.getCenterY())
);
DataPoint p2 = new DataPoint(
pointData,
new PointND(bounds.getCenterX(), bounds.getCenterY())
);
DataPoint p3 = new DataPoint(
pointData,
new PointND(bounds.getMaxX(), bounds.getCenterY())
);
List points = Arrays.asList(p1, p2, p3);
// TODO: Provide a means to set the AreaRenderer used for the Legend
AreaRenderer areaRenderer = null;
List areaRenderers = plot.getAreaRenderers(data);
if (!areaRenderers.isEmpty()) {
areaRenderer = areaRenderers.get(0);
}
if (areaRenderer != null) {
Shape area = areaRenderer.getAreaShape(points);
Drawable drawable = areaRenderer.getArea(points, area);
drawable.draw(context);
}
// TODO: Provide a means to set the LineRenderer used for the Legend
LineRenderer lineRenderer = null;
List lineRenderers = plot.getLineRenderers(data);
if (!lineRenderers.isEmpty()) {
lineRenderer = lineRenderers.get(0);
}
if (lineRenderer != null) {
Shape line = lineRenderer.getLineShape(points);
Drawable drawable = lineRenderer.getLine(points, line);
drawable.draw(context);
}
// TODO: Provide a means to set the PointRenderer used for the Legend
PointRenderer pointRenderer = null;
List pointRenderers = plot.getPointRenderers(data);
if (!pointRenderers.isEmpty()) {
pointRenderer = pointRenderers.get(0);
}
if (pointRenderer != null) {
Graphics2D graphics = context.getGraphics();
Point2D pos = p2.position.getPoint2D();
AffineTransform txOrig = graphics.getTransform();
graphics.translate(pos.getX(), pos.getY());
Shape shape = pointRenderer.getPointShape(pointData);
Drawable drawable = pointRenderer.getPoint(pointData, shape);
drawable.draw(context);
graphics.setTransform(txOrig);
}
}
}
/**
* Initializes a new instance object with the specified data sources and
* reasonable default settings.
* @param data Data to be displayed.
*/
public XYPlot(DataSource... data) {
super();
pointRenderersByDataSource = new HashMap>(data.length);
lineRenderersByDataSource = new HashMap>(data.length);
areaRenderersByDataSource = new HashMap>(data.length);
setPlotArea(new XYPlotArea2D(this));
setLegend(new XYLegend(this));
// Handle data sources after the renderer lists are initialized
for (DataSource source : data) {
add(source);
}
createDefaultAxes();
autoscaleAxes();
createDefaultAxisRenderers();
// Listen for changes of the axis range
for (String axisName : getAxesNames()) {
getAxis(axisName).addAxisListener(this);
}
}
@Override
protected void createDefaultAxes() {
// Create x axis and y axis by default
Axis axisX = new Axis();
Axis axisY = new Axis();
setAxis(AXIS_X, axisX);
setAxis(AXIS_Y, axisY);
}
@Override
protected void createDefaultAxisRenderers() {
// Create renderers for x and y axes by default
AxisRenderer axisXRenderer = new LinearRenderer2D();
AxisRenderer axisYRenderer = new LinearRenderer2D();
setAxisRenderer(AXIS_X, axisXRenderer);
setAxisRenderer(AXIS_Y, axisYRenderer);
}
@Override
protected void layoutAxes() {
if (getPlotArea() == null) {
return;
}
// Set the new shapes first to allow for correct positioning
layoutAxisShape(AXIS_X, Orientation.HORIZONTAL);
layoutAxisShape(AXIS_X2, Orientation.HORIZONTAL);
layoutAxisShape(AXIS_Y, Orientation.VERTICAL);
layoutAxisShape(AXIS_Y2, Orientation.VERTICAL);
// Set bounds with new axis shapes
layoutAxisComponent(AXIS_X, Orientation.HORIZONTAL);
layoutAxisComponent(AXIS_X2, Orientation.HORIZONTAL);
layoutAxisComponent(AXIS_Y, Orientation.VERTICAL);
layoutAxisComponent(AXIS_Y2, Orientation.VERTICAL);
}
private void layoutAxisShape(String axisName, Orientation orientation) {
Rectangle2D plotBounds = getPlotArea().getBounds();
Drawable comp = getAxisComponent(axisName);
AxisRenderer renderer = getAxisRenderer(axisName);
if (comp == null || renderer == null) {
return;
}
Dimension2D size = comp.getPreferredSize();
Shape shape;
if (orientation == Orientation.HORIZONTAL) {
shape = new Line2D.Double(
0.0, 0.0,
plotBounds.getWidth(), 0.0
);
} else {
shape = new Line2D.Double(
size.getWidth(), plotBounds.getHeight(),
size.getWidth(), 0.0
);
}
renderer.setShape(shape);
}
private void layoutAxisComponent(String axisName, Orientation orientation) {
Drawable comp = getAxisComponent(axisName);
AxisRenderer renderer = getAxisRenderer(axisName);
if (comp == null || renderer == null) {
return;
}
String nameSecondary;
if (orientation == Orientation.HORIZONTAL) {
nameSecondary = AXIS_Y;
} else {
nameSecondary = AXIS_X;
}
Axis axisSecondary = getAxis(nameSecondary);
AxisRenderer rendererSecondary = getAxisRenderer(nameSecondary);
if (axisSecondary == null || !axisSecondary.isValid() ||
rendererSecondary == null) {
return;
}
Number intersection = renderer.getIntersection();
PointND pos = rendererSecondary.getPosition(
axisSecondary, intersection, false, false);
if (pos == null) {
pos = new PointND(0.0, 0.0);
}
Rectangle2D plotBounds = getPlotArea().getBounds();
Dimension2D size = comp.getPreferredSize();
if (orientation == Orientation.HORIZONTAL) {
comp.setBounds(
plotBounds.getMinX(),
pos.get(1) + plotBounds.getMinY(),
plotBounds.getWidth(),
size.getHeight()
);
} else {
comp.setBounds(
plotBounds.getMinX() - size.getWidth() + pos.get(0),
plotBounds.getMinY(),
size.getWidth(),
plotBounds.getHeight()
);
}
}
/**
* Returns all {@code PointRenderer}s that display the data of the specified data source.
* @param s Data source in question.
* @return Renderers being applied on the specified data source.
*/
public List getPointRenderers(DataSource s) {
List pointRenderers = pointRenderersByDataSource.get(s);
if (pointRenderers != null) {
return Collections.unmodifiableList(pointRenderers);
}
return Collections.emptyList();
}
/**
* Adds a {@code PointRenderer} for the specified data source.
* @param s Data to be rendered.
* @param pointRenderer PointRenderer to be used.
*/
public void addPointRenderer(DataSource s, PointRenderer pointRenderer) {
List pointRenderers = pointRenderersByDataSource.get(s);
if (pointRenderers == null) {
pointRenderers = new ArrayList();
pointRenderersByDataSource.put(s, pointRenderers);
}
pointRenderers.add(pointRenderer);
}
/**
* Decouples the specified {@code PointRenderer} from the rendering of the specified data source.
* @param s Data to be rendered no longer.
* @param pointRenderer PointRenderer to be removed.
*/
public void removePointRenderer(DataSource s, PointRenderer pointRenderer) {
List pointRenderers = pointRenderersByDataSource.get(s);
if (pointRenderers != null) {
pointRenderers.remove(pointRenderer);
}
}
/**
* Sets the {@code PointRenderer}s for a certain data source to the specified value.
* @param s Data source.
* @param pointRenderers PointRenderers to be set.
*/
public void setPointRenderers(DataSource s, List pointRenderers) {
this.pointRenderersByDataSource.put(s, pointRenderers);
}
/**
* Sets the {@code PointRenderer}s for a certain data source to the specified value.
* @param s Data source.
* @param pointRendererFirst First PointRenderer.
* @param pointRenderers Remaining PointRenderers to be set.
*/
public void setPointRenderers(DataSource s, PointRenderer pointRendererFirst, PointRenderer... pointRenderers) {
List pointRendererList = null;
if (pointRendererFirst == null) {
setPointRenderers(s, pointRendererList);
return;
}
pointRendererList = new ArrayList(pointRenderers.length + 1);
pointRendererList.add(pointRendererFirst);
for (PointRenderer pointRenderer : pointRenderers) {
if (pointRenderer == null) {
throw new IllegalArgumentException("A PointRenderer for a DataSource cannot be null.");
}
pointRendererList.add(pointRenderer);
}
setPointRenderers(s, pointRendererList);
}
/**
* Returns all {@code LineRenderer}s that display the data of the specified data source.
* @param s Data source in question.
* @return Renderers being applied on the specified data source.
*/
public List getLineRenderers(DataSource s) {
List lineRenderers = lineRenderersByDataSource.get(s);
if (lineRenderers != null) {
return Collections.unmodifiableList(lineRenderers);
}
return Collections.emptyList();
}
/**
* Sets the {@code LineRenderer}s for a certain data source to the specified
* value.
* @param s Data source.
* @param lineRenderers {@code LineRenderer}s to be set.
*/
public void setLineRenderers(DataSource s, List lineRenderers) {
lineRenderersByDataSource.put(s, lineRenderers);
}
/**
* Sets the {@code LineRenderer}s for a certain data source to the specified
* value.
* @param s Data source.
* @param lineRendererFirst First {@code LineRenderer} to be set.
* @param lineRenderers Remaining {@code LineRenderer}s to be set.
*/
public void setLineRenderers(DataSource s, LineRenderer lineRendererFirst, LineRenderer... lineRenderers) {
List lineRendererList = null;
if (lineRendererFirst == null) {
setLineRenderers(s, lineRendererList);
return;
}
lineRendererList = new ArrayList(lineRenderers.length + 1);
lineRendererList.add(lineRendererFirst);
for (LineRenderer lineRenderer : lineRenderers) {
if (lineRenderer == null) {
throw new IllegalArgumentException("A LineRenderer for a DataSource cannot be null.");
}
lineRendererList.add(lineRenderer);
}
setLineRenderers(s, lineRendererList);
}
/**
* Returns all {@code AreaRenderer}s for the specified data source.
* @param s Data source.
* @return {@code AreaRenderer}s used to render the {@code DataSource}.
*/
public List getAreaRenderers(DataSource s) {
List areaRenderers = areaRenderersByDataSource.get(s);
if (areaRenderers != null) {
return Collections.unmodifiableList(areaRenderers);
}
return Collections.emptyList();
}
/**
* Sets the {@code AreaRenderer}s for a certain data source to the specified
* value.
* @param s Data source.
* @param areaRenderers {@code AreaRenderer}s to be set.
*/
public void setAreaRenderers(DataSource s, List areaRenderers) {
areaRenderersByDataSource.put(s, areaRenderers);
}
/**
* Sets the {@code AreaRenderer}s for a certain data source to the specified
* value.
* @param s Data source.
* @param areaRendererFirst First {@code AreaRenderer} to be set.
* @param areaRenderers Remaining {@code AreaRenderer}s to be set.
*/
public void setAreaRenderers(DataSource s, AreaRenderer areaRendererFirst, AreaRenderer... areaRenderers) {
List areaRendererList = null;
if (areaRendererFirst == null) {
setAreaRenderers(s, areaRendererList);
return;
}
areaRendererList = new ArrayList(areaRenderers.length + 1);
areaRendererList.add(areaRendererFirst);
for (AreaRenderer areaRenderer : areaRenderers) {
if (areaRenderer == null) {
throw new IllegalArgumentException("An AreaRenderer for a DataSource cannot be null.");
}
areaRendererList.add(areaRenderer);
}
setAreaRenderers(s, areaRendererList);
}
@Override
public void setAxisRenderer(String axisName, AxisRenderer renderer) {
if (renderer != null) {
if (AXIS_X2.equals(axisName) || AXIS_Y.equals(axisName)) {
renderer.setShapeNormalOrientationClockwise(true);
}
if (AXIS_Y.equals(axisName)) {
renderer.getLabel().setRotation(90.0);
}
}
super.setAxisRenderer(axisName, renderer);
}
@Override
public void add(int index, DataSource source, boolean visible) {
super.add(index, source, visible);
// Set axis mapping
setMapping(source, AXIS_X, AXIS_Y);
// The mapping from columns to axes has changed, so scaling has to be
// refreshed
autoscaleAxes();
// Assign default renderers
PointRenderer pointRendererDefault = new DefaultPointRenderer2D();
LineRenderer lineRendererDefault = null;
AreaRenderer areaRendererDefault = null;
// FIXME: Overwrites possible present point and line renderers
setPointRenderers(source, pointRendererDefault);
setLineRenderers(source, lineRendererDefault);
setAreaRenderers(source, areaRendererDefault);
}
/**
* Returns a navigator instance that can control the current object.
* @return A navigator instance.
*/
public Navigator getNavigator() {
if (navigator == null) {
navigator = new XYPlotNavigator(this);
}
return navigator;
}
@Override
public void draw(DrawingContext context) {
super.draw(context);
if (!navigatorInitialized) {
getNavigator().setDefaultState();
navigatorInitialized = true;
}
}
/**
* Notified if the range of an axis has changed.
* @param axis Axis instance that has changed.
* @param min New minimum value.
* @param max New maximum value.
*/
public void rangeChanged(Axis axis, Number min, Number max) {
layoutAxes();
}
/**
* Custom deserialization method.
* @param in Input stream.
* @throws ClassNotFoundException if a serialized class doesn't exist anymore.
* @throws IOException if there is an error while reading data from the
* input stream.
*/
private void readObject(ObjectInputStream in)
throws ClassNotFoundException, IOException {
// Normal deserialization
in.defaultReadObject();
// Restore listeners
for (String axisName : getAxesNames()) {
getAxis(axisName).addAxisListener(this);
}
}
}
Ads help maintain this website.