    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, by Object Refinery Limited and Contributors.
    006 *
    007 * Project Info:  http://www.jfree.org/jfreechart/index.html
    008 *
    009 * This library is free software; you can redistribute it and/or modify it
    010 * under the terms of the GNU Lesser General Public License as published by
    011 * the Free Software Foundation; either version 2.1 of the License, or
    012 * (at your option) any later version.
    013 *
    014 * This library is distributed in the hope that it will be useful, but
    015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017 * License for more details.
    018 *
    019 * You should have received a copy of the GNU Lesser General Public
    020 * License along with this library; if not, write to the Free Software
    021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022 * USA.
    023 *
    024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    025 * Other names may be trademarks of their respective owners.]
    026 *
    027 * -------------------------
    028 * XYDifferenceRenderer.java
    029 * -------------------------
    030 * (C) Copyright 2003-2008, by Object Refinery Limited and Contributors.
    031 *
    032 * Original Author:  David Gilbert (for Object Refinery Limited);
    033 * Contributor(s):   Richard West, Advanced Micro Devices, Inc. (major rewrite
    034 *                   of difference drawing algorithm);
    035 *
    036 * Changes:
    037 * --------
    038 * 30-Apr-2003 : Version 1 (DG);
    039 * 30-Jul-2003 : Modified entity constructor (CZ);
    040 * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
    041 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
    042 * 09-Feb-2004 : Updated to support horizontal plot orientation (DG);
    043 * 10-Feb-2004 : Added default constructor, setter methods and updated
    044 *               Javadocs (DG);
    045 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
    046 * 30-Mar-2004 : Fixed bug in getNegativePaint() method (DG);
    047 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
    048 *               getYValue() (DG);
    049 * 25-Aug-2004 : Fixed a bug preventing the use of crosshairs (DG);
    050 * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
    051 * 19-Jan-2005 : Now accesses only primitive values from dataset (DG);
    052 * 22-Feb-2005 : Override getLegendItem(int, int) to return "line" items (DG);
    053 * 13-Apr-2005 : Fixed shape positioning bug (id = 1182062) (DG);
    054 * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
    055 * 04-May-2005 : Override equals() method, renamed get/setPlotShapes() -->
    056 *               get/setShapesVisible (DG);
    057 * 09-Jun-2005 : Updated equals() to handle GradientPaint (DG);
    058 * 16-Jun-2005 : Fix bug (1221021) affecting stroke used for each series (DG);
    059 * ------------- JFREECHART 1.0.x ---------------------------------------------
    060 * 24-Jan-2007 : Added flag to allow rounding of x-coordinates, and fixed
    061 *               bug in clone() (DG);
    062 * 05-Feb-2007 : Added an extra call to updateCrosshairValues() in
    063 *               drawItemPass1(), to fix bug 1564967 (DG);
    064 * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
    065 * 08-Mar-2007 : Fixed entity generation (DG);
    066 * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
    067 * 23-Apr-2007 : Rewrite of difference drawing algorithm to allow use of
    068 *               series with disjoint x-values (RW);
    069 * 04-May-2007 : Set processVisibleItemsOnly flag to false (DG);
    070 * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG);
    071 * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
    072 * 05-Nov-2007 : Draw item labels if visible (RW);
    073 * 17-Jun-2008 : Apply legend shape, font and paint attributes (DG);
    074 *
    075 */
    076
    077package org.jfree.chart.renderer.xy;
    078
    079import java.awt.Color;
    080import java.awt.Graphics2D;
    081import java.awt.Paint;
    082import java.awt.Shape;
    083import java.awt.Stroke;
    084import java.awt.geom.GeneralPath;
    085import java.awt.geom.Line2D;
    086import java.awt.geom.Rectangle2D;
    087import java.io.IOException;
    088import java.io.ObjectInputStream;
    089import java.io.ObjectOutputStream;
    090import java.util.Collections;
    091import java.util.LinkedList;
    092
    093import org.jfree.chart.LegendItem;
    094import org.jfree.chart.axis.ValueAxis;
    095import org.jfree.chart.entity.EntityCollection;
    096import org.jfree.chart.entity.XYItemEntity;
    097import org.jfree.chart.event.RendererChangeEvent;
    098import org.jfree.chart.labels.XYToolTipGenerator;
    099import org.jfree.chart.plot.CrosshairState;
    100import org.jfree.chart.plot.PlotOrientation;
    101import org.jfree.chart.plot.PlotRenderingInfo;
    102import org.jfree.chart.plot.XYPlot;
    103import org.jfree.chart.urls.XYURLGenerator;
    104import org.jfree.data.xy.XYDataset;
    105import org.jfree.io.SerialUtilities;
    106import org.jfree.ui.RectangleEdge;
    107import org.jfree.util.PaintUtilities;
    108import org.jfree.util.PublicCloneable;
    109import org.jfree.util.ShapeUtilities;
    110
    111/**
    112 * A renderer for an {@link XYPlot} that highlights the differences between two
    113 * series.  The example shown here is generated by the
    114 * <code>DifferenceChartDemo1.java</code> program included in the JFreeChart
    115 * demo collection:
    116 * <br><br>
    117 * <img src="../../../../../images/XYDifferenceRendererSample.png"
    118 * alt="XYDifferenceRendererSample.png" />
    119 */
    120public class XYDifferenceRenderer extends AbstractXYItemRenderer
    121        implements XYItemRenderer, PublicCloneable {
    122
    123    /** For serialization. */
    124    private static final long serialVersionUID = -8447915602375584857L;
    125
    126    /** The paint used to highlight positive differences (y(0) > y(1)). */
    127    private transient Paint positivePaint;
    128
    129    /** The paint used to highlight negative differences (y(0) < y(1)). */
    130    private transient Paint negativePaint;
    131
    132    /** Display shapes at each point? */
    133    private boolean shapesVisible;
    134
    135    /** The shape to display in the legend item. */
    136    private transient Shape legendLine;
    137
    138    /**
    139     * This flag controls whether or not the x-coordinates (in Java2D space)
    140     * are rounded to integers.  When set to true, this can avoid the vertical
    141     * striping that anti-aliasing can generate.  However, the rounding may not
    142     * be appropriate for output in high resolution formats (for example,
    143     * vector graphics formats such as SVG and PDF).
    144     *
    145     * @since 1.0.4
    146     */
    147    private boolean roundXCoordinates;
    148
    149    /**
    150     * Creates a new renderer with default attributes.
    151     */
    152    public XYDifferenceRenderer() {
    153        this(Color.green, Color.red, false);
    154    }
    155
    156    /**
    157     * Creates a new renderer.
    158     *
    159     * @param positivePaint  the highlight color for positive differences
    160     *                       (<code>null</code> not permitted).
    161     * @param negativePaint  the highlight color for negative differences
    162     *                       (<code>null</code> not permitted).
    163     * @param shapes  draw shapes?
    164     */
    165    public XYDifferenceRenderer(Paint positivePaint, Paint negativePaint,
    166                                boolean shapes) {
    167        if (positivePaint == null) {
    168            throw new IllegalArgumentException(
    169                    "Null 'positivePaint' argument.");
    170        }
    171        if (negativePaint == null) {
    172            throw new IllegalArgumentException(
    173                    "Null 'negativePaint' argument.");
    174        }
    175        this.positivePaint = positivePaint;
    176        this.negativePaint = negativePaint;
    177        this.shapesVisible = shapes;
    178        this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
    179        this.roundXCoordinates = false;
    180    }
    181
    182    /**
    183     * Returns the paint used to highlight positive differences.
    184     *
    185     * @return The paint (never <code>null</code>).
    186     *
    187     * @see #setPositivePaint(Paint)
    188     */
    189    public Paint getPositivePaint() {
    190        return this.positivePaint;
    191    }
    192
    193    /**
    194     * Sets the paint used to highlight positive differences and sends a
    195     * {@link RendererChangeEvent} to all registered listeners.
    196     *
    197     * @param paint  the paint (<code>null</code> not permitted).
    198     *
    199     * @see #getPositivePaint()
    200     */
    201    public void setPositivePaint(Paint paint) {
    202        if (paint == null) {
    203            throw new IllegalArgumentException("Null 'paint' argument.");
    204        }
    205        this.positivePaint = paint;
    206        fireChangeEvent();
    207    }
    208
    209    /**
    210     * Returns the paint used to highlight negative differences.
    211     *
    212     * @return The paint (never <code>null</code>).
    213     *
    214     * @see #setNegativePaint(Paint)
    215     */
    216    public Paint getNegativePaint() {
    217        return this.negativePaint;
    218    }
    219
    220    /**
    221     * Sets the paint used to highlight negative differences.
    222     *
    223     * @param paint  the paint (<code>null</code> not permitted).
    224     *
    225     * @see #getNegativePaint()
    226     */
    227    public void setNegativePaint(Paint paint) {
    228        if (paint == null) {
    229            throw new IllegalArgumentException("Null 'paint' argument.");
    230        }
    231        this.negativePaint = paint;
    232        notifyListeners(new RendererChangeEvent(this));
    233    }
    234
    235    /**
    236     * Returns a flag that controls whether or not shapes are drawn for each
    237     * data value.
    238     *
    239     * @return A boolean.
    240     *
    241     * @see #setShapesVisible(boolean)
    242     */
    243    public boolean getShapesVisible() {
    244        return this.shapesVisible;
    245    }
    246
    247    /**
    248     * Sets a flag that controls whether or not shapes are drawn for each
    249     * data value, and sends a {@link RendererChangeEvent} to all registered
    250     * listeners.
    251     *
    252     * @param flag  the flag.
    253     *
    254     * @see #getShapesVisible()
    255     */
    256    public void setShapesVisible(boolean flag) {
    257        this.shapesVisible = flag;
    258        fireChangeEvent();
    259    }
    260
    261    /**
    262     * Returns the shape used to represent a line in the legend.
    263     *
    264     * @return The legend line (never <code>null</code>).
    265     *
    266     * @see #setLegendLine(Shape)
    267     */
    268    public Shape getLegendLine() {
    269        return this.legendLine;
    270    }
    271
    272    /**
    273     * Sets the shape used as a line in each legend item and sends a
    274     * {@link RendererChangeEvent} to all registered listeners.
    275     *
    276     * @param line  the line (<code>null</code> not permitted).
    277     *
    278     * @see #getLegendLine()
    279     */
    280    public void setLegendLine(Shape line) {
    281        if (line == null) {
    282            throw new IllegalArgumentException("Null 'line' argument.");
    283        }
    284        this.legendLine = line;
    285        fireChangeEvent();
    286    }
    287
    288    /**
    289     * Returns the flag that controls whether or not the x-coordinates (in
    290     * Java2D space) are rounded to integer values.
    291     *
    292     * @return The flag.
    293     *
    294     * @since 1.0.4
    295     *
    296     * @see #setRoundXCoordinates(boolean)
    297     */
    298    public boolean getRoundXCoordinates() {
    299        return this.roundXCoordinates;
    300    }
    301
    302    /**
    303     * Sets the flag that controls whether or not the x-coordinates (in
    304     * Java2D space) are rounded to integer values, and sends a
    305     * {@link RendererChangeEvent} to all registered listeners.
    306     *
    307     * @param round  the new flag value.
    308     *
    309     * @since 1.0.4
    310     *
    311     * @see #getRoundXCoordinates()
    312     */
    313    public void setRoundXCoordinates(boolean round) {
    314        this.roundXCoordinates = round;
    315        fireChangeEvent();
    316    }
    317
    318    /**
    319     * Initialises the renderer and returns a state object that should be
    320     * passed to subsequent calls to the drawItem() method.  This method will
    321     * be called before the first item is rendered, giving the renderer an
    322     * opportunity to initialise any state information it wants to maintain.
    323     * The renderer can do nothing if it chooses.
    324     *
    325     * @param g2  the graphics device.
    326     * @param dataArea  the area inside the axes.
    327     * @param plot  the plot.
    328     * @param data  the data.
    329     * @param info  an optional info collection object to return data back to
    330     *              the caller.
    331     *
    332     * @return A state object.
    333     */
    334    public XYItemRendererState initialise(Graphics2D g2,
    335                                          Rectangle2D dataArea,
    336                                          XYPlot plot,
    337                                          XYDataset data,
    338                                          PlotRenderingInfo info) {
    339
    340        XYItemRendererState state = super.initialise(g2, dataArea, plot, data,
    341                info);
    342        state.setProcessVisibleItemsOnly(false);
    343        return state;
    344
    345    }
    346
    347    /**
    348     * Returns <code>2</code>, the number of passes required by the renderer.
    349     * The {@link XYPlot} will run through the dataset this number of times.
    350     *
    351     * @return The number of passes required by the renderer.
    352     */
    353    public int getPassCount() {
    354        return 2;
    355    }
    356
    357    /**
    358     * Draws the visual representation of a single data item.
    359     *
    360     * @param g2  the graphics device.
    361     * @param state  the renderer state.
    362     * @param dataArea  the area within which the data is being drawn.
    363     * @param info  collects information about the drawing.
    364     * @param plot  the plot (can be used to obtain standard color
    365     *              information etc).
    366     * @param domainAxis  the domain (horizontal) axis.
    367     * @param rangeAxis  the range (vertical) axis.
    368     * @param dataset  the dataset.
    369     * @param series  the series index (zero-based).
    370     * @param item  the item index (zero-based).
    371     * @param crosshairState  crosshair information for the plot
    372     *                        (<code>null</code> permitted).
    373     * @param pass  the pass index.
    374     */
    375    public void drawItem(Graphics2D g2,
    376                         XYItemRendererState state,
    377                         Rectangle2D dataArea,
    378                         PlotRenderingInfo info,
    379                         XYPlot plot,
    380                         ValueAxis domainAxis,
    381                         ValueAxis rangeAxis,
    382                         XYDataset dataset,
    383                         int series,
    384                         int item,
    385                         CrosshairState crosshairState,
    386                         int pass) {
    387
    388        if (pass == 0) {
    389            drawItemPass0(g2, dataArea, info, plot, domainAxis, rangeAxis,
    390                    dataset, series, item, crosshairState);
    391        }
    392        else if (pass == 1) {
    393            drawItemPass1(g2, dataArea, info, plot, domainAxis, rangeAxis,
    394                    dataset, series, item, crosshairState);
    395        }
    396
    397    }
    398
    399    /**
    400     * Draws the visual representation of a single data item, first pass.
    401     *
    402     * @param x_graphics  the graphics device.
    403     * @param x_dataArea  the area within which the data is being drawn.
    404     * @param x_info  collects information about the drawing.
    405     * @param x_plot  the plot (can be used to obtain standard color
    406     *                information etc).
    407     * @param x_domainAxis  the domain (horizontal) axis.
    408     * @param x_rangeAxis  the range (vertical) axis.
    409     * @param x_dataset  the dataset.
    410     * @param x_series  the series index (zero-based).
    411     * @param x_item  the item index (zero-based).
    412     * @param x_crosshairState  crosshair information for the plot
    413     *                          (<code>null</code> permitted).
    414     */
    415    protected void drawItemPass0(Graphics2D x_graphics,
    416                                 Rectangle2D x_dataArea,
    417                                 PlotRenderingInfo x_info,
    418                                 XYPlot x_plot,
    419                                 ValueAxis x_domainAxis,
    420                                 ValueAxis x_rangeAxis,
    421                                 XYDataset x_dataset,
    422                                 int x_series,
    423                                 int x_item,
    424                                 CrosshairState x_crosshairState) {
    425
    426        if (!((0 == x_series) && (0 == x_item))) {
    427            return;
    428        }
    429
    430        boolean b_impliedZeroSubtrahend = (1 == x_dataset.getSeriesCount());
    431
    432        // check if either series is a degenerate case (i.e. less than 2 points)
    433        if (isEitherSeriesDegenerate(x_dataset, b_impliedZeroSubtrahend)) {
    434            return;
    435        }
    436
    437        // check if series are disjoint (i.e. domain-spans do not overlap)
    438        if (!b_impliedZeroSubtrahend && areSeriesDisjoint(x_dataset)) {
    439            return;
    440        }
    441
    442        // polygon definitions
    443        LinkedList l_minuendXs    = new LinkedList();
    444        LinkedList l_minuendYs    = new LinkedList();
    445        LinkedList l_subtrahendXs = new LinkedList();
    446        LinkedList l_subtrahendYs = new LinkedList();
    447        LinkedList l_polygonXs    = new LinkedList();
    448        LinkedList l_polygonYs    = new LinkedList();
    449
    450        // state
    451        int l_minuendItem      = 0;
    452        int l_minuendItemCount = x_dataset.getItemCount(0);
    453        Double l_minuendCurX   = null;
    454        Double l_minuendNextX  = null;
    455        Double l_minuendCurY   = null;
    456        Double l_minuendNextY  = null;
    457        double l_minuendMaxY   = Double.NEGATIVE_INFINITY;
    458        double l_minuendMinY   = Double.POSITIVE_INFINITY;
    459
    460        int l_subtrahendItem      = 0;
    461        int l_subtrahendItemCount = 0; // actual value set below
    462        Double l_subtrahendCurX   = null;
    463        Double l_subtrahendNextX  = null;
    464        Double l_subtrahendCurY   = null;
    465        Double l_subtrahendNextY  = null;
    466        double l_subtrahendMaxY   = Double.NEGATIVE_INFINITY;
    467        double l_subtrahendMinY   = Double.POSITIVE_INFINITY;
    468
    469        // if a subtrahend is not specified, assume it is zero
    470        if (b_impliedZeroSubtrahend) {
    471            l_subtrahendItem      = 0;
    472            l_subtrahendItemCount = 2;
    473            l_subtrahendCurX      = new Double(x_dataset.getXValue(0, 0));
    474            l_subtrahendNextX     = new Double(x_dataset.getXValue(0,
    475                    (l_minuendItemCount - 1)));
    476            l_subtrahendCurY      = new Double(0.0);
    477            l_subtrahendNextY     = new Double(0.0);
    478            l_subtrahendMaxY      = 0.0;
    479            l_subtrahendMinY      = 0.0;
    480
    481            l_subtrahendXs.add(l_subtrahendCurX);
    482            l_subtrahendYs.add(l_subtrahendCurY);
    483        }
    484        else {
    485            l_subtrahendItemCount = x_dataset.getItemCount(1);
    486        }
    487
    488        boolean b_minuendDone           = false;
    489        boolean b_minuendAdvanced       = true;
    490        boolean b_minuendAtIntersect    = false;
    491        boolean b_minuendFastForward    = false;
    492        boolean b_subtrahendDone        = false;
    493        boolean b_subtrahendAdvanced    = true;
    494        boolean b_subtrahendAtIntersect = false;
    495        boolean b_subtrahendFastForward = false;
    496        boolean b_colinear              = false;
    497
    498        boolean b_positive;
    499
    500        // coordinate pairs
    501        double l_x1 = 0.0, l_y1 = 0.0; // current minuend point
    502        double l_x2 = 0.0, l_y2 = 0.0; // next minuend point
    503        double l_x3 = 0.0, l_y3 = 0.0; // current subtrahend point
    504        double l_x4 = 0.0, l_y4 = 0.0; // next subtrahend point
    505
    506        // fast-forward through leading tails
    507        boolean b_fastForwardDone = false;
    508        while (!b_fastForwardDone) {
    509            // get the x and y coordinates
    510            l_x1 = x_dataset.getXValue(0, l_minuendItem);
    511            l_y1 = x_dataset.getYValue(0, l_minuendItem);
    512            l_x2 = x_dataset.getXValue(0, l_minuendItem + 1);
    513            l_y2 = x_dataset.getYValue(0, l_minuendItem + 1);
    514
    515            l_minuendCurX  = new Double(l_x1);
    516            l_minuendCurY  = new Double(l_y1);
    517            l_minuendNextX = new Double(l_x2);
    518            l_minuendNextY = new Double(l_y2);
    519
    520            if (b_impliedZeroSubtrahend) {
    521                l_x3 = l_subtrahendCurX.doubleValue();
    522                l_y3 = l_subtrahendCurY.doubleValue();
    523                l_x4 = l_subtrahendNextX.doubleValue();
    524                l_y4 = l_subtrahendNextY.doubleValue();
    525            }
    526            else {
    527                l_x3 = x_dataset.getXValue(1, l_subtrahendItem);
    528                l_y3 = x_dataset.getYValue(1, l_subtrahendItem);
    529                l_x4 = x_dataset.getXValue(1, l_subtrahendItem + 1);
    530                l_y4 = x_dataset.getYValue(1, l_subtrahendItem + 1);
    531
    532                l_subtrahendCurX  = new Double(l_x3);
    533                l_subtrahendCurY  = new Double(l_y3);
    534                l_subtrahendNextX = new Double(l_x4);
    535                l_subtrahendNextY = new Double(l_y4);
    536            }
    537
    538            if (l_x2 <= l_x3) {
    539                // minuend needs to be fast forwarded
    540                l_minuendItem++;
    541                b_minuendFastForward = true;
    542                continue;
    543            }
    544
    545            if (l_x4 <= l_x1) {
    546                // subtrahend needs to be fast forwarded
    547                l_subtrahendItem++;
    548                b_subtrahendFastForward = true;
    549                continue;
    550            }
    551
    552            // check if initial polygon needs to be clipped
    553            if ((l_x3 < l_x1) && (l_x1 < l_x4)) {
    554                // project onto subtrahend
    555                double l_slope   = (l_y4 - l_y3) / (l_x4 - l_x3);
    556                l_subtrahendCurX = l_minuendCurX;
    557                l_subtrahendCurY = new Double((l_slope * l_x1)
    558                        + (l_y3 - (l_slope * l_x3)));
    559
    560                l_subtrahendXs.add(l_subtrahendCurX);
    561                l_subtrahendYs.add(l_subtrahendCurY);
    562            }
    563
    564            if ((l_x1 < l_x3) && (l_x3 < l_x2)) {
    565                // project onto minuend
    566                double l_slope = (l_y2 - l_y1) / (l_x2 - l_x1);
    567                l_minuendCurX  = l_subtrahendCurX;
    568                l_minuendCurY  = new Double((l_slope * l_x3)
    569                        + (l_y1 - (l_slope * l_x1)));
    570
    571                l_minuendXs.add(l_minuendCurX);
    572                l_minuendYs.add(l_minuendCurY);
    573            }
    574
    575            l_minuendMaxY    = l_minuendCurY.doubleValue();
    576            l_minuendMinY    = l_minuendCurY.doubleValue();
    577            l_subtrahendMaxY = l_subtrahendCurY.doubleValue();
    578            l_subtrahendMinY = l_subtrahendCurY.doubleValue();
    579
    580            b_fastForwardDone = true;
    581        }
    582
    583        // start of algorithm
    584        while (!b_minuendDone && !b_subtrahendDone) {
    585            if (!b_minuendDone && !b_minuendFastForward && b_minuendAdvanced) {
    586                l_x1 = x_dataset.getXValue(0, l_minuendItem);
    587                l_y1 = x_dataset.getYValue(0, l_minuendItem);
    588                l_minuendCurX = new Double(l_x1);
    589                l_minuendCurY = new Double(l_y1);
    590
    591                if (!b_minuendAtIntersect) {
    592                    l_minuendXs.add(l_minuendCurX);
    593                    l_minuendYs.add(l_minuendCurY);
    594                }
    595
    596                l_minuendMaxY = Math.max(l_minuendMaxY, l_y1);
    597                l_minuendMinY = Math.min(l_minuendMinY, l_y1);
    598
    599                l_x2 = x_dataset.getXValue(0, l_minuendItem + 1);
    600                l_y2 = x_dataset.getYValue(0, l_minuendItem + 1);
    601                l_minuendNextX = new Double(l_x2);
    602                l_minuendNextY = new Double(l_y2);
    603            }
    604
    605            // never updated the subtrahend if it is implied to be zero
    606            if (!b_impliedZeroSubtrahend && !b_subtrahendDone
    607                    && !b_subtrahendFastForward && b_subtrahendAdvanced) {
    608                l_x3 = x_dataset.getXValue(1, l_subtrahendItem);
    609                l_y3 = x_dataset.getYValue(1, l_subtrahendItem);
    610                l_subtrahendCurX = new Double(l_x3);
    611                l_subtrahendCurY = new Double(l_y3);
    612
    613                if (!b_subtrahendAtIntersect) {
    614                    l_subtrahendXs.add(l_subtrahendCurX);
    615                    l_subtrahendYs.add(l_subtrahendCurY);
    616                }
    617
    618                l_subtrahendMaxY = Math.max(l_subtrahendMaxY, l_y3);
    619                l_subtrahendMinY = Math.min(l_subtrahendMinY, l_y3);
    620
    621                l_x4 = x_dataset.getXValue(1, l_subtrahendItem + 1);
    622                l_y4 = x_dataset.getYValue(1, l_subtrahendItem + 1);
    623                l_subtrahendNextX = new Double(l_x4);
    624                l_subtrahendNextY = new Double(l_y4);
    625            }
    626
    627            // deassert b_*FastForward (only matters for 1st time through loop)
    628            b_minuendFastForward    = false;
    629            b_subtrahendFastForward = false;
    630
    631            Double l_intersectX = null;
    632            Double l_intersectY = null;
    633            boolean b_intersect = false;
    634
    635            b_minuendAtIntersect    = false;
    636            b_subtrahendAtIntersect = false;
    637
    638            // check for intersect
    639            if ((l_x2 == l_x4) && (l_y2 == l_y4)) {
    640                // check if line segments are colinear
    641                if ((l_x1 == l_x3) && (l_y1 == l_y3)) {
    642                    b_colinear = true;
    643                }
    644                else {
    645                    // the intersect is at the next point for both the minuend
    646                    // and subtrahend
    647                    l_intersectX = new Double(l_x2);
    648                    l_intersectY = new Double(l_y2);
    649
    650                    b_intersect             = true;
    651                    b_minuendAtIntersect    = true;
    652                    b_subtrahendAtIntersect = true;
    653                 }
    654            }
    655            else {
    656                // compute common denominator
    657                double l_denominator = ((l_y4 - l_y3) * (l_x2 - l_x1))
    658                        - ((l_x4 - l_x3) * (l_y2 - l_y1));
    659
    660                // compute common deltas
    661                double l_deltaY = l_y1 - l_y3;
    662                double l_deltaX = l_x1 - l_x3;
    663
    664                // compute numerators
    665                double l_numeratorA = ((l_x4 - l_x3) * l_deltaY)
    666                        - ((l_y4 - l_y3) * l_deltaX);
    667                double l_numeratorB = ((l_x2 - l_x1) * l_deltaY)
    668                        - ((l_y2 - l_y1) * l_deltaX);
    669
    670                // check if line segments are colinear
    671                if ((0 == l_numeratorA) && (0 == l_numeratorB)
    672                        && (0 == l_denominator)) {
    673                    b_colinear = true;
    674                }
    675                else {
    676                    // check if previously colinear
    677                    if (b_colinear) {
    678                        // clear colinear points and flag
    679                        l_minuendXs.clear();
    680                        l_minuendYs.clear();
    681                        l_subtrahendXs.clear();
    682                        l_subtrahendYs.clear();
    683                        l_polygonXs.clear();
    684                        l_polygonYs.clear();
    685
    686                        b_colinear = false;
    687
    688                        // set new starting point for the polygon
    689                        boolean b_useMinuend = ((l_x3 <= l_x1)
    690                                && (l_x1 <= l_x4));
    691                        l_polygonXs.add(b_useMinuend ? l_minuendCurX
    692                                : l_subtrahendCurX);
    693                        l_polygonYs.add(b_useMinuend ? l_minuendCurY
    694                                : l_subtrahendCurY);
    695                    }
    696
    697                    // compute slope components
    698                    double l_slopeA = l_numeratorA / l_denominator;
    699                    double l_slopeB = l_numeratorB / l_denominator;
    700
    701                    // check if the line segments intersect
    702                    if ((0 < l_slopeA) && (l_slopeA <= 1) && (0 < l_slopeB)
    703                            && (l_slopeB <= 1)) {
    704                        // compute the point of intersection
    705                        double l_xi = l_x1 + (l_slopeA * (l_x2 - l_x1));
    706                        double l_yi = l_y1 + (l_slopeA * (l_y2 - l_y1));
    707
    708                        l_intersectX            = new Double(l_xi);
    709                        l_intersectY            = new Double(l_yi);
    710                        b_intersect             = true;
    711                        b_minuendAtIntersect    = ((l_xi == l_x2)
    712                                && (l_yi == l_y2));
    713                        b_subtrahendAtIntersect = ((l_xi == l_x4)
    714                                && (l_yi == l_y4));
    715
    716                        // advance minuend and subtrahend to intesect
    717                        l_minuendCurX    = l_intersectX;
    718                        l_minuendCurY    = l_intersectY;
    719                        l_subtrahendCurX = l_intersectX;
    720                        l_subtrahendCurY = l_intersectY;
    721                    }
    722                }
    723            }
    724
    725            if (b_intersect) {
    726                // create the polygon
    727                // add the minuend's points to polygon
    728                l_polygonXs.addAll(l_minuendXs);
    729                l_polygonYs.addAll(l_minuendYs);
    730
    731                // add intersection point to the polygon
    732                l_polygonXs.add(l_intersectX);
    733                l_polygonYs.add(l_intersectY);
    734
    735                // add the subtrahend's points to the polygon in reverse
    736                Collections.reverse(l_subtrahendXs);
    737                Collections.reverse(l_subtrahendYs);
    738                l_polygonXs.addAll(l_subtrahendXs);
    739                l_polygonYs.addAll(l_subtrahendYs);
    740
    741                // create an actual polygon
    742                b_positive = (l_subtrahendMaxY <= l_minuendMaxY)
    743                        && (l_subtrahendMinY <= l_minuendMinY);
    744                createPolygon(x_graphics, x_dataArea, x_plot, x_domainAxis,
    745                        x_rangeAxis, b_positive, l_polygonXs, l_polygonYs);
    746
    747                // clear the point vectors
    748                l_minuendXs.clear();
    749                l_minuendYs.clear();
    750                l_subtrahendXs.clear();
    751                l_subtrahendYs.clear();
    752                l_polygonXs.clear();
    753                l_polygonYs.clear();
    754
    755                // set the maxY and minY values to intersect y-value
    756                double l_y       = l_intersectY.doubleValue();
    757                l_minuendMaxY    = l_y;
    758                l_subtrahendMaxY = l_y;
    759                l_minuendMinY    = l_y;
    760                l_subtrahendMinY = l_y;
    761
    762                // add interection point to new polygon
    763                l_polygonXs.add(l_intersectX);
    764                l_polygonYs.add(l_intersectY);
    765            }
    766
    767            // advance the minuend if needed
    768            if (l_x2 <= l_x4) {
    769                l_minuendItem++;
    770                b_minuendAdvanced = true;
    771            }
    772            else {
    773                b_minuendAdvanced = false;
    774            }
    775
    776            // advance the subtrahend if needed
    777            if (l_x4 <= l_x2) {
    778                l_subtrahendItem++;
    779                b_subtrahendAdvanced = true;
    780            }
    781            else {
    782                b_subtrahendAdvanced = false;
    783            }
    784
    785            b_minuendDone    = (l_minuendItem == (l_minuendItemCount - 1));
    786            b_subtrahendDone = (l_subtrahendItem == (l_subtrahendItemCount
    787                    - 1));
    788        }
    789
    790        // check if the final polygon needs to be clipped
    791        if (b_minuendDone && (l_x3 < l_x2) && (l_x2 < l_x4)) {
    792            // project onto subtrahend
    793            double l_slope    = (l_y4 - l_y3) / (l_x4 - l_x3);
    794            l_subtrahendNextX = l_minuendNextX;
    795            l_subtrahendNextY = new Double((l_slope * l_x2)
    796                    + (l_y3 - (l_slope * l_x3)));
    797        }
    798
    799        if (b_subtrahendDone && (l_x1 < l_x4) && (l_x4 < l_x2)) {
    800            // project onto minuend
    801            double l_slope = (l_y2 - l_y1) / (l_x2 - l_x1);
    802            l_minuendNextX = l_subtrahendNextX;
    803            l_minuendNextY = new Double((l_slope * l_x4)
    804                    + (l_y1 - (l_slope * l_x1)));
    805        }
    806
    807        // consider last point of minuend and subtrahend for determining
    808        // positivity
    809        l_minuendMaxY    = Math.max(l_minuendMaxY,
    810                l_minuendNextY.doubleValue());
    811        l_subtrahendMaxY = Math.max(l_subtrahendMaxY,
    812                l_subtrahendNextY.doubleValue());
    813        l_minuendMinY    = Math.min(l_minuendMinY,
    814                l_minuendNextY.doubleValue());
    815        l_subtrahendMinY = Math.min(l_subtrahendMinY,
    816                l_subtrahendNextY.doubleValue());
    817
    818        // add the last point of the minuned and subtrahend
    819        l_minuendXs.add(l_minuendNextX);
    820        l_minuendYs.add(l_minuendNextY);
    821        l_subtrahendXs.add(l_subtrahendNextX);
    822        l_subtrahendYs.add(l_subtrahendNextY);
    823
    824        // create the polygon
    825        // add the minuend's points to polygon
    826        l_polygonXs.addAll(l_minuendXs);
    827        l_polygonYs.addAll(l_minuendYs);
    828
    829        // add the subtrahend's points to the polygon in reverse
    830        Collections.reverse(l_subtrahendXs);
    831        Collections.reverse(l_subtrahendYs);
    832        l_polygonXs.addAll(l_subtrahendXs);
    833        l_polygonYs.addAll(l_subtrahendYs);
    834
    835        // create an actual polygon
    836        b_positive = (l_subtrahendMaxY <= l_minuendMaxY)
    837                && (l_subtrahendMinY <= l_minuendMinY);
    838        createPolygon(x_graphics, x_dataArea, x_plot, x_domainAxis,
    839                x_rangeAxis, b_positive, l_polygonXs, l_polygonYs);
    840    }
    841
    842    /**
    843     * Draws the visual representation of a single data item, second pass.  In
    844     * the second pass, the renderer draws the lines and shapes for the
    845     * individual points in the two series.
    846     *
    847     * @param x_graphics  the graphics device.
    848     * @param x_dataArea  the area within which the data is being drawn.
    849     * @param x_info  collects information about the drawing.
    850     * @param x_plot  the plot (can be used to obtain standard color
    851     *         information etc).
    852     * @param x_domainAxis  the domain (horizontal) axis.
    853     * @param x_rangeAxis  the range (vertical) axis.
    854     * @param x_dataset  the dataset.
    855     * @param x_series  the series index (zero-based).
    856     * @param x_item  the item index (zero-based).
    857     * @param x_crosshairState  crosshair information for the plot
    858     *                          (<code>null</code> permitted).
    859     */
    860    protected void drawItemPass1(Graphics2D x_graphics,
    861                                 Rectangle2D x_dataArea,
    862                                 PlotRenderingInfo x_info,
    863                                 XYPlot x_plot,
    864                                 ValueAxis x_domainAxis,
    865                                 ValueAxis x_rangeAxis,
    866                                 XYDataset x_dataset,
    867                                 int x_series,
    868                                 int x_item,
    869                                 CrosshairState x_crosshairState) {
    870
    871        Shape l_entityArea = null;
    872        EntityCollection l_entities = null;
    873        if (null != x_info) {
    874            l_entities = x_info.getOwner().getEntityCollection();
    875        }
    876
    877        Paint l_seriesPaint   = getItemPaint(x_series, x_item);
    878        Stroke l_seriesStroke = getItemStroke(x_series, x_item);
    879        x_graphics.setPaint(l_seriesPaint);
    880        x_graphics.setStroke(l_seriesStroke);
    881
    882        PlotOrientation l_orientation      = x_plot.getOrientation();
    883        RectangleEdge l_domainAxisLocation = x_plot.getDomainAxisEdge();
    884        RectangleEdge l_rangeAxisLocation  = x_plot.getRangeAxisEdge();
    885
    886        double l_x0 = x_dataset.getXValue(x_series, x_item);
    887        double l_y0 = x_dataset.getYValue(x_series, x_item);
    888        double l_x1 = x_domainAxis.valueToJava2D(l_x0, x_dataArea,
    889                l_domainAxisLocation);
    890        double l_y1 = x_rangeAxis.valueToJava2D(l_y0, x_dataArea,
    891                l_rangeAxisLocation);
    892
    893        if (getShapesVisible()) {
    894            Shape l_shape = getItemShape(x_series, x_item);
    895            if (l_orientation == PlotOrientation.HORIZONTAL) {
    896                l_shape = ShapeUtilities.createTranslatedShape(l_shape,
    897                        l_y1, l_x1);
    898            }
    899            else {
    900                l_shape = ShapeUtilities.createTranslatedShape(l_shape,
    901                        l_x1, l_y1);
    902            }
    903            if (l_shape.intersects(x_dataArea)) {
    904                x_graphics.setPaint(getItemPaint(x_series, x_item));
    905                x_graphics.fill(l_shape);
    906            }
    907            l_entityArea = l_shape;
    908        }
    909
    910        // add an entity for the item...
    911        if (null != l_entities) {
    912            if (null == l_entityArea) {
    913                l_entityArea = new Rectangle2D.Double((l_x1 - 2), (l_y1 - 2),
    914                        4, 4);
    915            }
    916            String l_tip = null;
    917            XYToolTipGenerator l_tipGenerator = getToolTipGenerator(x_series,
    918                    x_item);
    919            if (null != l_tipGenerator) {
    920                l_tip = l_tipGenerator.generateToolTip(x_dataset, x_series,
    921                        x_item);
    922            }
    923            String l_url = null;
    924            XYURLGenerator l_urlGenerator = getURLGenerator();
    925            if (null != l_urlGenerator) {
    926                l_url = l_urlGenerator.generateURL(x_dataset, x_series,
    927                        x_item);
    928            }
    929            XYItemEntity l_entity = new XYItemEntity(l_entityArea, x_dataset,
    930                    x_series, x_item, l_tip, l_url);
    931            l_entities.add(l_entity);
    932        }
    933
    934        // draw the item label if there is one...
    935        if (isItemLabelVisible(x_series, x_item)) {
    936            drawItemLabel(x_graphics, l_orientation, x_dataset, x_series,
    937                          x_item, l_x1, l_y1, (l_y1 < 0.0));
    938        }
    939
    940        int l_domainAxisIndex = x_plot.getDomainAxisIndex(x_domainAxis);
    941        int l_rangeAxisIndex  = x_plot.getRangeAxisIndex(x_rangeAxis);
    942        updateCrosshairValues(x_crosshairState, l_x0, l_y0, l_domainAxisIndex,
    943                              l_rangeAxisIndex, l_x1, l_y1, l_orientation);
    944
    945        if (0 == x_item) {
    946            return;
    947        }
    948
    949        double l_x2 = x_domainAxis.valueToJava2D(x_dataset.getXValue(x_series,
    950                (x_item - 1)), x_dataArea, l_domainAxisLocation);
    951        double l_y2 = x_rangeAxis.valueToJava2D(x_dataset.getYValue(x_series,
    952                (x_item - 1)), x_dataArea, l_rangeAxisLocation);
    953
    954        Line2D l_line = null;
    955        if (PlotOrientation.HORIZONTAL == l_orientation) {
    956            l_line = new Line2D.Double(l_y1, l_x1, l_y2, l_x2);
    957        }
    958        else if (PlotOrientation.VERTICAL == l_orientation) {
    959            l_line = new Line2D.Double(l_x1, l_y1, l_x2, l_y2);
    960        }
    961
    962        if ((null != l_line) && l_line.intersects(x_dataArea)) {
    963            x_graphics.setPaint(getItemPaint(x_series, x_item));
    964            x_graphics.setStroke(getItemStroke(x_series, x_item));
    965            x_graphics.draw(l_line);
    966        }
    967    }
    968
    969    /**
    970     * Determines if a dataset is degenerate.  A degenerate dataset is a
    971     * dataset where either series has less than two (2) points.
    972     *
    973     * @param x_dataset  the dataset.
    974     * @param x_impliedZeroSubtrahend  if false, do not check the subtrahend
    975     *
    976     * @return true if the dataset is degenerate.
    977     */
    978    private boolean isEitherSeriesDegenerate(XYDataset x_dataset,
    979            boolean x_impliedZeroSubtrahend) {
    980
    981        if (x_impliedZeroSubtrahend) {
    982            return (x_dataset.getItemCount(0) < 2);
    983        }
    984
    985        return ((x_dataset.getItemCount(0) < 2)
    986                || (x_dataset.getItemCount(1) < 2));
    987    }
    988
    989    /**
    990     * Determines if the two (2) series are disjoint.
    991     * Disjoint series do not overlap in the domain space.
    992     *
    993     * @param x_dataset  the dataset.
    994     *
    995     * @return true if the dataset is degenerate.
    996     */
    997    private boolean areSeriesDisjoint(XYDataset x_dataset) {
    998
    999        int l_minuendItemCount = x_dataset.getItemCount(0);
    1000        double l_minuendFirst  = x_dataset.getXValue(0, 0);
    1001        double l_minuendLast   = x_dataset.getXValue(0, l_minuendItemCount - 1);
    1002
    1003        int l_subtrahendItemCount = x_dataset.getItemCount(1);
    1004        double l_subtrahendFirst  = x_dataset.getXValue(1, 0);
    1005        double l_subtrahendLast   = x_dataset.getXValue(1,
    1006                l_subtrahendItemCount - 1);
    1007
    1008        return ((l_minuendLast < l_subtrahendFirst)
    1009                || (l_subtrahendLast < l_minuendFirst));
    1010    }
    1011
    1012    /**
    1013     * Draws the visual representation of a polygon
    1014     *
    1015     * @param x_graphics  the graphics device.
    1016     * @param x_dataArea  the area within which the data is being drawn.
    1017     * @param x_plot  the plot (can be used to obtain standard color
    1018     *                information etc).
    1019     * @param x_domainAxis  the domain (horizontal) axis.
    1020     * @param x_rangeAxis  the range (vertical) axis.
    1021     * @param x_positive  indicates if the polygon is positive (true) or
    1022     *                    negative (false).
    1023     * @param x_xValues  a linked list of the x values (expects values to be
    1024     *                   of type Double).
    1025     * @param x_yValues  a linked list of the y values (expects values to be
    1026     *                   of type Double).
    1027     */
    1028    private void createPolygon (Graphics2D x_graphics,
    1029                                Rectangle2D x_dataArea,
    1030                                XYPlot x_plot,
    1031                                ValueAxis x_domainAxis,
    1032                                ValueAxis x_rangeAxis,
    1033                                boolean x_positive,
    1034                                LinkedList x_xValues,
    1035                                LinkedList x_yValues) {
    1036
    1037        PlotOrientation l_orientation      = x_plot.getOrientation();
    1038        RectangleEdge l_domainAxisLocation = x_plot.getDomainAxisEdge();
    1039        RectangleEdge l_rangeAxisLocation  = x_plot.getRangeAxisEdge();
    1040
    1041        Object[] l_xValues = x_xValues.toArray();
    1042        Object[] l_yValues = x_yValues.toArray();
    1043
    1044        GeneralPath l_path = new GeneralPath();
    1045
    1046        if (PlotOrientation.VERTICAL == l_orientation) {
    1047            double l_x = x_domainAxis.valueToJava2D((
    1048                    (Double) l_xValues[0]).doubleValue(), x_dataArea,
    1049                    l_domainAxisLocation);
    1050            if (this.roundXCoordinates) {
    1051                l_x = Math.rint(l_x);
    1052            }
    1053
    1054            double l_y = x_rangeAxis.valueToJava2D((
    1055                    (Double) l_yValues[0]).doubleValue(), x_dataArea,
    1056                    l_rangeAxisLocation);
    1057
    1058            l_path.moveTo((float) l_x, (float) l_y);
    1059            for (int i = 1; i < l_xValues.length; i++) {
    1060                l_x = x_domainAxis.valueToJava2D((
    1061                        (Double) l_xValues[i]).doubleValue(), x_dataArea,
    1062                        l_domainAxisLocation);
    1063                if (this.roundXCoordinates) {
    1064                    l_x = Math.rint(l_x);
    1065                }
    1066
    1067                l_y = x_rangeAxis.valueToJava2D((
    1068                        (Double) l_yValues[i]).doubleValue(), x_dataArea,
    1069                        l_rangeAxisLocation);
    1070                l_path.lineTo((float) l_x, (float) l_y);
    1071            }
    1072            l_path.closePath();
    1073        }
    1074        else {
    1075            double l_x = x_domainAxis.valueToJava2D((
    1076                    (Double) l_xValues[0]).doubleValue(), x_dataArea,
    1077                    l_domainAxisLocation);
    1078            if (this.roundXCoordinates) {
    1079                l_x = Math.rint(l_x);
    1080            }
    1081
    1082            double l_y = x_rangeAxis.valueToJava2D((
    1083                    (Double) l_yValues[0]).doubleValue(), x_dataArea,
    1084                    l_rangeAxisLocation);
    1085
    1086            l_path.moveTo((float) l_y, (float) l_x);
    1087            for (int i = 1; i < l_xValues.length; i++) {
    1088                l_x = x_domainAxis.valueToJava2D((
    1089                        (Double) l_xValues[i]).doubleValue(), x_dataArea,
    1090                        l_domainAxisLocation);
    1091                if (this.roundXCoordinates) {
    1092                    l_x = Math.rint(l_x);
    1093                }
    1094
    1095                l_y = x_rangeAxis.valueToJava2D((
    1096                        (Double) l_yValues[i]).doubleValue(), x_dataArea,
    1097                        l_rangeAxisLocation);
    1098                l_path.lineTo((float) l_y, (float) l_x);
    1099            }
    1100            l_path.closePath();
    1101        }
    1102
    1103        if (l_path.intersects(x_dataArea)) {
    1104            x_graphics.setPaint(x_positive ? getPositivePaint()
    1105                    : getNegativePaint());
    1106            x_graphics.fill(l_path);
    1107        }
    1108    }
    1109
    1110    /**
    1111     * Returns a default legend item for the specified series.  Subclasses
    1112     * should override this method to generate customised items.
    1113     *
    1114     * @param datasetIndex  the dataset index (zero-based).
    1115     * @param series  the series index (zero-based).
    1116     *
    1117     * @return A legend item for the series.
    1118     */
    1119    public LegendItem getLegendItem(int datasetIndex, int series) {
    1120        LegendItem result = null;
    1121        XYPlot p = getPlot();
    1122        if (p != null) {
    1123            XYDataset dataset = p.getDataset(datasetIndex);
    1124            if (dataset != null) {
    1125                if (getItemVisible(series, 0)) {
    1126                    String label = getLegendItemLabelGenerator().generateLabel(
    1127                            dataset, series);
    1128                    String description = label;
    1129                    String toolTipText = null;
    1130                    if (getLegendItemToolTipGenerator() != null) {
    1131                        toolTipText
    1132                            = getLegendItemToolTipGenerator().generateLabel(
    1133                                    dataset, series);
    1134                    }
    1135                    String urlText = null;
    1136                    if (getLegendItemURLGenerator() != null) {
    1137                        urlText = getLegendItemURLGenerator().generateLabel(
    1138                                dataset, series);
    1139                    }
    1140                    Paint paint = lookupSeriesPaint(series);
    1141                    Stroke stroke = lookupSeriesStroke(series);
    1142                    Shape line = getLegendLine();
    1143                    result = new LegendItem(label, description,
    1144                            toolTipText, urlText, line, stroke, paint);
    1145                    result.setLabelFont(lookupLegendTextFont(series));
    1146                    Paint labelPaint = lookupLegendTextPaint(series);
    1147                    if (labelPaint != null) {
    1148                        result.setLabelPaint(labelPaint);
    1149                    }
    1150                    result.setDataset(dataset);
    1151                    result.setDatasetIndex(datasetIndex);
    1152                    result.setSeriesKey(dataset.getSeriesKey(series));
    1153                    result.setSeriesIndex(series);
    1154                }
    1155            }
    1156
    1157        }
    1158
    1159        return result;
    1160
    1161    }
    1162
    1163    /**
    1164     * Tests this renderer for equality with an arbitrary object.
    1165     *
    1166     * @param obj  the object (<code>null</code> permitted).
    1167     *
    1168     * @return A boolean.
    1169     */
    1170    public boolean equals(Object obj) {
    1171        if (obj == this) {
    1172            return true;
    1173        }
    1174        if (!(obj instanceof XYDifferenceRenderer)) {
    1175            return false;
    1176        }
    1177        if (!super.equals(obj)) {
    1178            return false;
    1179        }
    1180        XYDifferenceRenderer that = (XYDifferenceRenderer) obj;
    1181        if (!PaintUtilities.equal(this.positivePaint, that.positivePaint)) {
    1182            return false;
    1183        }
    1184        if (!PaintUtilities.equal(this.negativePaint, that.negativePaint)) {
    1185            return false;
    1186        }
    1187        if (this.shapesVisible != that.shapesVisible) {
    1188            return false;
    1189        }
    1190        if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) {
    1191            return false;
    1192        }
    1193        if (this.roundXCoordinates != that.roundXCoordinates) {
    1194            return false;
    1195        }
    1196        return true;
    1197    }
    1198
    1199    /**
    1200     * Returns a clone of the renderer.
    1201     *
    1202     * @return A clone.
    1203     *
    1204     * @throws CloneNotSupportedException  if the renderer cannot be cloned.
    1205     */
    1206    public Object clone() throws CloneNotSupportedException {
    1207        XYDifferenceRenderer clone = (XYDifferenceRenderer) super.clone();
    1208        clone.legendLine = ShapeUtilities.clone(this.legendLine);
    1209        return clone;
    1210    }
    1211
    1212    /**
    1213     * Provides serialization support.
    1214     *
    1215     * @param stream  the output stream.
    1216     *
    1217     * @throws IOException  if there is an I/O error.
    1218     */
    1219    private void writeObject(ObjectOutputStream stream) throws IOException {
    1220        stream.defaultWriteObject();
    1221        SerialUtilities.writePaint(this.positivePaint, stream);
    1222        SerialUtilities.writePaint(this.negativePaint, stream);
    1223        SerialUtilities.writeShape(this.legendLine, stream);
    1224    }
    1225
    1226    /**
    1227     * Provides serialization support.
    1228     *
    1229     * @param stream  the input stream.
    1230     *
    1231     * @throws IOException  if there is an I/O error.
    1232     * @throws ClassNotFoundException  if there is a classpath problem.
    1233     */
    1234    private void readObject(ObjectInputStream stream)
    1235        throws IOException, ClassNotFoundException {
    1236        stream.defaultReadObject();
    1237        this.positivePaint = SerialUtilities.readPaint(stream);
    1238        this.negativePaint = SerialUtilities.readPaint(stream);
    1239        this.legendLine = SerialUtilities.readShape(stream);
    1240    }
    1241
    1242}