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