0001: /* ===========================================================
0002: * JFreeChart : a free chart library for the Java(tm) platform
0003: * ===========================================================
0004: *
0005: * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
0006: *
0007: * Project Info: http://www.jfree.org/jfreechart/index.html
0008: *
0009: * This library is free software; you can redistribute it and/or modify it
0010: * under the terms of the GNU Lesser General Public License as published by
0011: * the Free Software Foundation; either version 2.1 of the License, or
0012: * (at your option) any later version.
0013: *
0014: * This library is distributed in the hope that it will be useful, but
0015: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
0016: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
0017: * License for more details.
0018: *
0019: * You should have received a copy of the GNU Lesser General Public
0020: * License along with this library; if not, write to the Free Software
0021: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
0022: * USA.
0023: *
0024: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
0025: * in the United States and other countries.]
0026: *
0027: * ---------------------------
0028: * StandardXYItemRenderer.java
0029: * ---------------------------
0030: * (C) Copyright 2001-2007, by Object Refinery Limited and Contributors.
0031: *
0032: * Original Author: David Gilbert (for Object Refinery Limited);
0033: * Contributor(s): Mark Watson (www.markwatson.com);
0034: * Jonathan Nash;
0035: * Andreas Schneider;
0036: * Norbert Kiesel (for TBD Networks);
0037: * Christian W. Zuckschwerdt;
0038: * Bill Kelemen;
0039: * Nicolas Brodu (for Astrium and EADS Corporate Research
0040: * Center);
0041: *
0042: * $Id: StandardXYItemRenderer.java,v 1.18.2.14 2007/06/08 13:29:38 mungady Exp $
0043: *
0044: * Changes:
0045: * --------
0046: * 19-Oct-2001 : Version 1, based on code by Mark Watson (DG);
0047: * 22-Oct-2001 : Renamed DataSource.java --> Dataset.java etc. (DG);
0048: * 21-Dec-2001 : Added working line instance to improve performance (DG);
0049: * 22-Jan-2002 : Added code to lock crosshairs to data points. Based on code
0050: * by Jonathan Nash (DG);
0051: * 23-Jan-2002 : Added DrawInfo parameter to drawItem() method (DG);
0052: * 28-Mar-2002 : Added a property change listener mechanism so that the
0053: * renderer no longer needs to be immutable (DG);
0054: * 02-Apr-2002 : Modified to handle null values (DG);
0055: * 09-Apr-2002 : Modified draw method to return void. Removed the translated
0056: * zero from the drawItem method. Override the initialise()
0057: * method to calculate it (DG);
0058: * 13-May-2002 : Added code from Andreas Schneider to allow changing
0059: * shapes/colors per item (DG);
0060: * 24-May-2002 : Incorporated tooltips into chart entities (DG);
0061: * 25-Jun-2002 : Removed redundant code (DG);
0062: * 05-Aug-2002 : Incorporated URLs for HTML image maps into chart entities (RA);
0063: * 08-Aug-2002 : Added discontinuous lines option contributed by
0064: * Norbert Kiesel (DG);
0065: * 20-Aug-2002 : Added user definable default values to be returned by
0066: * protected methods unless overridden by a subclass (DG);
0067: * 23-Sep-2002 : Updated for changes in the XYItemRenderer interface (DG);
0068: * 02-Oct-2002 : Fixed errors reported by Checkstyle (DG);
0069: * 25-Mar-2003 : Implemented Serializable (DG);
0070: * 01-May-2003 : Modified drawItem() method signature (DG);
0071: * 15-May-2003 : Modified to take into account the plot orientation (DG);
0072: * 29-Jul-2003 : Amended code that doesn't compile with JDK 1.2.2 (DG);
0073: * 30-Jul-2003 : Modified entity constructor (CZ);
0074: * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
0075: * 24-Aug-2003 : Added null/NaN checks in drawItem (BK);
0076: * 08-Sep-2003 : Fixed serialization (NB);
0077: * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
0078: * 21-Jan-2004 : Override for getLegendItem() method (DG);
0079: * 27-Jan-2004 : Moved working line into state object (DG);
0080: * 10-Feb-2004 : Changed drawItem() method to make cut-and-paste overriding
0081: * easier (DG);
0082: * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState. Renamed
0083: * XYToolTipGenerator --> XYItemLabelGenerator (DG);
0084: * 08-Jun-2004 : Modified to use getX() and getY() methods (DG);
0085: * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
0086: * getYValue() (DG);
0087: * 25-Aug-2004 : Created addEntity() method in superclass (DG);
0088: * 08-Oct-2004 : Added 'gapThresholdType' as suggested by Mike Watts (DG);
0089: * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
0090: * 23-Feb-2005 : Fixed getLegendItem() method to show lines. Fixed bug
0091: * 1077108 (shape not visible for first item in series) (DG);
0092: * 10-Apr-2005 : Fixed item label positioning with horizontal orientation (DG);
0093: * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
0094: * 27-Apr-2005 : Use generator for series label in legend (DG);
0095: * ------------- JFREECHART 1.0.x ---------------------------------------------
0096: * 15-Jun-2006 : Fixed bug (1380480) for rendering series as path (DG);
0097: * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
0098: * 14-Mar-2007 : Fixed problems with the equals() and clone() methods (DG);
0099: * 23-Mar-2007 : Clean-up of shapesFilled attributes (DG);
0100: * 20-Apr-2007 : Updated getLegendItem() and drawItem() for renderer
0101: * change (DG);
0102: * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem()
0103: * method (DG);
0104: * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
0105: * 08-Jun-2007 : Fixed bug in entity creation (DG);
0106: *
0107: */
0108:
0109: package org.jfree.chart.renderer.xy;
0110:
0111: import java.awt.Graphics2D;
0112: import java.awt.Image;
0113: import java.awt.Paint;
0114: import java.awt.Point;
0115: import java.awt.Shape;
0116: import java.awt.Stroke;
0117: import java.awt.geom.GeneralPath;
0118: import java.awt.geom.Line2D;
0119: import java.awt.geom.Rectangle2D;
0120: import java.io.IOException;
0121: import java.io.ObjectInputStream;
0122: import java.io.ObjectOutputStream;
0123: import java.io.Serializable;
0124:
0125: import org.jfree.chart.LegendItem;
0126: import org.jfree.chart.axis.ValueAxis;
0127: import org.jfree.chart.entity.EntityCollection;
0128: import org.jfree.chart.event.RendererChangeEvent;
0129: import org.jfree.chart.labels.XYToolTipGenerator;
0130: import org.jfree.chart.plot.CrosshairState;
0131: import org.jfree.chart.plot.Plot;
0132: import org.jfree.chart.plot.PlotOrientation;
0133: import org.jfree.chart.plot.PlotRenderingInfo;
0134: import org.jfree.chart.plot.XYPlot;
0135: import org.jfree.chart.urls.XYURLGenerator;
0136: import org.jfree.data.xy.XYDataset;
0137: import org.jfree.io.SerialUtilities;
0138: import org.jfree.ui.RectangleEdge;
0139: import org.jfree.util.BooleanList;
0140: import org.jfree.util.BooleanUtilities;
0141: import org.jfree.util.ObjectUtilities;
0142: import org.jfree.util.PublicCloneable;
0143: import org.jfree.util.ShapeUtilities;
0144: import org.jfree.util.UnitType;
0145:
0146: /**
0147: * Standard item renderer for an {@link XYPlot}. This class can draw (a)
0148: * shapes at each point, or (b) lines between points, or (c) both shapes and
0149: * lines.
0150: * <P>
0151: * This renderer has been retained for historical reasons and, in general, you
0152: * should use the {@link XYLineAndShapeRenderer} class instead.
0153: */
0154: public class StandardXYItemRenderer extends AbstractXYItemRenderer
0155: implements XYItemRenderer, Cloneable, PublicCloneable,
0156: Serializable {
0157:
0158: /** For serialization. */
0159: private static final long serialVersionUID = -3271351259436865995L;
0160:
0161: /** Constant for the type of rendering (shapes only). */
0162: public static final int SHAPES = 1;
0163:
0164: /** Constant for the type of rendering (lines only). */
0165: public static final int LINES = 2;
0166:
0167: /** Constant for the type of rendering (shapes and lines). */
0168: public static final int SHAPES_AND_LINES = SHAPES | LINES;
0169:
0170: /** Constant for the type of rendering (images only). */
0171: public static final int IMAGES = 4;
0172:
0173: /** Constant for the type of rendering (discontinuous lines). */
0174: public static final int DISCONTINUOUS = 8;
0175:
0176: /** Constant for the type of rendering (discontinuous lines). */
0177: public static final int DISCONTINUOUS_LINES = LINES | DISCONTINUOUS;
0178:
0179: /** A flag indicating whether or not shapes are drawn at each XY point. */
0180: private boolean baseShapesVisible;
0181:
0182: /** A flag indicating whether or not lines are drawn between XY points. */
0183: private boolean plotLines;
0184:
0185: /** A flag indicating whether or not images are drawn between XY points. */
0186: private boolean plotImages;
0187:
0188: /** A flag controlling whether or not discontinuous lines are used. */
0189: private boolean plotDiscontinuous;
0190:
0191: /** Specifies how the gap threshold value is interpreted. */
0192: private UnitType gapThresholdType = UnitType.RELATIVE;
0193:
0194: /** Threshold for deciding when to discontinue a line. */
0195: private double gapThreshold = 1.0;
0196:
0197: /** A flag that controls whether or not shapes are filled for ALL series. */
0198: private Boolean shapesFilled;
0199:
0200: /**
0201: * A table of flags that control (per series) whether or not shapes are
0202: * filled.
0203: */
0204: private BooleanList seriesShapesFilled;
0205:
0206: /** The default value returned by the getShapeFilled() method. */
0207: private boolean baseShapesFilled;
0208:
0209: /**
0210: * A flag that controls whether or not each series is drawn as a single
0211: * path.
0212: */
0213: private boolean drawSeriesLineAsPath;
0214:
0215: /**
0216: * The shape that is used to represent a line in the legend.
0217: * This should never be set to <code>null</code>.
0218: */
0219: private transient Shape legendLine;
0220:
0221: /**
0222: * Constructs a new renderer.
0223: */
0224: public StandardXYItemRenderer() {
0225: this (LINES, null);
0226: }
0227:
0228: /**
0229: * Constructs a new renderer. To specify the type of renderer, use one of
0230: * the constants: {@link #SHAPES}, {@link #LINES} or
0231: * {@link #SHAPES_AND_LINES}.
0232: *
0233: * @param type the type.
0234: */
0235: public StandardXYItemRenderer(int type) {
0236: this (type, null);
0237: }
0238:
0239: /**
0240: * Constructs a new renderer. To specify the type of renderer, use one of
0241: * the constants: {@link #SHAPES}, {@link #LINES} or
0242: * {@link #SHAPES_AND_LINES}.
0243: *
0244: * @param type the type of renderer.
0245: * @param toolTipGenerator the item label generator (<code>null</code>
0246: * permitted).
0247: */
0248: public StandardXYItemRenderer(int type,
0249: XYToolTipGenerator toolTipGenerator) {
0250: this (type, toolTipGenerator, null);
0251: }
0252:
0253: /**
0254: * Constructs a new renderer. To specify the type of renderer, use one of
0255: * the constants: {@link #SHAPES}, {@link #LINES} or
0256: * {@link #SHAPES_AND_LINES}.
0257: *
0258: * @param type the type of renderer.
0259: * @param toolTipGenerator the item label generator (<code>null</code>
0260: * permitted).
0261: * @param urlGenerator the URL generator.
0262: */
0263: public StandardXYItemRenderer(int type,
0264: XYToolTipGenerator toolTipGenerator,
0265: XYURLGenerator urlGenerator) {
0266:
0267: super ();
0268: setBaseToolTipGenerator(toolTipGenerator);
0269: setURLGenerator(urlGenerator);
0270: if ((type & SHAPES) != 0) {
0271: this .baseShapesVisible = true;
0272: }
0273: if ((type & LINES) != 0) {
0274: this .plotLines = true;
0275: }
0276: if ((type & IMAGES) != 0) {
0277: this .plotImages = true;
0278: }
0279: if ((type & DISCONTINUOUS) != 0) {
0280: this .plotDiscontinuous = true;
0281: }
0282:
0283: this .shapesFilled = null;
0284: this .seriesShapesFilled = new BooleanList();
0285: this .baseShapesFilled = true;
0286: this .legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
0287: this .drawSeriesLineAsPath = false;
0288: }
0289:
0290: /**
0291: * Returns true if shapes are being plotted by the renderer.
0292: *
0293: * @return <code>true</code> if shapes are being plotted by the renderer.
0294: *
0295: * @see #setBaseShapesVisible
0296: */
0297: public boolean getBaseShapesVisible() {
0298: return this .baseShapesVisible;
0299: }
0300:
0301: /**
0302: * Sets the flag that controls whether or not a shape is plotted at each
0303: * data point.
0304: *
0305: * @param flag the flag.
0306: *
0307: * @see #getBaseShapesVisible
0308: */
0309: public void setBaseShapesVisible(boolean flag) {
0310: if (this .baseShapesVisible != flag) {
0311: this .baseShapesVisible = flag;
0312: notifyListeners(new RendererChangeEvent(this ));
0313: }
0314: }
0315:
0316: // SHAPES FILLED
0317:
0318: /**
0319: * Returns the flag used to control whether or not the shape for an item is
0320: * filled.
0321: * <p>
0322: * The default implementation passes control to the
0323: * <code>getSeriesShapesFilled</code> method. You can override this method
0324: * if you require different behaviour.
0325: *
0326: * @param series the series index (zero-based).
0327: * @param item the item index (zero-based).
0328: *
0329: * @return A boolean.
0330: *
0331: * @see #getSeriesShapesFilled(int)
0332: */
0333: public boolean getItemShapeFilled(int series, int item) {
0334: // return the overall setting, if there is one...
0335: if (this .shapesFilled != null) {
0336: return this .shapesFilled.booleanValue();
0337: }
0338:
0339: // otherwise look up the paint table
0340: Boolean flag = this .seriesShapesFilled.getBoolean(series);
0341: if (flag != null) {
0342: return flag.booleanValue();
0343: } else {
0344: return this .baseShapesFilled;
0345: }
0346: }
0347:
0348: /**
0349: * Returns the override flag that controls whether or not shapes are filled
0350: * for ALL series.
0351: *
0352: * @return The flag (possibly <code>null</code>).
0353: *
0354: * @since 1.0.5
0355: */
0356: public Boolean getShapesFilled() {
0357: return this .shapesFilled;
0358: }
0359:
0360: /**
0361: * Sets the 'shapes filled' for ALL series.
0362: *
0363: * @param filled the flag.
0364: *
0365: * @see #setShapesFilled(Boolean)
0366: */
0367: public void setShapesFilled(boolean filled) {
0368: // here we use BooleanUtilities to remain compatible with JDKs < 1.4
0369: setShapesFilled(BooleanUtilities.valueOf(filled));
0370: }
0371:
0372: /**
0373: * Sets the override flag that controls whether or not shapes are filled
0374: * for ALL series and sends a {@link RendererChangeEvent} to all registered
0375: * listeners.
0376: *
0377: * @param filled the flag (<code>null</code> permitted).
0378: *
0379: * @see #setShapesFilled(boolean)
0380: */
0381: public void setShapesFilled(Boolean filled) {
0382: this .shapesFilled = filled;
0383: fireChangeEvent();
0384: }
0385:
0386: /**
0387: * Returns the flag used to control whether or not the shapes for a series
0388: * are filled.
0389: *
0390: * @param series the series index (zero-based).
0391: *
0392: * @return A boolean.
0393: */
0394: public Boolean getSeriesShapesFilled(int series) {
0395: return this .seriesShapesFilled.getBoolean(series);
0396: }
0397:
0398: /**
0399: * Sets the 'shapes filled' flag for a series.
0400: *
0401: * @param series the series index (zero-based).
0402: * @param flag the flag.
0403: *
0404: * @see #getSeriesShapesFilled(int)
0405: */
0406: public void setSeriesShapesFilled(int series, Boolean flag) {
0407: this .seriesShapesFilled.setBoolean(series, flag);
0408: fireChangeEvent();
0409: }
0410:
0411: /**
0412: * Returns the base 'shape filled' attribute.
0413: *
0414: * @return The base flag.
0415: *
0416: * @see #setBaseShapesFilled(boolean)
0417: */
0418: public boolean getBaseShapesFilled() {
0419: return this .baseShapesFilled;
0420: }
0421:
0422: /**
0423: * Sets the base 'shapes filled' flag.
0424: *
0425: * @param flag the flag.
0426: *
0427: * @see #getBaseShapesFilled()
0428: */
0429: public void setBaseShapesFilled(boolean flag) {
0430: this .baseShapesFilled = flag;
0431: }
0432:
0433: /**
0434: * Returns true if lines are being plotted by the renderer.
0435: *
0436: * @return <code>true</code> if lines are being plotted by the renderer.
0437: *
0438: * @see #setPlotLines(boolean)
0439: */
0440: public boolean getPlotLines() {
0441: return this .plotLines;
0442: }
0443:
0444: /**
0445: * Sets the flag that controls whether or not a line is plotted between
0446: * each data point.
0447: *
0448: * @param flag the flag.
0449: *
0450: * @see #getPlotLines()
0451: */
0452: public void setPlotLines(boolean flag) {
0453: if (this .plotLines != flag) {
0454: this .plotLines = flag;
0455: notifyListeners(new RendererChangeEvent(this ));
0456: }
0457: }
0458:
0459: /**
0460: * Returns the gap threshold type (relative or absolute).
0461: *
0462: * @return The type.
0463: *
0464: * @see #setGapThresholdType(UnitType)
0465: */
0466: public UnitType getGapThresholdType() {
0467: return this .gapThresholdType;
0468: }
0469:
0470: /**
0471: * Sets the gap threshold type.
0472: *
0473: * @param thresholdType the type (<code>null</code> not permitted).
0474: *
0475: * @see #getGapThresholdType()
0476: */
0477: public void setGapThresholdType(UnitType thresholdType) {
0478: if (thresholdType == null) {
0479: throw new IllegalArgumentException(
0480: "Null 'thresholdType' argument.");
0481: }
0482: this .gapThresholdType = thresholdType;
0483: notifyListeners(new RendererChangeEvent(this ));
0484: }
0485:
0486: /**
0487: * Returns the gap threshold for discontinuous lines.
0488: *
0489: * @return The gap threshold.
0490: *
0491: * @see #setGapThreshold(double)
0492: */
0493: public double getGapThreshold() {
0494: return this .gapThreshold;
0495: }
0496:
0497: /**
0498: * Sets the gap threshold for discontinuous lines.
0499: *
0500: * @param t the threshold.
0501: *
0502: * @see #getGapThreshold()
0503: */
0504: public void setGapThreshold(double t) {
0505: this .gapThreshold = t;
0506: notifyListeners(new RendererChangeEvent(this ));
0507: }
0508:
0509: /**
0510: * Returns true if images are being plotted by the renderer.
0511: *
0512: * @return <code>true</code> if images are being plotted by the renderer.
0513: *
0514: * @see #setPlotImages(boolean)
0515: */
0516: public boolean getPlotImages() {
0517: return this .plotImages;
0518: }
0519:
0520: /**
0521: * Sets the flag that controls whether or not an image is drawn at each
0522: * data point.
0523: *
0524: * @param flag the flag.
0525: *
0526: * @see #getPlotImages()
0527: */
0528: public void setPlotImages(boolean flag) {
0529: if (this .plotImages != flag) {
0530: this .plotImages = flag;
0531: notifyListeners(new RendererChangeEvent(this ));
0532: }
0533: }
0534:
0535: /**
0536: * Returns a flag that controls whether or not the renderer shows
0537: * discontinuous lines.
0538: *
0539: * @return <code>true</code> if lines should be discontinuous.
0540: */
0541: public boolean getPlotDiscontinuous() {
0542: return this .plotDiscontinuous;
0543: }
0544:
0545: /**
0546: * Sets the flag that controls whether or not the renderer shows
0547: * discontinuous lines, and sends a {@link RendererChangeEvent} to all
0548: * registered listeners.
0549: *
0550: * @param flag the new flag value.
0551: *
0552: * @since 1.0.5
0553: */
0554: public void setPlotDiscontinuous(boolean flag) {
0555: if (this .plotDiscontinuous != flag) {
0556: this .plotDiscontinuous = flag;
0557: fireChangeEvent();
0558: }
0559: }
0560:
0561: /**
0562: * Returns a flag that controls whether or not each series is drawn as a
0563: * single path.
0564: *
0565: * @return A boolean.
0566: *
0567: * @see #setDrawSeriesLineAsPath(boolean)
0568: */
0569: public boolean getDrawSeriesLineAsPath() {
0570: return this .drawSeriesLineAsPath;
0571: }
0572:
0573: /**
0574: * Sets the flag that controls whether or not each series is drawn as a
0575: * single path.
0576: *
0577: * @param flag the flag.
0578: *
0579: * @see #getDrawSeriesLineAsPath()
0580: */
0581: public void setDrawSeriesLineAsPath(boolean flag) {
0582: this .drawSeriesLineAsPath = flag;
0583: }
0584:
0585: /**
0586: * Returns the shape used to represent a line in the legend.
0587: *
0588: * @return The legend line (never <code>null</code>).
0589: *
0590: * @see #setLegendLine(Shape)
0591: */
0592: public Shape getLegendLine() {
0593: return this .legendLine;
0594: }
0595:
0596: /**
0597: * Sets the shape used as a line in each legend item and sends a
0598: * {@link RendererChangeEvent} to all registered listeners.
0599: *
0600: * @param line the line (<code>null</code> not permitted).
0601: *
0602: * @see #getLegendLine()
0603: */
0604: public void setLegendLine(Shape line) {
0605: if (line == null) {
0606: throw new IllegalArgumentException("Null 'line' argument.");
0607: }
0608: this .legendLine = line;
0609: notifyListeners(new RendererChangeEvent(this ));
0610: }
0611:
0612: /**
0613: * Returns a legend item for a series.
0614: *
0615: * @param datasetIndex the dataset index (zero-based).
0616: * @param series the series index (zero-based).
0617: *
0618: * @return A legend item for the series.
0619: */
0620: public LegendItem getLegendItem(int datasetIndex, int series) {
0621: XYPlot plot = getPlot();
0622: if (plot == null) {
0623: return null;
0624: }
0625: LegendItem result = null;
0626: XYDataset dataset = plot.getDataset(datasetIndex);
0627: if (dataset != null) {
0628: if (getItemVisible(series, 0)) {
0629: String label = getLegendItemLabelGenerator()
0630: .generateLabel(dataset, series);
0631: String description = label;
0632: String toolTipText = null;
0633: if (getLegendItemToolTipGenerator() != null) {
0634: toolTipText = getLegendItemToolTipGenerator()
0635: .generateLabel(dataset, series);
0636: }
0637: String urlText = null;
0638: if (getLegendItemURLGenerator() != null) {
0639: urlText = getLegendItemURLGenerator()
0640: .generateLabel(dataset, series);
0641: }
0642: Shape shape = lookupSeriesShape(series);
0643: boolean shapeFilled = getItemShapeFilled(series, 0);
0644: Paint paint = lookupSeriesPaint(series);
0645: Paint linePaint = paint;
0646: Stroke lineStroke = lookupSeriesStroke(series);
0647: result = new LegendItem(label, description,
0648: toolTipText, urlText, this .baseShapesVisible,
0649: shape, shapeFilled, paint, !shapeFilled, paint,
0650: lineStroke, this .plotLines, this .legendLine,
0651: lineStroke, linePaint);
0652: result.setDataset(dataset);
0653: result.setDatasetIndex(datasetIndex);
0654: result.setSeriesKey(dataset.getSeriesKey(series));
0655: result.setSeriesIndex(series);
0656: }
0657: }
0658: return result;
0659: }
0660:
0661: /**
0662: * Records the state for the renderer. This is used to preserve state
0663: * information between calls to the drawItem() method for a single chart
0664: * drawing.
0665: */
0666: public static class State extends XYItemRendererState {
0667:
0668: /** The path for the current series. */
0669: public GeneralPath seriesPath;
0670:
0671: /** The series index. */
0672: private int seriesIndex;
0673:
0674: /**
0675: * A flag that indicates if the last (x, y) point was 'good'
0676: * (non-null).
0677: */
0678: private boolean lastPointGood;
0679:
0680: /**
0681: * Creates a new state instance.
0682: *
0683: * @param info the plot rendering info.
0684: */
0685: public State(PlotRenderingInfo info) {
0686: super (info);
0687: }
0688:
0689: /**
0690: * Returns a flag that indicates if the last point drawn (in the
0691: * current series) was 'good' (non-null).
0692: *
0693: * @return A boolean.
0694: */
0695: public boolean isLastPointGood() {
0696: return this .lastPointGood;
0697: }
0698:
0699: /**
0700: * Sets a flag that indicates if the last point drawn (in the current
0701: * series) was 'good' (non-null).
0702: *
0703: * @param good the flag.
0704: */
0705: public void setLastPointGood(boolean good) {
0706: this .lastPointGood = good;
0707: }
0708:
0709: /**
0710: * Returns the series index for the current path.
0711: *
0712: * @return The series index for the current path.
0713: */
0714: public int getSeriesIndex() {
0715: return this .seriesIndex;
0716: }
0717:
0718: /**
0719: * Sets the series index for the current path.
0720: *
0721: * @param index the index.
0722: */
0723: public void setSeriesIndex(int index) {
0724: this .seriesIndex = index;
0725: }
0726: }
0727:
0728: /**
0729: * Initialises the renderer.
0730: * <P>
0731: * This method will be called before the first item is rendered, giving the
0732: * renderer an opportunity to initialise any state information it wants to
0733: * maintain. The renderer can do nothing if it chooses.
0734: *
0735: * @param g2 the graphics device.
0736: * @param dataArea the area inside the axes.
0737: * @param plot the plot.
0738: * @param data the data.
0739: * @param info an optional info collection object to return data back to
0740: * the caller.
0741: *
0742: * @return The renderer state.
0743: */
0744: public XYItemRendererState initialise(Graphics2D g2,
0745: Rectangle2D dataArea, XYPlot plot, XYDataset data,
0746: PlotRenderingInfo info) {
0747:
0748: State state = new State(info);
0749: state.seriesPath = new GeneralPath();
0750: state.seriesIndex = -1;
0751: return state;
0752:
0753: }
0754:
0755: /**
0756: * Draws the visual representation of a single data item.
0757: *
0758: * @param g2 the graphics device.
0759: * @param state the renderer state.
0760: * @param dataArea the area within which the data is being drawn.
0761: * @param info collects information about the drawing.
0762: * @param plot the plot (can be used to obtain standard color information
0763: * etc).
0764: * @param domainAxis the domain axis.
0765: * @param rangeAxis the range axis.
0766: * @param dataset the dataset.
0767: * @param series the series index (zero-based).
0768: * @param item the item index (zero-based).
0769: * @param crosshairState crosshair information for the plot
0770: * (<code>null</code> permitted).
0771: * @param pass the pass index.
0772: */
0773: public void drawItem(Graphics2D g2, XYItemRendererState state,
0774: Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
0775: ValueAxis domainAxis, ValueAxis rangeAxis,
0776: XYDataset dataset, int series, int item,
0777: CrosshairState crosshairState, int pass) {
0778:
0779: boolean itemVisible = getItemVisible(series, item);
0780:
0781: // setup for collecting optional entity info...
0782: Shape entityArea = null;
0783: EntityCollection entities = null;
0784: if (info != null) {
0785: entities = info.getOwner().getEntityCollection();
0786: }
0787:
0788: PlotOrientation orientation = plot.getOrientation();
0789: Paint paint = getItemPaint(series, item);
0790: Stroke seriesStroke = getItemStroke(series, item);
0791: g2.setPaint(paint);
0792: g2.setStroke(seriesStroke);
0793:
0794: // get the data point...
0795: double x1 = dataset.getXValue(series, item);
0796: double y1 = dataset.getYValue(series, item);
0797: if (Double.isNaN(x1) || Double.isNaN(y1)) {
0798: itemVisible = false;
0799: }
0800:
0801: RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
0802: RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
0803: double transX1 = domainAxis.valueToJava2D(x1, dataArea,
0804: xAxisLocation);
0805: double transY1 = rangeAxis.valueToJava2D(y1, dataArea,
0806: yAxisLocation);
0807:
0808: if (getPlotLines()) {
0809: if (this .drawSeriesLineAsPath) {
0810: State s = (State) state;
0811: if (s.getSeriesIndex() != series) {
0812: // we are starting a new series path
0813: s.seriesPath.reset();
0814: s.lastPointGood = false;
0815: s.setSeriesIndex(series);
0816: }
0817:
0818: // update path to reflect latest point
0819: if (itemVisible && !Double.isNaN(transX1)
0820: && !Double.isNaN(transY1)) {
0821: float x = (float) transX1;
0822: float y = (float) transY1;
0823: if (orientation == PlotOrientation.HORIZONTAL) {
0824: x = (float) transY1;
0825: y = (float) transX1;
0826: }
0827: if (s.isLastPointGood()) {
0828: // TODO: check threshold
0829: s.seriesPath.lineTo(x, y);
0830: } else {
0831: s.seriesPath.moveTo(x, y);
0832: }
0833: s.setLastPointGood(true);
0834: } else {
0835: s.setLastPointGood(false);
0836: }
0837: if (item == dataset.getItemCount(series) - 1) {
0838: if (s.seriesIndex == series) {
0839: // draw path
0840: g2.setStroke(lookupSeriesStroke(series));
0841: g2.setPaint(lookupSeriesPaint(series));
0842: g2.draw(s.seriesPath);
0843: }
0844: }
0845: }
0846:
0847: else if (item != 0 && itemVisible) {
0848: // get the previous data point...
0849: double x0 = dataset.getXValue(series, item - 1);
0850: double y0 = dataset.getYValue(series, item - 1);
0851: if (!Double.isNaN(x0) && !Double.isNaN(y0)) {
0852: boolean drawLine = true;
0853: if (getPlotDiscontinuous()) {
0854: // only draw a line if the gap between the current and
0855: // previous data point is within the threshold
0856: int numX = dataset.getItemCount(series);
0857: double minX = dataset.getXValue(series, 0);
0858: double maxX = dataset.getXValue(series,
0859: numX - 1);
0860: if (this .gapThresholdType == UnitType.ABSOLUTE) {
0861: drawLine = Math.abs(x1 - x0) <= this .gapThreshold;
0862: } else {
0863: drawLine = Math.abs(x1 - x0) <= ((maxX - minX)
0864: / numX * getGapThreshold());
0865: }
0866: }
0867: if (drawLine) {
0868: double transX0 = domainAxis.valueToJava2D(x0,
0869: dataArea, xAxisLocation);
0870: double transY0 = rangeAxis.valueToJava2D(y0,
0871: dataArea, yAxisLocation);
0872:
0873: // only draw if we have good values
0874: if (Double.isNaN(transX0)
0875: || Double.isNaN(transY0)
0876: || Double.isNaN(transX1)
0877: || Double.isNaN(transY1)) {
0878: return;
0879: }
0880:
0881: if (orientation == PlotOrientation.HORIZONTAL) {
0882: state.workingLine.setLine(transY0, transX0,
0883: transY1, transX1);
0884: } else if (orientation == PlotOrientation.VERTICAL) {
0885: state.workingLine.setLine(transX0, transY0,
0886: transX1, transY1);
0887: }
0888:
0889: if (state.workingLine.intersects(dataArea)) {
0890: g2.draw(state.workingLine);
0891: }
0892: }
0893: }
0894: }
0895: }
0896:
0897: // we needed to get this far even for invisible items, to ensure that
0898: // seriesPath updates happened, but now there is nothing more we need
0899: // to do for non-visible items...
0900: if (!itemVisible) {
0901: return;
0902: }
0903:
0904: if (getBaseShapesVisible()) {
0905:
0906: Shape shape = getItemShape(series, item);
0907: if (orientation == PlotOrientation.HORIZONTAL) {
0908: shape = ShapeUtilities.createTranslatedShape(shape,
0909: transY1, transX1);
0910: } else if (orientation == PlotOrientation.VERTICAL) {
0911: shape = ShapeUtilities.createTranslatedShape(shape,
0912: transX1, transY1);
0913: }
0914: if (shape.intersects(dataArea)) {
0915: if (getItemShapeFilled(series, item)) {
0916: g2.fill(shape);
0917: } else {
0918: g2.draw(shape);
0919: }
0920: }
0921: entityArea = shape;
0922:
0923: }
0924:
0925: if (getPlotImages()) {
0926: Image image = getImage(plot, series, item, transX1, transY1);
0927: if (image != null) {
0928: Point hotspot = getImageHotspot(plot, series, item,
0929: transX1, transY1, image);
0930: g2.drawImage(image, (int) (transX1 - hotspot.getX()),
0931: (int) (transY1 - hotspot.getY()), null);
0932: entityArea = new Rectangle2D.Double(transX1
0933: - hotspot.getX(), transY1 - hotspot.getY(),
0934: image.getWidth(null), image.getHeight(null));
0935: }
0936:
0937: }
0938:
0939: double xx = transX1;
0940: double yy = transY1;
0941: if (orientation == PlotOrientation.HORIZONTAL) {
0942: xx = transY1;
0943: yy = transX1;
0944: }
0945:
0946: // draw the item label if there is one...
0947: if (isItemLabelVisible(series, item)) {
0948: drawItemLabel(g2, orientation, dataset, series, item, xx,
0949: yy, (y1 < 0.0));
0950: }
0951:
0952: int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
0953: int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
0954: updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex,
0955: rangeAxisIndex, transX1, transY1, orientation);
0956:
0957: // add an entity for the item...
0958: if (entities != null && dataArea.contains(xx, yy)) {
0959: addEntity(entities, entityArea, dataset, series, item, xx,
0960: yy);
0961: }
0962:
0963: }
0964:
0965: /**
0966: * Tests this renderer for equality with another object.
0967: *
0968: * @param obj the object (<code>null</code> permitted).
0969: *
0970: * @return A boolean.
0971: */
0972: public boolean equals(Object obj) {
0973:
0974: if (obj == this ) {
0975: return true;
0976: }
0977: if (!(obj instanceof StandardXYItemRenderer)) {
0978: return false;
0979: }
0980: StandardXYItemRenderer that = (StandardXYItemRenderer) obj;
0981: if (this .baseShapesVisible != that.baseShapesVisible) {
0982: return false;
0983: }
0984: if (this .plotLines != that.plotLines) {
0985: return false;
0986: }
0987: if (this .plotImages != that.plotImages) {
0988: return false;
0989: }
0990: if (this .plotDiscontinuous != that.plotDiscontinuous) {
0991: return false;
0992: }
0993: if (this .gapThresholdType != that.gapThresholdType) {
0994: return false;
0995: }
0996: if (this .gapThreshold != that.gapThreshold) {
0997: return false;
0998: }
0999: if (!ObjectUtilities
1000: .equal(this .shapesFilled, that.shapesFilled)) {
1001: return false;
1002: }
1003: if (!this .seriesShapesFilled.equals(that.seriesShapesFilled)) {
1004: return false;
1005: }
1006: if (this .baseShapesFilled != that.baseShapesFilled) {
1007: return false;
1008: }
1009: if (this .drawSeriesLineAsPath != that.drawSeriesLineAsPath) {
1010: return false;
1011: }
1012: if (!ShapeUtilities.equal(this .legendLine, that.legendLine)) {
1013: return false;
1014: }
1015: return super .equals(obj);
1016:
1017: }
1018:
1019: /**
1020: * Returns a clone of the renderer.
1021: *
1022: * @return A clone.
1023: *
1024: * @throws CloneNotSupportedException if the renderer cannot be cloned.
1025: */
1026: public Object clone() throws CloneNotSupportedException {
1027: StandardXYItemRenderer clone = (StandardXYItemRenderer) super
1028: .clone();
1029: clone.seriesShapesFilled = (BooleanList) this .seriesShapesFilled
1030: .clone();
1031: clone.legendLine = ShapeUtilities.clone(this .legendLine);
1032: return clone;
1033: }
1034:
1035: ////////////////////////////////////////////////////////////////////////////
1036: // PROTECTED METHODS
1037: // These provide the opportunity to subclass the standard renderer and
1038: // create custom effects.
1039: ////////////////////////////////////////////////////////////////////////////
1040:
1041: /**
1042: * Returns the image used to draw a single data item.
1043: *
1044: * @param plot the plot (can be used to obtain standard color information
1045: * etc).
1046: * @param series the series index.
1047: * @param item the item index.
1048: * @param x the x value of the item.
1049: * @param y the y value of the item.
1050: *
1051: * @return The image.
1052: *
1053: * @see #getPlotImages()
1054: */
1055: protected Image getImage(Plot plot, int series, int item, double x,
1056: double y) {
1057: // this method must be overridden if you want to display images
1058: return null;
1059: }
1060:
1061: /**
1062: * Returns the hotspot of the image used to draw a single data item.
1063: * The hotspot is the point relative to the top left of the image
1064: * that should indicate the data item. The default is the center of the
1065: * image.
1066: *
1067: * @param plot the plot (can be used to obtain standard color information
1068: * etc).
1069: * @param image the image (can be used to get size information about the
1070: * image)
1071: * @param series the series index
1072: * @param item the item index
1073: * @param x the x value of the item
1074: * @param y the y value of the item
1075: *
1076: * @return The hotspot used to draw the data item.
1077: */
1078: protected Point getImageHotspot(Plot plot, int series, int item,
1079: double x, double y, Image image) {
1080:
1081: int height = image.getHeight(null);
1082: int width = image.getWidth(null);
1083: return new Point(width / 2, height / 2);
1084:
1085: }
1086:
1087: /**
1088: * Provides serialization support.
1089: *
1090: * @param stream the input stream.
1091: *
1092: * @throws IOException if there is an I/O error.
1093: * @throws ClassNotFoundException if there is a classpath problem.
1094: */
1095: private void readObject(ObjectInputStream stream)
1096: throws IOException, ClassNotFoundException {
1097: stream.defaultReadObject();
1098: this .legendLine = SerialUtilities.readShape(stream);
1099: }
1100:
1101: /**
1102: * Provides serialization support.
1103: *
1104: * @param stream the output stream.
1105: *
1106: * @throws IOException if there is an I/O error.
1107: */
1108: private void writeObject(ObjectOutputStream stream)
1109: throws IOException {
1110: stream.defaultWriteObject();
1111: SerialUtilities.writeShape(this.legendLine, stream);
1112: }
1113:
1114: }
|