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: * XYDifferenceRenderer.java
0029: * -------------------------
0030: * (C) Copyright 2003-2007, by Object Refinery Limited and Contributors.
0031: *
0032: * Original Author: David Gilbert (for Object Refinery Limited);
0033: * Contributor(s): Richard West, Advanced Micro Devices, Inc. (major rewrite
0034: * of difference drawing algorithm);
0035: *
0036: * $Id: XYDifferenceRenderer.java,v 1.12.2.15 2007/05/18 10:28:31 mungady Exp $
0037: *
0038: * Changes:
0039: * --------
0040: * 30-Apr-2003 : Version 1 (DG);
0041: * 30-Jul-2003 : Modified entity constructor (CZ);
0042: * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
0043: * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
0044: * 09-Feb-2004 : Updated to support horizontal plot orientation (DG);
0045: * 10-Feb-2004 : Added default constructor, setter methods and updated
0046: * Javadocs (DG);
0047: * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
0048: * 30-Mar-2004 : Fixed bug in getNegativePaint() method (DG);
0049: * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
0050: * getYValue() (DG);
0051: * 25-Aug-2004 : Fixed a bug preventing the use of crosshairs (DG);
0052: * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
0053: * 19-Jan-2005 : Now accesses only primitive values from dataset (DG);
0054: * 22-Feb-2005 : Override getLegendItem(int, int) to return "line" items (DG);
0055: * 13-Apr-2005 : Fixed shape positioning bug (id = 1182062) (DG);
0056: * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
0057: * 04-May-2005 : Override equals() method, renamed get/setPlotShapes() -->
0058: * get/setShapesVisible (DG);
0059: * 09-Jun-2005 : Updated equals() to handle GradientPaint (DG);
0060: * 16-Jun-2005 : Fix bug (1221021) affecting stroke used for each series (DG);
0061: * ------------- JFREECHART 1.0.x ---------------------------------------------
0062: * 24-Jan-2007 : Added flag to allow rounding of x-coordinates, and fixed
0063: * bug in clone() (DG);
0064: * 05-Feb-2007 : Added an extra call to updateCrosshairValues() in
0065: * drawItemPass1(), to fix bug 1564967 (DG);
0066: * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
0067: * 08-Mar-2007 : Fixed entity generation (DG);
0068: * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
0069: * 23-Apr-2007 : Rewrite of difference drawing algorithm to allow use of
0070: * series with disjoint x-values (RW);
0071: * 04-May-2007 : Set processVisibleItemsOnly flag to false (DG);
0072: * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG);
0073: * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
0074: *
0075: */
0076:
0077: package org.jfree.chart.renderer.xy;
0078:
0079: import java.awt.Color;
0080: import java.awt.Graphics2D;
0081: import java.awt.Paint;
0082: import java.awt.Shape;
0083: import java.awt.Stroke;
0084: import java.awt.geom.GeneralPath;
0085: import java.awt.geom.Line2D;
0086: import java.awt.geom.Rectangle2D;
0087: import java.io.IOException;
0088: import java.io.ObjectInputStream;
0089: import java.io.ObjectOutputStream;
0090: import java.io.Serializable;
0091: import java.util.Collections;
0092: import java.util.LinkedList;
0093:
0094: import org.jfree.chart.LegendItem;
0095: import org.jfree.chart.axis.ValueAxis;
0096: import org.jfree.chart.entity.EntityCollection;
0097: import org.jfree.chart.entity.XYItemEntity;
0098: import org.jfree.chart.event.RendererChangeEvent;
0099: import org.jfree.chart.labels.XYToolTipGenerator;
0100: import org.jfree.chart.plot.CrosshairState;
0101: import org.jfree.chart.plot.PlotOrientation;
0102: import org.jfree.chart.plot.PlotRenderingInfo;
0103: import org.jfree.chart.plot.XYPlot;
0104: import org.jfree.chart.urls.XYURLGenerator;
0105: import org.jfree.data.xy.XYDataset;
0106: import org.jfree.io.SerialUtilities;
0107: import org.jfree.ui.RectangleEdge;
0108: import org.jfree.util.PaintUtilities;
0109: import org.jfree.util.PublicCloneable;
0110: import org.jfree.util.ShapeUtilities;
0111:
0112: /**
0113: * A renderer for an {@link XYPlot} that highlights the differences between two
0114: * series.
0115: */
0116: public class XYDifferenceRenderer extends AbstractXYItemRenderer
0117: implements XYItemRenderer, Cloneable, PublicCloneable,
0118: Serializable {
0119:
0120: /** For serialization. */
0121: private static final long serialVersionUID = -8447915602375584857L;
0122:
0123: /** The paint used to highlight positive differences (y(0) > y(1)). */
0124: private transient Paint positivePaint;
0125:
0126: /** The paint used to highlight negative differences (y(0) < y(1)). */
0127: private transient Paint negativePaint;
0128:
0129: /** Display shapes at each point? */
0130: private boolean shapesVisible;
0131:
0132: /** The shape to display in the legend item. */
0133: private transient Shape legendLine;
0134:
0135: /**
0136: * This flag controls whether or not the x-coordinates (in Java2D space)
0137: * are rounded to integers. When set to true, this can avoid the vertical
0138: * striping that anti-aliasing can generate. However, the rounding may not
0139: * be appropriate for output in high resolution formats (for example,
0140: * vector graphics formats such as SVG and PDF).
0141: *
0142: * @since 1.0.4
0143: */
0144: private boolean roundXCoordinates;
0145:
0146: /**
0147: * Creates a new renderer with default attributes.
0148: */
0149: public XYDifferenceRenderer() {
0150: this (Color.green, Color.red, false);
0151: }
0152:
0153: /**
0154: * Creates a new renderer.
0155: *
0156: * @param positivePaint the highlight color for positive differences
0157: * (<code>null</code> not permitted).
0158: * @param negativePaint the highlight color for negative differences
0159: * (<code>null</code> not permitted).
0160: * @param shapes draw shapes?
0161: */
0162: public XYDifferenceRenderer(Paint positivePaint,
0163: Paint negativePaint, boolean shapes) {
0164: if (positivePaint == null) {
0165: throw new IllegalArgumentException(
0166: "Null 'positivePaint' argument.");
0167: }
0168: if (negativePaint == null) {
0169: throw new IllegalArgumentException(
0170: "Null 'negativePaint' argument.");
0171: }
0172: this .positivePaint = positivePaint;
0173: this .negativePaint = negativePaint;
0174: this .shapesVisible = shapes;
0175: this .legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
0176: this .roundXCoordinates = false;
0177: }
0178:
0179: /**
0180: * Returns the paint used to highlight positive differences.
0181: *
0182: * @return The paint (never <code>null</code>).
0183: *
0184: * @see #setPositivePaint(Paint)
0185: */
0186: public Paint getPositivePaint() {
0187: return this .positivePaint;
0188: }
0189:
0190: /**
0191: * Sets the paint used to highlight positive differences.
0192: *
0193: * @param paint the paint (<code>null</code> not permitted).
0194: *
0195: * @see #getPositivePaint()
0196: */
0197: public void setPositivePaint(Paint paint) {
0198: if (paint == null) {
0199: throw new IllegalArgumentException("Null 'paint' argument.");
0200: }
0201: this .positivePaint = paint;
0202: notifyListeners(new RendererChangeEvent(this ));
0203: }
0204:
0205: /**
0206: * Returns the paint used to highlight negative differences.
0207: *
0208: * @return The paint (never <code>null</code>).
0209: *
0210: * @see #setNegativePaint(Paint)
0211: */
0212: public Paint getNegativePaint() {
0213: return this .negativePaint;
0214: }
0215:
0216: /**
0217: * Sets the paint used to highlight negative differences.
0218: *
0219: * @param paint the paint (<code>null</code> not permitted).
0220: *
0221: * @see #getNegativePaint()
0222: */
0223: public void setNegativePaint(Paint paint) {
0224: if (paint == null) {
0225: throw new IllegalArgumentException("Null 'paint' argument.");
0226: }
0227: this .negativePaint = paint;
0228: notifyListeners(new RendererChangeEvent(this ));
0229: }
0230:
0231: /**
0232: * Returns a flag that controls whether or not shapes are drawn for each
0233: * data value.
0234: *
0235: * @return A boolean.
0236: *
0237: * @see #setShapesVisible(boolean)
0238: */
0239: public boolean getShapesVisible() {
0240: return this .shapesVisible;
0241: }
0242:
0243: /**
0244: * Sets a flag that controls whether or not shapes are drawn for each
0245: * data value.
0246: *
0247: * @param flag the flag.
0248: *
0249: * @see #getShapesVisible()
0250: */
0251: public void setShapesVisible(boolean flag) {
0252: this .shapesVisible = flag;
0253: notifyListeners(new RendererChangeEvent(this ));
0254: }
0255:
0256: /**
0257: * Returns the shape used to represent a line in the legend.
0258: *
0259: * @return The legend line (never <code>null</code>).
0260: *
0261: * @see #setLegendLine(Shape)
0262: */
0263: public Shape getLegendLine() {
0264: return this .legendLine;
0265: }
0266:
0267: /**
0268: * Sets the shape used as a line in each legend item and sends a
0269: * {@link RendererChangeEvent} to all registered listeners.
0270: *
0271: * @param line the line (<code>null</code> not permitted).
0272: *
0273: * @see #getLegendLine()
0274: */
0275: public void setLegendLine(Shape line) {
0276: if (line == null) {
0277: throw new IllegalArgumentException("Null 'line' argument.");
0278: }
0279: this .legendLine = line;
0280: notifyListeners(new RendererChangeEvent(this ));
0281: }
0282:
0283: /**
0284: * Returns the flag that controls whether or not the x-coordinates (in
0285: * Java2D space) are rounded to integer values.
0286: *
0287: * @return The flag.
0288: *
0289: * @since 1.0.4
0290: *
0291: * @see #setRoundXCoordinates(boolean)
0292: */
0293: public boolean getRoundXCoordinates() {
0294: return this .roundXCoordinates;
0295: }
0296:
0297: /**
0298: * Sets the flag that controls whether or not the x-coordinates (in
0299: * Java2D space) are rounded to integer values, and sends a
0300: * {@link RendererChangeEvent} to all registered listeners.
0301: *
0302: * @param round the new flag value.
0303: *
0304: * @since 1.0.4
0305: *
0306: * @see #getRoundXCoordinates()
0307: */
0308: public void setRoundXCoordinates(boolean round) {
0309: this .roundXCoordinates = round;
0310: notifyListeners(new RendererChangeEvent(this ));
0311: }
0312:
0313: /**
0314: * Initialises the renderer and returns a state object that should be
0315: * passed to subsequent calls to the drawItem() method. This method will
0316: * be called before the first item is rendered, giving the renderer an
0317: * opportunity to initialise any state information it wants to maintain.
0318: * The renderer can do nothing if it chooses.
0319: *
0320: * @param g2 the graphics device.
0321: * @param dataArea the area inside the axes.
0322: * @param plot the plot.
0323: * @param data the data.
0324: * @param info an optional info collection object to return data back to
0325: * the caller.
0326: *
0327: * @return A state object.
0328: */
0329: public XYItemRendererState initialise(Graphics2D g2,
0330: Rectangle2D dataArea, XYPlot plot, XYDataset data,
0331: PlotRenderingInfo info) {
0332:
0333: XYItemRendererState state = super .initialise(g2, dataArea,
0334: plot, data, info);
0335: state.setProcessVisibleItemsOnly(false);
0336: return state;
0337:
0338: }
0339:
0340: /**
0341: * Returns <code>2</code>, the number of passes required by the renderer.
0342: * The {@link XYPlot} will run through the dataset this number of times.
0343: *
0344: * @return The number of passes required by the renderer.
0345: */
0346: public int getPassCount() {
0347: return 2;
0348: }
0349:
0350: /**
0351: * Draws the visual representation of a single data item.
0352: *
0353: * @param g2 the graphics device.
0354: * @param state the renderer state.
0355: * @param dataArea the area within which the data is being drawn.
0356: * @param info collects information about the drawing.
0357: * @param plot the plot (can be used to obtain standard color
0358: * information etc).
0359: * @param domainAxis the domain (horizontal) axis.
0360: * @param rangeAxis the range (vertical) axis.
0361: * @param dataset the dataset.
0362: * @param series the series index (zero-based).
0363: * @param item the item index (zero-based).
0364: * @param crosshairState crosshair information for the plot
0365: * (<code>null</code> permitted).
0366: * @param pass the pass index.
0367: */
0368: public void drawItem(Graphics2D g2, XYItemRendererState state,
0369: Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
0370: ValueAxis domainAxis, ValueAxis rangeAxis,
0371: XYDataset dataset, int series, int item,
0372: CrosshairState crosshairState, int pass) {
0373:
0374: if (pass == 0) {
0375: drawItemPass0(g2, dataArea, info, plot, domainAxis,
0376: rangeAxis, dataset, series, item, crosshairState);
0377: } else if (pass == 1) {
0378: drawItemPass1(g2, dataArea, info, plot, domainAxis,
0379: rangeAxis, dataset, series, item, crosshairState);
0380: }
0381:
0382: }
0383:
0384: /**
0385: * Draws the visual representation of a single data item, first pass.
0386: *
0387: * @param x_graphics the graphics device.
0388: * @param x_dataArea the area within which the data is being drawn.
0389: * @param x_info collects information about the drawing.
0390: * @param x_plot the plot (can be used to obtain standard color
0391: * information etc).
0392: * @param x_domainAxis the domain (horizontal) axis.
0393: * @param x_rangeAxis the range (vertical) axis.
0394: * @param x_dataset the dataset.
0395: * @param x_series the series index (zero-based).
0396: * @param x_item the item index (zero-based).
0397: * @param x_crosshairState crosshair information for the plot
0398: * (<code>null</code> permitted).
0399: */
0400: protected void drawItemPass0(Graphics2D x_graphics,
0401: Rectangle2D x_dataArea, PlotRenderingInfo x_info,
0402: XYPlot x_plot, ValueAxis x_domainAxis,
0403: ValueAxis x_rangeAxis, XYDataset x_dataset, int x_series,
0404: int x_item, CrosshairState x_crosshairState) {
0405:
0406: if (!((0 == x_series) && (0 == x_item))) {
0407: return;
0408: }
0409:
0410: boolean b_impliedZeroSubtrahend = (1 == x_dataset
0411: .getSeriesCount());
0412:
0413: // check if either series is a degenerate case (i.e. less than 2 points)
0414: if (isEitherSeriesDegenerate(x_dataset, b_impliedZeroSubtrahend)) {
0415: return;
0416: }
0417:
0418: // check if series are disjoint (i.e. domain-spans do not overlap)
0419: if (!b_impliedZeroSubtrahend && areSeriesDisjoint(x_dataset)) {
0420: return;
0421: }
0422:
0423: // polygon definitions
0424: LinkedList l_minuendXs = new LinkedList();
0425: LinkedList l_minuendYs = new LinkedList();
0426: LinkedList l_subtrahendXs = new LinkedList();
0427: LinkedList l_subtrahendYs = new LinkedList();
0428: LinkedList l_polygonXs = new LinkedList();
0429: LinkedList l_polygonYs = new LinkedList();
0430:
0431: // state
0432: int l_minuendItem = 0;
0433: int l_minuendItemCount = x_dataset.getItemCount(0);
0434: Double l_minuendCurX = null;
0435: Double l_minuendNextX = null;
0436: Double l_minuendCurY = null;
0437: Double l_minuendNextY = null;
0438: double l_minuendMaxY = Double.NEGATIVE_INFINITY;
0439: double l_minuendMinY = Double.POSITIVE_INFINITY;
0440:
0441: int l_subtrahendItem = 0;
0442: int l_subtrahendItemCount = 0; // actual value set below
0443: Double l_subtrahendCurX = null;
0444: Double l_subtrahendNextX = null;
0445: Double l_subtrahendCurY = null;
0446: Double l_subtrahendNextY = null;
0447: double l_subtrahendMaxY = Double.NEGATIVE_INFINITY;
0448: double l_subtrahendMinY = Double.POSITIVE_INFINITY;
0449:
0450: // if a subtrahend is not specified, assume it is zero
0451: if (b_impliedZeroSubtrahend) {
0452: l_subtrahendItem = 0;
0453: l_subtrahendItemCount = 2;
0454: l_subtrahendCurX = new Double(x_dataset.getXValue(0, 0));
0455: l_subtrahendNextX = new Double(x_dataset.getXValue(0,
0456: (l_minuendItemCount - 1)));
0457: l_subtrahendCurY = new Double(0.0);
0458: l_subtrahendNextY = new Double(0.0);
0459: l_subtrahendMaxY = 0.0;
0460: l_subtrahendMinY = 0.0;
0461:
0462: l_subtrahendXs.add(l_subtrahendCurX);
0463: l_subtrahendYs.add(l_subtrahendCurY);
0464: } else {
0465: l_subtrahendItemCount = x_dataset.getItemCount(1);
0466: }
0467:
0468: boolean b_minuendDone = false;
0469: boolean b_minuendAdvanced = true;
0470: boolean b_minuendAtIntersect = false;
0471: boolean b_minuendFastForward = false;
0472: boolean b_subtrahendDone = false;
0473: boolean b_subtrahendAdvanced = true;
0474: boolean b_subtrahendAtIntersect = false;
0475: boolean b_subtrahendFastForward = false;
0476: boolean b_colinear = false;
0477:
0478: boolean b_positive;
0479:
0480: // coordinate pairs
0481: double l_x1 = 0.0, l_y1 = 0.0; // current minuend point
0482: double l_x2 = 0.0, l_y2 = 0.0; // next minuend point
0483: double l_x3 = 0.0, l_y3 = 0.0; // current subtrahend point
0484: double l_x4 = 0.0, l_y4 = 0.0; // next subtrahend point
0485:
0486: // fast-forward through leading tails
0487: boolean b_fastForwardDone = false;
0488: while (!b_fastForwardDone) {
0489: // get the x and y coordinates
0490: l_x1 = x_dataset.getXValue(0, l_minuendItem);
0491: l_y1 = x_dataset.getYValue(0, l_minuendItem);
0492: l_x2 = x_dataset.getXValue(0, l_minuendItem + 1);
0493: l_y2 = x_dataset.getYValue(0, l_minuendItem + 1);
0494:
0495: l_minuendCurX = new Double(l_x1);
0496: l_minuendCurY = new Double(l_y1);
0497: l_minuendNextX = new Double(l_x2);
0498: l_minuendNextY = new Double(l_y2);
0499:
0500: if (b_impliedZeroSubtrahend) {
0501: l_x3 = l_subtrahendCurX.doubleValue();
0502: l_y3 = l_subtrahendCurY.doubleValue();
0503: l_x4 = l_subtrahendNextX.doubleValue();
0504: l_y4 = l_subtrahendNextY.doubleValue();
0505: } else {
0506: l_x3 = x_dataset.getXValue(1, l_subtrahendItem);
0507: l_y3 = x_dataset.getYValue(1, l_subtrahendItem);
0508: l_x4 = x_dataset.getXValue(1, l_subtrahendItem + 1);
0509: l_y4 = x_dataset.getYValue(1, l_subtrahendItem + 1);
0510:
0511: l_subtrahendCurX = new Double(l_x3);
0512: l_subtrahendCurY = new Double(l_y3);
0513: l_subtrahendNextX = new Double(l_x4);
0514: l_subtrahendNextY = new Double(l_y4);
0515: }
0516:
0517: if (l_x2 <= l_x3) {
0518: // minuend needs to be fast forwarded
0519: l_minuendItem++;
0520: b_minuendFastForward = true;
0521: continue;
0522: }
0523:
0524: if (l_x4 <= l_x1) {
0525: // subtrahend needs to be fast forwarded
0526: l_subtrahendItem++;
0527: b_subtrahendFastForward = true;
0528: continue;
0529: }
0530:
0531: // check if initial polygon needs to be clipped
0532: if ((l_x3 < l_x1) && (l_x1 < l_x4)) {
0533: // project onto subtrahend
0534: double l_slope = (l_y4 - l_y3) / (l_x4 - l_x3);
0535: l_subtrahendCurX = l_minuendCurX;
0536: l_subtrahendCurY = new Double((l_slope * l_x1)
0537: + (l_y3 - (l_slope * l_x3)));
0538:
0539: l_subtrahendXs.add(l_subtrahendCurX);
0540: l_subtrahendYs.add(l_subtrahendCurY);
0541: }
0542:
0543: if ((l_x1 < l_x3) && (l_x3 < l_x2)) {
0544: // project onto minuend
0545: double l_slope = (l_y2 - l_y1) / (l_x2 - l_x1);
0546: l_minuendCurX = l_subtrahendCurX;
0547: l_minuendCurY = new Double((l_slope * l_x3)
0548: + (l_y1 - (l_slope * l_x1)));
0549:
0550: l_minuendXs.add(l_minuendCurX);
0551: l_minuendYs.add(l_minuendCurY);
0552: }
0553:
0554: l_minuendMaxY = l_minuendCurY.doubleValue();
0555: l_minuendMinY = l_minuendCurY.doubleValue();
0556: l_subtrahendMaxY = l_subtrahendCurY.doubleValue();
0557: l_subtrahendMinY = l_subtrahendCurY.doubleValue();
0558:
0559: b_fastForwardDone = true;
0560: }
0561:
0562: // start of algorithm
0563: while (!b_minuendDone && !b_subtrahendDone) {
0564: if (!b_minuendDone && !b_minuendFastForward
0565: && b_minuendAdvanced) {
0566: l_x1 = x_dataset.getXValue(0, l_minuendItem);
0567: l_y1 = x_dataset.getYValue(0, l_minuendItem);
0568: l_minuendCurX = new Double(l_x1);
0569: l_minuendCurY = new Double(l_y1);
0570:
0571: if (!b_minuendAtIntersect) {
0572: l_minuendXs.add(l_minuendCurX);
0573: l_minuendYs.add(l_minuendCurY);
0574: }
0575:
0576: l_minuendMaxY = Math.max(l_minuendMaxY, l_y1);
0577: l_minuendMinY = Math.min(l_minuendMinY, l_y1);
0578:
0579: l_x2 = x_dataset.getXValue(0, l_minuendItem + 1);
0580: l_y2 = x_dataset.getYValue(0, l_minuendItem + 1);
0581: l_minuendNextX = new Double(l_x2);
0582: l_minuendNextY = new Double(l_y2);
0583: }
0584:
0585: // never updated the subtrahend if it is implied to be zero
0586: if (!b_impliedZeroSubtrahend && !b_subtrahendDone
0587: && !b_subtrahendFastForward && b_subtrahendAdvanced) {
0588: l_x3 = x_dataset.getXValue(1, l_subtrahendItem);
0589: l_y3 = x_dataset.getYValue(1, l_subtrahendItem);
0590: l_subtrahendCurX = new Double(l_x3);
0591: l_subtrahendCurY = new Double(l_y3);
0592:
0593: if (!b_subtrahendAtIntersect) {
0594: l_subtrahendXs.add(l_subtrahendCurX);
0595: l_subtrahendYs.add(l_subtrahendCurY);
0596: }
0597:
0598: l_subtrahendMaxY = Math.max(l_subtrahendMaxY, l_y3);
0599: l_subtrahendMinY = Math.min(l_subtrahendMinY, l_y3);
0600:
0601: l_x4 = x_dataset.getXValue(1, l_subtrahendItem + 1);
0602: l_y4 = x_dataset.getYValue(1, l_subtrahendItem + 1);
0603: l_subtrahendNextX = new Double(l_x4);
0604: l_subtrahendNextY = new Double(l_y4);
0605: }
0606:
0607: // deassert b_*FastForward (only matters for 1st time through loop)
0608: b_minuendFastForward = false;
0609: b_subtrahendFastForward = false;
0610:
0611: Double l_intersectX = null;
0612: Double l_intersectY = null;
0613: boolean b_intersect = false;
0614:
0615: b_minuendAtIntersect = false;
0616: b_subtrahendAtIntersect = false;
0617:
0618: // check for intersect
0619: if ((l_x2 == l_x4) && (l_y2 == l_y4)) {
0620: // check if line segments are colinear
0621: if ((l_x1 == l_x3) && (l_y1 == l_y3)) {
0622: b_colinear = true;
0623: } else {
0624: // the intersect is at the next point for both the minuend
0625: // and subtrahend
0626: l_intersectX = new Double(l_x2);
0627: l_intersectY = new Double(l_y2);
0628:
0629: b_intersect = true;
0630: b_minuendAtIntersect = true;
0631: b_subtrahendAtIntersect = true;
0632: }
0633: } else {
0634: // compute common denominator
0635: double l_denominator = ((l_y4 - l_y3) * (l_x2 - l_x1))
0636: - ((l_x4 - l_x3) * (l_y2 - l_y1));
0637:
0638: // compute common deltas
0639: double l_deltaY = l_y1 - l_y3;
0640: double l_deltaX = l_x1 - l_x3;
0641:
0642: // compute numerators
0643: double l_numeratorA = ((l_x4 - l_x3) * l_deltaY)
0644: - ((l_y4 - l_y3) * l_deltaX);
0645: double l_numeratorB = ((l_x2 - l_x1) * l_deltaY)
0646: - ((l_y2 - l_y1) * l_deltaX);
0647:
0648: // check if line segments are colinear
0649: if ((0 == l_numeratorA) && (0 == l_numeratorB)
0650: && (0 == l_denominator)) {
0651: b_colinear = true;
0652: } else {
0653: // check if previously colinear
0654: if (b_colinear) {
0655: // clear colinear points and flag
0656: l_minuendXs.clear();
0657: l_minuendYs.clear();
0658: l_subtrahendXs.clear();
0659: l_subtrahendYs.clear();
0660: l_polygonXs.clear();
0661: l_polygonYs.clear();
0662:
0663: b_colinear = false;
0664:
0665: // set new starting point for the polygon
0666: boolean b_useMinuend = ((l_x3 <= l_x1) && (l_x1 <= l_x4));
0667: l_polygonXs.add(b_useMinuend ? l_minuendCurX
0668: : l_subtrahendCurX);
0669: l_polygonYs.add(b_useMinuend ? l_minuendCurY
0670: : l_subtrahendCurY);
0671: }
0672:
0673: // compute slope components
0674: double l_slopeA = l_numeratorA / l_denominator;
0675: double l_slopeB = l_numeratorB / l_denominator;
0676:
0677: // check if the line segments intersect
0678: if ((0 < l_slopeA) && (l_slopeA <= 1)
0679: && (0 < l_slopeB) && (l_slopeB <= 1)) {
0680: // compute the point of intersection
0681: double l_xi = l_x1 + (l_slopeA * (l_x2 - l_x1));
0682: double l_yi = l_y1 + (l_slopeA * (l_y2 - l_y1));
0683:
0684: l_intersectX = new Double(l_xi);
0685: l_intersectY = new Double(l_yi);
0686: b_intersect = true;
0687: b_minuendAtIntersect = ((l_xi == l_x2) && (l_yi == l_y2));
0688: b_subtrahendAtIntersect = ((l_xi == l_x4) && (l_yi == l_y4));
0689:
0690: // advance minuend and subtrahend to intesect
0691: l_minuendCurX = l_intersectX;
0692: l_minuendCurY = l_intersectY;
0693: l_subtrahendCurX = l_intersectX;
0694: l_subtrahendCurY = l_intersectY;
0695: }
0696: }
0697: }
0698:
0699: if (b_intersect) {
0700: // create the polygon
0701: // add the minuend's points to polygon
0702: l_polygonXs.addAll(l_minuendXs);
0703: l_polygonYs.addAll(l_minuendYs);
0704:
0705: // add intersection point to the polygon
0706: l_polygonXs.add(l_intersectX);
0707: l_polygonYs.add(l_intersectY);
0708:
0709: // add the subtrahend's points to the polygon in reverse
0710: Collections.reverse(l_subtrahendXs);
0711: Collections.reverse(l_subtrahendYs);
0712: l_polygonXs.addAll(l_subtrahendXs);
0713: l_polygonYs.addAll(l_subtrahendYs);
0714:
0715: // create an actual polygon
0716: b_positive = (l_subtrahendMaxY <= l_minuendMaxY)
0717: && (l_subtrahendMinY <= l_minuendMinY);
0718: createPolygon(x_graphics, x_dataArea, x_plot,
0719: x_domainAxis, x_rangeAxis, b_positive,
0720: l_polygonXs, l_polygonYs);
0721:
0722: // clear the point vectors
0723: l_minuendXs.clear();
0724: l_minuendYs.clear();
0725: l_subtrahendXs.clear();
0726: l_subtrahendYs.clear();
0727: l_polygonXs.clear();
0728: l_polygonYs.clear();
0729:
0730: // set the maxY and minY values to intersect y-value
0731: double l_y = l_intersectY.doubleValue();
0732: l_minuendMaxY = l_y;
0733: l_subtrahendMaxY = l_y;
0734: l_minuendMinY = l_y;
0735: l_subtrahendMinY = l_y;
0736:
0737: // add interection point to new polygon
0738: l_polygonXs.add(l_intersectX);
0739: l_polygonYs.add(l_intersectY);
0740: }
0741:
0742: // advance the minuend if needed
0743: if (l_x2 <= l_x4) {
0744: l_minuendItem++;
0745: b_minuendAdvanced = true;
0746: } else {
0747: b_minuendAdvanced = false;
0748: }
0749:
0750: // advance the subtrahend if needed
0751: if (l_x4 <= l_x2) {
0752: l_subtrahendItem++;
0753: b_subtrahendAdvanced = true;
0754: } else {
0755: b_subtrahendAdvanced = false;
0756: }
0757:
0758: b_minuendDone = (l_minuendItem == (l_minuendItemCount - 1));
0759: b_subtrahendDone = (l_subtrahendItem == (l_subtrahendItemCount - 1));
0760: }
0761:
0762: // check if the final polygon needs to be clipped
0763: if (b_minuendDone && (l_x3 < l_x2) && (l_x2 < l_x4)) {
0764: // project onto subtrahend
0765: double l_slope = (l_y4 - l_y3) / (l_x4 - l_x3);
0766: l_subtrahendNextX = l_minuendNextX;
0767: l_subtrahendNextY = new Double((l_slope * l_x2)
0768: + (l_y3 - (l_slope * l_x3)));
0769: }
0770:
0771: if (b_subtrahendDone && (l_x1 < l_x4) && (l_x4 < l_x2)) {
0772: // project onto minuend
0773: double l_slope = (l_y2 - l_y1) / (l_x2 - l_x1);
0774: l_minuendNextX = l_subtrahendNextX;
0775: l_minuendNextY = new Double((l_slope * l_x4)
0776: + (l_y1 - (l_slope * l_x1)));
0777: }
0778:
0779: // consider last point of minuend and subtrahend for determining positivity
0780: l_minuendMaxY = Math.max(l_minuendMaxY, l_minuendNextY
0781: .doubleValue());
0782: l_subtrahendMaxY = Math.max(l_subtrahendMaxY, l_subtrahendNextY
0783: .doubleValue());
0784: l_minuendMinY = Math.min(l_minuendMinY, l_minuendNextY
0785: .doubleValue());
0786: l_subtrahendMinY = Math.min(l_subtrahendMinY, l_subtrahendNextY
0787: .doubleValue());
0788:
0789: // add the last point of the minuned and subtrahend
0790: l_minuendXs.add(l_minuendNextX);
0791: l_minuendYs.add(l_minuendNextY);
0792: l_subtrahendXs.add(l_subtrahendNextX);
0793: l_subtrahendYs.add(l_subtrahendNextY);
0794:
0795: // create the polygon
0796: // add the minuend's points to polygon
0797: l_polygonXs.addAll(l_minuendXs);
0798: l_polygonYs.addAll(l_minuendYs);
0799:
0800: // add the subtrahend's points to the polygon in reverse
0801: Collections.reverse(l_subtrahendXs);
0802: Collections.reverse(l_subtrahendYs);
0803: l_polygonXs.addAll(l_subtrahendXs);
0804: l_polygonYs.addAll(l_subtrahendYs);
0805:
0806: // create an actual polygon
0807: b_positive = (l_subtrahendMaxY <= l_minuendMaxY)
0808: && (l_subtrahendMinY <= l_minuendMinY);
0809: createPolygon(x_graphics, x_dataArea, x_plot, x_domainAxis,
0810: x_rangeAxis, b_positive, l_polygonXs, l_polygonYs);
0811: }
0812:
0813: /**
0814: * Draws the visual representation of a single data item, second pass. In
0815: * the second pass, the renderer draws the lines and shapes for the
0816: * individual points in the two series.
0817: *
0818: * @param x_graphics the graphics device.
0819: * @param x_dataArea the area within which the data is being drawn.
0820: * @param x_info collects information about the drawing.
0821: * @param x_plot the plot (can be used to obtain standard color
0822: * information etc).
0823: * @param x_domainAxis the domain (horizontal) axis.
0824: * @param x_rangeAxis the range (vertical) axis.
0825: * @param x_dataset the dataset.
0826: * @param x_series the series index (zero-based).
0827: * @param x_item the item index (zero-based).
0828: * @param x_crosshairState crosshair information for the plot
0829: * (<code>null</code> permitted).
0830: */
0831: protected void drawItemPass1(Graphics2D x_graphics,
0832: Rectangle2D x_dataArea, PlotRenderingInfo x_info,
0833: XYPlot x_plot, ValueAxis x_domainAxis,
0834: ValueAxis x_rangeAxis, XYDataset x_dataset, int x_series,
0835: int x_item, CrosshairState x_crosshairState) {
0836:
0837: Shape l_entityArea = null;
0838: EntityCollection l_entities = null;
0839: if (null != x_info) {
0840: l_entities = x_info.getOwner().getEntityCollection();
0841: }
0842:
0843: Paint l_seriesPaint = getItemPaint(x_series, x_item);
0844: Stroke l_seriesStroke = getItemStroke(x_series, x_item);
0845: x_graphics.setPaint(l_seriesPaint);
0846: x_graphics.setStroke(l_seriesStroke);
0847:
0848: PlotOrientation l_orientation = x_plot.getOrientation();
0849: RectangleEdge l_domainAxisLocation = x_plot.getDomainAxisEdge();
0850: RectangleEdge l_rangeAxisLocation = x_plot.getRangeAxisEdge();
0851:
0852: double l_x0 = x_dataset.getXValue(x_series, x_item);
0853: double l_y0 = x_dataset.getYValue(x_series, x_item);
0854: double l_x1 = x_domainAxis.valueToJava2D(l_x0, x_dataArea,
0855: l_domainAxisLocation);
0856: double l_y1 = x_rangeAxis.valueToJava2D(l_y0, x_dataArea,
0857: l_rangeAxisLocation);
0858:
0859: if (getShapesVisible()) {
0860: Shape l_shape = getItemShape(x_series, x_item);
0861: if (l_orientation == PlotOrientation.HORIZONTAL) {
0862: l_shape = ShapeUtilities.createTranslatedShape(l_shape,
0863: l_y1, l_x1);
0864: } else {
0865: l_shape = ShapeUtilities.createTranslatedShape(l_shape,
0866: l_x1, l_y1);
0867: }
0868: if (l_shape.intersects(x_dataArea)) {
0869: x_graphics.setPaint(getItemPaint(x_series, x_item));
0870: x_graphics.fill(l_shape);
0871: }
0872: l_entityArea = l_shape;
0873: }
0874:
0875: // add an entity for the item...
0876: if (null != l_entities) {
0877: if (null == l_entityArea) {
0878: l_entityArea = new Rectangle2D.Double((l_x1 - 2),
0879: (l_y1 - 2), 4, 4);
0880: }
0881: String l_tip = null;
0882: XYToolTipGenerator l_tipGenerator = getToolTipGenerator(
0883: x_series, x_item);
0884: if (null != l_tipGenerator) {
0885: l_tip = l_tipGenerator.generateToolTip(x_dataset,
0886: x_series, x_item);
0887: }
0888: String l_url = null;
0889: XYURLGenerator l_urlGenerator = getURLGenerator();
0890: if (null != l_urlGenerator) {
0891: l_url = l_urlGenerator.generateURL(x_dataset, x_series,
0892: x_item);
0893: }
0894: XYItemEntity l_entity = new XYItemEntity(l_entityArea,
0895: x_dataset, x_series, x_item, l_tip, l_url);
0896: l_entities.add(l_entity);
0897: }
0898:
0899: int l_domainAxisIndex = x_plot.getDomainAxisIndex(x_domainAxis);
0900: int l_rangeAxisIndex = x_plot.getRangeAxisIndex(x_rangeAxis);
0901: updateCrosshairValues(x_crosshairState, l_x0, l_y0,
0902: l_domainAxisIndex, l_rangeAxisIndex, l_x1, l_y1,
0903: l_orientation);
0904:
0905: if (0 == x_item) {
0906: return;
0907: }
0908:
0909: double l_x2 = x_domainAxis.valueToJava2D(x_dataset.getXValue(
0910: x_series, (x_item - 1)), x_dataArea,
0911: l_domainAxisLocation);
0912: double l_y2 = x_rangeAxis.valueToJava2D(x_dataset.getYValue(
0913: x_series, (x_item - 1)), x_dataArea,
0914: l_rangeAxisLocation);
0915:
0916: Line2D l_line = null;
0917: if (PlotOrientation.HORIZONTAL == l_orientation) {
0918: l_line = new Line2D.Double(l_y1, l_x1, l_y2, l_x2);
0919: } else if (PlotOrientation.VERTICAL == l_orientation) {
0920: l_line = new Line2D.Double(l_x1, l_y1, l_x2, l_y2);
0921: }
0922:
0923: if ((null != l_line) && l_line.intersects(x_dataArea)) {
0924: x_graphics.setPaint(getItemPaint(x_series, x_item));
0925: x_graphics.setStroke(getItemStroke(x_series, x_item));
0926: x_graphics.draw(l_line);
0927: }
0928: }
0929:
0930: /**
0931: * Determines if a dataset is degenerate. A degenerate dataset is a
0932: * dataset where either series has less than two (2) points.
0933: *
0934: * @param x_dataset the dataset.
0935: * @param x_impliedZeroSubtrahend if false, do not check the subtrahend
0936: *
0937: * @return true if the dataset is degenerate.
0938: */
0939: private boolean isEitherSeriesDegenerate(XYDataset x_dataset,
0940: boolean x_impliedZeroSubtrahend) {
0941:
0942: if (x_impliedZeroSubtrahend) {
0943: return (x_dataset.getItemCount(0) < 2);
0944: }
0945:
0946: return ((x_dataset.getItemCount(0) < 2) || (x_dataset
0947: .getItemCount(1) < 2));
0948: }
0949:
0950: /**
0951: * Determines if the two (2) series are disjoint.
0952: * Disjoint series do not overlap in the domain space.
0953: *
0954: * @param x_dataset the dataset.
0955: *
0956: * @return true if the dataset is degenerate.
0957: */
0958: private boolean areSeriesDisjoint(XYDataset x_dataset) {
0959:
0960: int l_minuendItemCount = x_dataset.getItemCount(0);
0961: double l_minuendFirst = x_dataset.getXValue(0, 0);
0962: double l_minuendLast = x_dataset.getXValue(0,
0963: l_minuendItemCount - 1);
0964:
0965: int l_subtrahendItemCount = x_dataset.getItemCount(1);
0966: double l_subtrahendFirst = x_dataset.getXValue(1, 0);
0967: double l_subtrahendLast = x_dataset.getXValue(1,
0968: l_subtrahendItemCount - 1);
0969:
0970: return ((l_minuendLast < l_subtrahendFirst) || (l_subtrahendLast < l_minuendFirst));
0971: }
0972:
0973: /**
0974: * Draws the visual representation of a polygon
0975: *
0976: * @param x_graphics the graphics device.
0977: * @param x_dataArea the area within which the data is being drawn.
0978: * @param x_plot the plot (can be used to obtain standard color
0979: * information etc).
0980: * @param x_domainAxis the domain (horizontal) axis.
0981: * @param x_rangeAxis the range (vertical) axis.
0982: * @param x_positive indicates if the polygon is positive (true) or
0983: * negative (false).
0984: * @param x_xValues a linked list of the x values (expects values to be
0985: * of type Double).
0986: * @param x_yValues a linked list of the y values (expects values to be
0987: * of type Double).
0988: */
0989: private void createPolygon(Graphics2D x_graphics,
0990: Rectangle2D x_dataArea, XYPlot x_plot,
0991: ValueAxis x_domainAxis, ValueAxis x_rangeAxis,
0992: boolean x_positive, LinkedList x_xValues,
0993: LinkedList x_yValues) {
0994:
0995: PlotOrientation l_orientation = x_plot.getOrientation();
0996: RectangleEdge l_domainAxisLocation = x_plot.getDomainAxisEdge();
0997: RectangleEdge l_rangeAxisLocation = x_plot.getRangeAxisEdge();
0998:
0999: Object[] l_xValues = x_xValues.toArray();
1000: Object[] l_yValues = x_yValues.toArray();
1001:
1002: GeneralPath l_path = new GeneralPath();
1003:
1004: if (PlotOrientation.VERTICAL == l_orientation) {
1005: double l_x = x_domainAxis.valueToJava2D(
1006: ((Double) l_xValues[0]).doubleValue(), x_dataArea,
1007: l_domainAxisLocation);
1008: if (this .roundXCoordinates) {
1009: l_x = Math.rint(l_x);
1010: }
1011:
1012: double l_y = x_rangeAxis.valueToJava2D(
1013: ((Double) l_yValues[0]).doubleValue(), x_dataArea,
1014: l_rangeAxisLocation);
1015:
1016: l_path.moveTo((float) l_x, (float) l_y);
1017: for (int i = 1; i < l_xValues.length; i++) {
1018: l_x = x_domainAxis.valueToJava2D(
1019: ((Double) l_xValues[i]).doubleValue(),
1020: x_dataArea, l_domainAxisLocation);
1021: if (this .roundXCoordinates) {
1022: l_x = Math.rint(l_x);
1023: }
1024:
1025: l_y = x_rangeAxis
1026: .valueToJava2D(((Double) l_yValues[i])
1027: .doubleValue(), x_dataArea,
1028: l_rangeAxisLocation);
1029: l_path.lineTo((float) l_x, (float) l_y);
1030: }
1031: l_path.closePath();
1032: } else {
1033: double l_x = x_domainAxis.valueToJava2D(
1034: ((Double) l_xValues[0]).doubleValue(), x_dataArea,
1035: l_domainAxisLocation);
1036: if (this .roundXCoordinates) {
1037: l_x = Math.rint(l_x);
1038: }
1039:
1040: double l_y = x_rangeAxis.valueToJava2D(
1041: ((Double) l_yValues[0]).doubleValue(), x_dataArea,
1042: l_rangeAxisLocation);
1043:
1044: l_path.moveTo((float) l_y, (float) l_x);
1045: for (int i = 1; i < l_xValues.length; i++) {
1046: l_x = x_domainAxis.valueToJava2D(
1047: ((Double) l_xValues[i]).doubleValue(),
1048: x_dataArea, l_domainAxisLocation);
1049: if (this .roundXCoordinates) {
1050: l_x = Math.rint(l_x);
1051: }
1052:
1053: l_y = x_rangeAxis
1054: .valueToJava2D(((Double) l_yValues[i])
1055: .doubleValue(), x_dataArea,
1056: l_rangeAxisLocation);
1057: l_path.lineTo((float) l_y, (float) l_x);
1058: }
1059: l_path.closePath();
1060: }
1061:
1062: if (l_path.intersects(x_dataArea)) {
1063: x_graphics.setPaint(x_positive ? getPositivePaint()
1064: : getNegativePaint());
1065: x_graphics.fill(l_path);
1066: }
1067: }
1068:
1069: /**
1070: * Returns a default legend item for the specified series. Subclasses
1071: * should override this method to generate customised items.
1072: *
1073: * @param datasetIndex the dataset index (zero-based).
1074: * @param series the series index (zero-based).
1075: *
1076: * @return A legend item for the series.
1077: */
1078: public LegendItem getLegendItem(int datasetIndex, int series) {
1079: LegendItem result = null;
1080: XYPlot p = getPlot();
1081: if (p != null) {
1082: XYDataset dataset = p.getDataset(datasetIndex);
1083: if (dataset != null) {
1084: if (getItemVisible(series, 0)) {
1085: String label = getLegendItemLabelGenerator()
1086: .generateLabel(dataset, series);
1087: String description = label;
1088: String toolTipText = null;
1089: if (getLegendItemToolTipGenerator() != null) {
1090: toolTipText = getLegendItemToolTipGenerator()
1091: .generateLabel(dataset, series);
1092: }
1093: String urlText = null;
1094: if (getLegendItemURLGenerator() != null) {
1095: urlText = getLegendItemURLGenerator()
1096: .generateLabel(dataset, series);
1097: }
1098: Paint paint = lookupSeriesPaint(series);
1099: Stroke stroke = lookupSeriesStroke(series);
1100: // TODO: the following hard-coded line needs generalising
1101: Line2D line = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
1102: result = new LegendItem(label, description,
1103: toolTipText, urlText, line, stroke, paint);
1104: result.setDataset(dataset);
1105: result.setDatasetIndex(datasetIndex);
1106: result.setSeriesKey(dataset.getSeriesKey(series));
1107: result.setSeriesIndex(series);
1108: }
1109: }
1110:
1111: }
1112:
1113: return result;
1114:
1115: }
1116:
1117: /**
1118: * Tests this renderer for equality with an arbitrary object.
1119: *
1120: * @param obj the object (<code>null</code> permitted).
1121: *
1122: * @return A boolean.
1123: */
1124: public boolean equals(Object obj) {
1125: if (obj == this ) {
1126: return true;
1127: }
1128: if (!(obj instanceof XYDifferenceRenderer)) {
1129: return false;
1130: }
1131: if (!super .equals(obj)) {
1132: return false;
1133: }
1134: XYDifferenceRenderer that = (XYDifferenceRenderer) obj;
1135: if (!PaintUtilities.equal(this .positivePaint,
1136: that.positivePaint)) {
1137: return false;
1138: }
1139: if (!PaintUtilities.equal(this .negativePaint,
1140: that.negativePaint)) {
1141: return false;
1142: }
1143: if (this .shapesVisible != that.shapesVisible) {
1144: return false;
1145: }
1146: if (!ShapeUtilities.equal(this .legendLine, that.legendLine)) {
1147: return false;
1148: }
1149: if (this .roundXCoordinates != that.roundXCoordinates) {
1150: return false;
1151: }
1152: return true;
1153: }
1154:
1155: /**
1156: * Returns a clone of the renderer.
1157: *
1158: * @return A clone.
1159: *
1160: * @throws CloneNotSupportedException if the renderer cannot be cloned.
1161: */
1162: public Object clone() throws CloneNotSupportedException {
1163: XYDifferenceRenderer clone = (XYDifferenceRenderer) super
1164: .clone();
1165: clone.legendLine = ShapeUtilities.clone(this .legendLine);
1166: return clone;
1167: }
1168:
1169: /**
1170: * Provides serialization support.
1171: *
1172: * @param stream the output stream.
1173: *
1174: * @throws IOException if there is an I/O error.
1175: */
1176: private void writeObject(ObjectOutputStream stream)
1177: throws IOException {
1178: stream.defaultWriteObject();
1179: SerialUtilities.writePaint(this .positivePaint, stream);
1180: SerialUtilities.writePaint(this .negativePaint, stream);
1181: SerialUtilities.writeShape(this .legendLine, stream);
1182: }
1183:
1184: /**
1185: * Provides serialization support.
1186: *
1187: * @param stream the input stream.
1188: *
1189: * @throws IOException if there is an I/O error.
1190: * @throws ClassNotFoundException if there is a classpath problem.
1191: */
1192: private void readObject(ObjectInputStream stream)
1193: throws IOException, ClassNotFoundException {
1194: stream.defaultReadObject();
1195: this.positivePaint = SerialUtilities.readPaint(stream);
1196: this.negativePaint = SerialUtilities.readPaint(stream);
1197: this.legendLine = SerialUtilities.readShape(stream);
1198: }
1199:
1200: }
|