    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
    077    package org.jfree.chart.renderer.xy;
    078
    079    import java.awt.Color;
    080    import java.awt.Graphics2D;
    081    import java.awt.Paint;
    082    import java.awt.Shape;
    083    import java.awt.Stroke;
    084    import java.awt.geom.GeneralPath;
    085    import java.awt.geom.Line2D;
    086    import java.awt.geom.Rectangle2D;
    087    import java.io.IOException;
    088    import java.io.ObjectInputStream;
    089    import java.io.ObjectOutputStream;
    090    import java.util.Collections;
    091    import java.util.LinkedList;
    092
    093    import org.jfree.chart.LegendItem;
    094    import org.jfree.chart.axis.ValueAxis;
    095    import org.jfree.chart.entity.EntityCollection;
    096    import org.jfree.chart.entity.XYItemEntity;
    097    import org.jfree.chart.event.RendererChangeEvent;
    098    import org.jfree.chart.labels.XYToolTipGenerator;
    099    import org.jfree.chart.plot.CrosshairState;
    100    import org.jfree.chart.plot.PlotOrientation;
    101    import org.jfree.chart.plot.PlotRenderingInfo;
    102    import org.jfree.chart.plot.XYPlot;
    103    import org.jfree.chart.urls.XYURLGenerator;
    104    import org.jfree.data.xy.XYDataset;
    105    import org.jfree.io.SerialUtilities;
    106    import org.jfree.ui.RectangleEdge;
    107    import org.jfree.util.PaintUtilities;
    108    import org.jfree.util.PublicCloneable;
    109    import 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     */
    120    public 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    }