    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2005, 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-2005, by Object Refinery Limited.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   Christian W. Zuckschwerdt;
    034     *
    035     * $Id: XYDifferenceRenderer.java,v 1.12.2.5 2005/11/28 12:06:35 mungady Exp $
    036     *
    037     * Changes:
    038     * --------
    039     * 30-Apr-2003 : Version 1 (DG);
    040     * 30-Jul-2003 : Modified entity constructor (CZ);
    041     * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
    042     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
    043     * 09-Feb-2004 : Updated to support horizontal plot orientation (DG);
    044     * 10-Feb-2004 : Added default constructor, setter methods and updated
    045     *               Javadocs (DG);
    046     * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG);
    047     * 30-Mar-2004 : Fixed bug in getNegativePaint() method (DG);
    048     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
    049     *               getYValue() (DG);
    050     * 25-Aug-2004 : Fixed a bug preventing the use of crosshairs (DG);
    051     * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
    052     * 19-Jan-2005 : Now accesses only primitive values from dataset (DG);
    053     * 22-Feb-2005 : Override getLegendItem(int, int) to return "line" items (DG);
    054     * 13-Apr-2005 : Fixed shape positioning bug (id = 1182062) (DG);
    055     * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
    056     * 04-May-2005 : Override equals() method, renamed get/setPlotShapes() -->
    057     *               get/setShapesVisible (DG);
    058     * 09-Jun-2005 : Updated equals() to handle GradientPaint (DG);
    059     * 16-Jun-2005 : Fix bug (1221021) affecting stroke used for each series (DG);
    060     *
    061     */
    062
    063    package org.jfree.chart.renderer.xy;
    064
    065    import java.awt.Color;
    066    import java.awt.Graphics2D;
    067    import java.awt.Paint;
    068    import java.awt.Shape;
    069    import java.awt.Stroke;
    070    import java.awt.geom.GeneralPath;
    071    import java.awt.geom.Line2D;
    072    import java.awt.geom.Rectangle2D;
    073    import java.io.IOException;
    074    import java.io.ObjectInputStream;
    075    import java.io.ObjectOutputStream;
    076    import java.io.Serializable;
    077
    078    import org.jfree.chart.LegendItem;
    079    import org.jfree.chart.axis.ValueAxis;
    080    import org.jfree.chart.entity.EntityCollection;
    081    import org.jfree.chart.entity.XYItemEntity;
    082    import org.jfree.chart.event.RendererChangeEvent;
    083    import org.jfree.chart.labels.XYToolTipGenerator;
    084    import org.jfree.chart.plot.CrosshairState;
    085    import org.jfree.chart.plot.PlotOrientation;
    086    import org.jfree.chart.plot.PlotRenderingInfo;
    087    import org.jfree.chart.plot.XYPlot;
    088    import org.jfree.data.xy.XYDataset;
    089    import org.jfree.io.SerialUtilities;
    090    import org.jfree.ui.RectangleEdge;
    091    import org.jfree.util.PaintUtilities;
    092    import org.jfree.util.PublicCloneable;
    093    import org.jfree.util.ShapeUtilities;
    094
    095    /**
    096     * A renderer for an {@link XYPlot} that highlights the differences between two
    097     * series.  The renderer expects a dataset that:
    098     * <ul>
    099     * <li>has exactly two series;</li>
    100     * <li>each series has the same x-values;</li>
    101     * <li>no <code>null</code> values;
    102     * </ul>
    103     */
    104    public class XYDifferenceRenderer extends AbstractXYItemRenderer
    105                                      implements XYItemRenderer,
    106                                                 Cloneable,
    107                                                 PublicCloneable,
    108                                                 Serializable {
    109
    110        /** For serialization. */
    111        private static final long serialVersionUID = -8447915602375584857L;
    112
    113        /** The paint used to highlight positive differences (y(0) > y(1)). */
    114        private transient Paint positivePaint;
    115
    116        /** The paint used to highlight negative differences (y(0) < y(1)). */
    117        private transient Paint negativePaint;
    118
    119        /** Display shapes at each point? */
    120        private boolean shapesVisible;
    121
    122        /** The shape to display in the legend item. */
    123        private transient Shape legendLine;
    124
    125        /**
    126         * Creates a new renderer with default attributes.
    127         */
    128        public XYDifferenceRenderer() {
    129            this(Color.green, Color.red, false);
    130        }
    131
    132        /**
    133         * Creates a new renderer.
    134         *
    135         * @param positivePaint  the highlight color for positive differences
    136         *                       (<code>null</code> not permitted).
    137         * @param negativePaint  the highlight color for negative differences
    138         *                       (<code>null</code> not permitted).
    139         * @param shapes  draw shapes?
    140         */
    141        public XYDifferenceRenderer(Paint positivePaint, Paint negativePaint,
    142                                    boolean shapes) {
    143            if (positivePaint == null) {
    144                throw new IllegalArgumentException(
    145                    "Null 'positivePaint' argument."
    146                );
    147            }
    148            if (negativePaint == null) {
    149                throw new IllegalArgumentException(
    150                    "Null 'negativePaint' argument."
    151                );
    152            }
    153            this.positivePaint = positivePaint;
    154            this.negativePaint = negativePaint;
    155            this.shapesVisible = shapes;
    156            this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
    157        }
    158
    159        /**
    160         * Returns the paint used to highlight positive differences.
    161         *
    162         * @return The paint (never <code>null</code>).
    163         */
    164        public Paint getPositivePaint() {
    165            return this.positivePaint;
    166        }
    167
    168        /**
    169         * Sets the paint used to highlight positive differences.
    170         *
    171         * @param paint  the paint (<code>null</code> not permitted).
    172         */
    173        public void setPositivePaint(Paint paint) {
    174            if (paint == null) {
    175                throw new IllegalArgumentException("Null 'paint' argument.");
    176            }
    177            this.positivePaint = paint;
    178            notifyListeners(new RendererChangeEvent(this));
    179        }
    180
    181        /**
    182         * Returns the paint used to highlight negative differences.
    183         *
    184         * @return The paint (never <code>null</code>).
    185         */
    186        public Paint getNegativePaint() {
    187            return this.negativePaint;
    188        }
    189
    190        /**
    191         * Sets the paint used to highlight negative differences.
    192         *
    193         * @param paint  the paint (<code>null</code> not permitted).
    194         */
    195        public void setNegativePaint(Paint paint) {
    196            if (paint == null) {
    197                throw new IllegalArgumentException("Null 'paint' argument.");
    198            }
    199            this.negativePaint = paint;
    200            notifyListeners(new RendererChangeEvent(this));
    201        }
    202
    203        /**
    204         * Returns a flag that controls whether or not shapes are drawn for each
    205         * data value.
    206         *
    207         * @return A boolean.
    208         */
    209        public boolean getShapesVisible() {
    210            return this.shapesVisible;
    211        }
    212
    213        /**
    214         * Sets a flag that controls whether or not shapes are drawn for each
    215         * data value.
    216         *
    217         * @param flag  the flag.
    218         */
    219        public void setShapesVisible(boolean flag) {
    220            this.shapesVisible = flag;
    221            notifyListeners(new RendererChangeEvent(this));
    222        }
    223
    224        /**
    225         * Returns the shape used to represent a line in the legend.
    226         *
    227         * @return The legend line (never <code>null</code>).
    228         */
    229        public Shape getLegendLine() {
    230            return this.legendLine;
    231        }
    232
    233        /**
    234         * Sets the shape used as a line in each legend item and sends a
    235         * {@link RendererChangeEvent} to all registered listeners.
    236         *
    237         * @param line  the line (<code>null</code> not permitted).
    238         */
    239        public void setLegendLine(Shape line) {
    240            if (line == null) {
    241                throw new IllegalArgumentException("Null 'line' argument.");
    242            }
    243            this.legendLine = line;
    244            notifyListeners(new RendererChangeEvent(this));
    245        }
    246
    247        /**
    248         * Initialises the renderer and returns a state object that should be
    249         * passed to subsequent calls to the drawItem() method.  This method will
    250         * be called before the first item is rendered, giving the renderer an
    251         * opportunity to initialise any state information it wants to maintain.
    252         * The renderer can do nothing if it chooses.
    253         *
    254         * @param g2  the graphics device.
    255         * @param dataArea  the area inside the axes.
    256         * @param plot  the plot.
    257         * @param data  the data.
    258         * @param info  an optional info collection object to return data back to
    259         *              the caller.
    260         *
    261         * @return A state object.
    262         */
    263        public XYItemRendererState initialise(Graphics2D g2,
    264                                              Rectangle2D dataArea,
    265                                              XYPlot plot,
    266                                              XYDataset data,
    267                                              PlotRenderingInfo info) {
    268
    269            return super.initialise(g2, dataArea, plot, data, info);
    270
    271        }
    272
    273        /**
    274         * Returns <code>2</code>, the number of passes required by the renderer.
    275         * The {@link XYPlot} will run through the dataset this number of times.
    276         *
    277         * @return The number of passes required by the renderer.
    278         */
    279        public int getPassCount() {
    280            return 2;
    281        }
    282
    283        /**
    284         * Draws the visual representation of a single data item.
    285         *
    286         * @param g2  the graphics device.
    287         * @param state  the renderer state.
    288         * @param dataArea  the area within which the data is being drawn.
    289         * @param info  collects information about the drawing.
    290         * @param plot  the plot (can be used to obtain standard color
    291         *              information etc).
    292         * @param domainAxis  the domain (horizontal) axis.
    293         * @param rangeAxis  the range (vertical) axis.
    294         * @param dataset  the dataset.
    295         * @param series  the series index (zero-based).
    296         * @param item  the item index (zero-based).
    297         * @param crosshairState  crosshair information for the plot
    298         *                        (<code>null</code> permitted).
    299         * @param pass  the pass index.
    300         */
    301        public void drawItem(Graphics2D g2,
    302                             XYItemRendererState state,
    303                             Rectangle2D dataArea,
    304                             PlotRenderingInfo info,
    305                             XYPlot plot,
    306                             ValueAxis domainAxis,
    307                             ValueAxis rangeAxis,
    308                             XYDataset dataset,
    309                             int series,
    310                             int item,
    311                             CrosshairState crosshairState,
    312                             int pass) {
    313
    314            if (pass == 0) {
    315                drawItemPass0(
    316                    g2, dataArea, info, plot, domainAxis, rangeAxis, dataset,
    317                    series, item, crosshairState
    318                );
    319            }
    320            else if (pass == 1) {
    321                drawItemPass1(
    322                    g2, dataArea, info, plot, domainAxis, rangeAxis, dataset,
    323                    series, item, crosshairState
    324                );
    325            }
    326
    327        }
    328
    329        /**
    330         * Draws the visual representation of a single data item, first pass.
    331         *
    332         * @param g2  the graphics device.
    333         * @param dataArea  the area within which the data is being drawn.
    334         * @param info  collects information about the drawing.
    335         * @param plot  the plot (can be used to obtain standard color
    336         *              information etc).
    337         * @param domainAxis  the domain (horizontal) axis.
    338         * @param rangeAxis  the range (vertical) axis.
    339         * @param dataset  the dataset.
    340         * @param series  the series index (zero-based).
    341         * @param item  the item index (zero-based).
    342         * @param crosshairState  crosshair information for the plot
    343         *                        (<code>null</code> permitted).
    344         */
    345        protected void drawItemPass0(Graphics2D g2,
    346                                     Rectangle2D dataArea,
    347                                     PlotRenderingInfo info,
    348                                     XYPlot plot,
    349                                     ValueAxis domainAxis,
    350                                     ValueAxis rangeAxis,
    351                                     XYDataset dataset,
    352                                     int series,
    353                                     int item,
    354                                     CrosshairState crosshairState) {
    355
    356            if (series == 0) {
    357
    358                PlotOrientation orientation = plot.getOrientation();
    359                RectangleEdge domainAxisLocation = plot.getDomainAxisEdge();
    360                RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();
    361
    362                double y0 = dataset.getYValue(0, item);
    363                double x1 = dataset.getXValue(1, item);
    364                double y1 = dataset.getYValue(1, item);
    365
    366                double transY0 = rangeAxis.valueToJava2D(
    367                    y0, dataArea, rangeAxisLocation
    368                );
    369                double transX1 = domainAxis.valueToJava2D(
    370                    x1, dataArea, domainAxisLocation
    371                );
    372                double transY1 = rangeAxis.valueToJava2D(
    373                    y1, dataArea, rangeAxisLocation
    374                );
    375
    376                if (item > 0) {
    377                    double prevx0 = dataset.getXValue(0, item - 1);
    378                    double prevy0 = dataset.getYValue(0, item - 1);
    379                    double prevy1 = dataset.getYValue(1, item - 1);
    380
    381                    double prevtransX0 = domainAxis.valueToJava2D(
    382                        prevx0, dataArea, domainAxisLocation
    383                    );
    384                    double prevtransY0 = rangeAxis.valueToJava2D(
    385                        prevy0, dataArea, rangeAxisLocation
    386                    );
    387                    double prevtransY1 = rangeAxis.valueToJava2D(
    388                        prevy1, dataArea, rangeAxisLocation
    389                    );
    390
    391                    Shape positive = getPositiveArea(
    392                        (float) prevtransX0, (float) prevtransY0,
    393                        (float) prevtransY1,
    394                        (float) transX1, (float) transY0, (float) transY1,
    395                        orientation
    396                    );
    397                    if (positive != null) {
    398                        g2.setPaint(getPositivePaint());
    399                        g2.fill(positive);
    400                    }
    401
    402                    Shape negative = getNegativeArea(
    403                        (float) prevtransX0, (float) prevtransY0,
    404                        (float) prevtransY1,
    405                        (float) transX1, (float) transY0, (float) transY1,
    406                        orientation
    407                    );
    408
    409                    if (negative != null) {
    410                        g2.setPaint(getNegativePaint());
    411                        g2.fill(negative);
    412                    }
    413                }
    414            }
    415
    416        }
    417
    418        /**
    419         * Draws the visual representation of a single data item, second pass.  In
    420         * the second pass, the renderer draws the lines and shapes for the
    421         * individual points in the two series.
    422         *
    423         * @param g2  the graphics device.
    424         * @param dataArea  the area within which the data is being drawn.
    425         * @param info  collects information about the drawing.
    426         * @param plot  the plot (can be used to obtain standard color information
    427         *              etc).
    428         * @param domainAxis  the domain (horizontal) axis.
    429         * @param rangeAxis  the range (vertical) axis.
    430         * @param dataset  the dataset.
    431         * @param series  the series index (zero-based).
    432         * @param item  the item index (zero-based).
    433         * @param crosshairState  crosshair information for the plot
    434         *                        (<code>null</code> permitted).
    435         */
    436        protected void drawItemPass1(Graphics2D g2,
    437                                     Rectangle2D dataArea,
    438                                     PlotRenderingInfo info,
    439                                     XYPlot plot,
    440                                     ValueAxis domainAxis,
    441                                     ValueAxis rangeAxis,
    442                                     XYDataset dataset,
    443                                     int series,
    444                                     int item,
    445                                     CrosshairState crosshairState) {
    446
    447            Shape entityArea = null;
    448            EntityCollection entities = null;
    449            if (info != null) {
    450                entities = info.getOwner().getEntityCollection();
    451            }
    452
    453            Paint seriesPaint = getItemPaint(series, item);
    454            Stroke seriesStroke = getItemStroke(series, item);
    455            g2.setPaint(seriesPaint);
    456            g2.setStroke(seriesStroke);
    457
    458            if (series == 0) {
    459
    460                PlotOrientation orientation = plot.getOrientation();
    461                RectangleEdge domainAxisLocation = plot.getDomainAxisEdge();
    462                RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();
    463
    464                double x0 = dataset.getXValue(0, item);
    465                double y0 = dataset.getYValue(0, item);
    466                double x1 = dataset.getXValue(1, item);
    467                double y1 = dataset.getYValue(1, item);
    468
    469                double transX0 = domainAxis.valueToJava2D(
    470                    x0, dataArea, domainAxisLocation
    471                );
    472                double transY0 = rangeAxis.valueToJava2D(
    473                    y0, dataArea, rangeAxisLocation
    474                );
    475                double transX1 = domainAxis.valueToJava2D(
    476                    x1, dataArea, domainAxisLocation
    477                );
    478                double transY1 = rangeAxis.valueToJava2D(
    479                    y1, dataArea, rangeAxisLocation
    480                );
    481
    482                if (item > 0) {
    483                    // get the previous data points...
    484                    double prevx0 = dataset.getXValue(0, item - 1);
    485                    double prevy0 = dataset.getYValue(0, item - 1);
    486                    double prevx1 = dataset.getXValue(1, item - 1);
    487                    double prevy1 = dataset.getYValue(1, item - 1);
    488
    489                    double prevtransX0 = domainAxis.valueToJava2D(
    490                        prevx0, dataArea, domainAxisLocation
    491                    );
    492                    double prevtransY0 = rangeAxis.valueToJava2D(
    493                        prevy0, dataArea, rangeAxisLocation
    494                    );
    495                    double prevtransX1 = domainAxis.valueToJava2D(
    496                        prevx1, dataArea, domainAxisLocation
    497                    );
    498                    double prevtransY1 = rangeAxis.valueToJava2D(
    499                        prevy1, dataArea, rangeAxisLocation
    500                    );
    501
    502                    Line2D line0 = null;
    503                    Line2D line1 = null;
    504                    if (orientation == PlotOrientation.HORIZONTAL) {
    505                        line0 = new Line2D.Double(
    506                            transY0, transX0, prevtransY0, prevtransX0
    507                        );
    508                        line1 = new Line2D.Double(
    509                            transY1, transX1, prevtransY1, prevtransX1
    510                        );
    511                    }
    512                    else if (orientation == PlotOrientation.VERTICAL) {
    513                        line0 = new Line2D.Double(
    514                            transX0, transY0, prevtransX0, prevtransY0
    515                        );
    516                        line1 = new Line2D.Double(
    517                            transX1, transY1, prevtransX1, prevtransY1
    518                        );
    519                    }
    520                    if (line0 != null && line0.intersects(dataArea)) {
    521                        g2.setPaint(getItemPaint(series, item));
    522                        g2.setStroke(getItemStroke(series, item));
    523                        g2.draw(line0);
    524                    }
    525                    if (line1 != null && line1.intersects(dataArea)) {
    526                        g2.setPaint(getItemPaint(1, item));
    527                        g2.setStroke(getItemStroke(1, item));
    528                        g2.draw(line1);
    529                    }
    530                }
    531
    532                if (getShapesVisible()) {
    533                    Shape shape0 = getItemShape(series, item);
    534                    if (orientation == PlotOrientation.HORIZONTAL) {
    535                        shape0 = ShapeUtilities.createTranslatedShape(
    536                            shape0, transY0, transX0
    537                        );
    538                    }
    539                    else {  // vertical
    540                        shape0 = ShapeUtilities.createTranslatedShape(
    541                            shape0, transX0, transY0
    542                        );
    543                    }
    544                    if (shape0.intersects(dataArea)) {
    545                        g2.setPaint(getItemPaint(series, item));
    546                        g2.fill(shape0);
    547                    }
    548                    entityArea = shape0;
    549
    550                    // add an entity for the item...
    551                    if (entities != null) {
    552                        if (entityArea == null) {
    553                            entityArea = new Rectangle2D.Double(
    554                                transX0 - 2, transY0 - 2, 4, 4
    555                            );
    556                        }
    557                        String tip = null;
    558                        XYToolTipGenerator generator = getToolTipGenerator(
    559                            series, item
    560                        );
    561                        if (generator != null) {
    562                            tip = generator.generateToolTip(dataset, series, item);
    563                        }
    564                        String url = null;
    565                        if (getURLGenerator() != null) {
    566                            url = getURLGenerator().generateURL(
    567                                dataset, series, item
    568                            );
    569                        }
    570                        XYItemEntity entity = new XYItemEntity(
    571                            entityArea, dataset, series, item, tip, url
    572                        );
    573                        entities.add(entity);
    574                    }
    575
    576                    Shape shape1 = getItemShape(series + 1, item);
    577                    if (orientation == PlotOrientation.HORIZONTAL) {
    578                        shape1 = ShapeUtilities.createTranslatedShape(
    579                            shape1, transY1, transX1
    580                        );
    581                    }
    582                    else {  // vertical
    583                        shape1 = ShapeUtilities.createTranslatedShape(
    584                            shape1, transX1, transY1
    585                        );
    586                    }
    587                    if (shape1.intersects(dataArea)) {
    588                        g2.setPaint(getItemPaint(series + 1, item));
    589                        g2.fill(shape1);
    590                    }
    591                    entityArea = shape1;
    592
    593                    // add an entity for the item...
    594                    if (entities != null) {
    595                        if (entityArea == null) {
    596                            entityArea = new Rectangle2D.Double(
    597                                transX1 - 2, transY1 - 2, 4, 4
    598                            );
    599                        }
    600                        String tip = null;
    601                        XYToolTipGenerator generator = getToolTipGenerator(
    602                            series, item
    603                        );
    604                        if (generator != null) {
    605                            tip = generator.generateToolTip(
    606                                dataset, series + 1, item
    607                            );
    608                        }
    609                        String url = null;
    610                        if (getURLGenerator() != null) {
    611                            url = getURLGenerator().generateURL(
    612                                dataset, series + 1, item
    613                            );
    614                        }
    615                        XYItemEntity entity = new XYItemEntity(
    616                            entityArea, dataset, series + 1, item, tip, url
    617                        );
    618                        entities.add(entity);
    619                    }
    620                }
    621                updateCrosshairValues(
    622                    crosshairState, x1, y1, transX1, transY1, orientation
    623                );
    624            }
    625
    626        }
    627
    628        /**
    629         * Returns the positive area for a crossover point.
    630         *
    631         * @param x0  x coordinate.
    632         * @param y0A  y coordinate A.
    633         * @param y0B  y coordinate B.
    634         * @param x1  x coordinate.
    635         * @param y1A  y coordinate A.
    636         * @param y1B  y coordinate B.
    637         * @param orientation  the plot orientation.
    638         *
    639         * @return The positive area.
    640         */
    641        protected Shape getPositiveArea(float x0, float y0A, float y0B,
    642                                        float x1, float y1A, float y1B,
    643                                        PlotOrientation orientation) {
    644
    645            Shape result = null;
    646
    647            boolean startsNegative = (y0A >= y0B);
    648            boolean endsNegative = (y1A >= y1B);
    649            if (orientation == PlotOrientation.HORIZONTAL) {
    650                startsNegative = (y0B >= y0A);
    651                endsNegative = (y1B >= y1A);
    652            }
    653
    654            if (startsNegative) {  // starts negative
    655                if (endsNegative) {
    656                    // all negative - return null
    657                    result = null;
    658                }
    659                else {
    660                    // changed from negative to positive
    661                    float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
    662                    GeneralPath area = new GeneralPath();
    663                    if (orientation == PlotOrientation.HORIZONTAL) {
    664                        area.moveTo(y1A, x1);
    665                        area.lineTo(p[1], p[0]);
    666                        area.lineTo(y1B, x1);
    667                        area.closePath();
    668                    }
    669                    else if (orientation == PlotOrientation.VERTICAL) {
    670                        area.moveTo(x1, y1A);
    671                        area.lineTo(p[0], p[1]);
    672                        area.lineTo(x1, y1B);
    673                        area.closePath();
    674                    }
    675                    result = area;
    676                }
    677            }
    678            else {  // starts positive
    679                if (endsNegative) {
    680                    // changed from positive to negative
    681                    float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
    682                    GeneralPath area = new GeneralPath();
    683                    if (orientation == PlotOrientation.HORIZONTAL) {
    684                        area.moveTo(y0A, x0);
    685                        area.lineTo(p[1], p[0]);
    686                        area.lineTo(y0B, x0);
    687                        area.closePath();
    688                    }
    689                    else if (orientation == PlotOrientation.VERTICAL) {
    690                        area.moveTo(x0, y0A);
    691                        area.lineTo(p[0], p[1]);
    692                        area.lineTo(x0, y0B);
    693                        area.closePath();
    694                    }
    695                    result = area;
    696
    697                }
    698                else {
    699                    GeneralPath area = new GeneralPath();
    700                    if (orientation == PlotOrientation.HORIZONTAL) {
    701                        area.moveTo(y0A, x0);
    702                        area.lineTo(y1A, x1);
    703                        area.lineTo(y1B, x1);
    704                        area.lineTo(y0B, x0);
    705                        area.closePath();
    706                    }
    707                    else if (orientation == PlotOrientation.VERTICAL) {
    708                        area.moveTo(x0, y0A);
    709                        area.lineTo(x1, y1A);
    710                        area.lineTo(x1, y1B);
    711                        area.lineTo(x0, y0B);
    712                        area.closePath();
    713                    }
    714                    result = area;
    715                }
    716
    717            }
    718
    719            return result;
    720
    721        }
    722
    723        /**
    724         * Returns the negative area for a cross-over section.
    725         *
    726         * @param x0  x coordinate.
    727         * @param y0A  y coordinate A.
    728         * @param y0B  y coordinate B.
    729         * @param x1  x coordinate.
    730         * @param y1A  y coordinate A.
    731         * @param y1B  y coordinate B.
    732         * @param orientation  the plot orientation.
    733         *
    734         * @return The negative area.
    735         */
    736        protected Shape getNegativeArea(float x0, float y0A, float y0B,
    737                                        float x1, float y1A, float y1B,
    738                                        PlotOrientation orientation) {
    739
    740            Shape result = null;
    741
    742            boolean startsNegative = (y0A >= y0B);
    743            boolean endsNegative = (y1A >= y1B);
    744            if (orientation == PlotOrientation.HORIZONTAL) {
    745                startsNegative = (y0B >= y0A);
    746                endsNegative = (y1B >= y1A);
    747            }
    748            if (startsNegative) {  // starts negative
    749                if (endsNegative) {  // all negative
    750                    GeneralPath area = new GeneralPath();
    751                    if (orientation == PlotOrientation.HORIZONTAL) {
    752                        area.moveTo(y0A, x0);
    753                        area.lineTo(y1A, x1);
    754                        area.lineTo(y1B, x1);
    755                        area.lineTo(y0B, x0);
    756                        area.closePath();
    757                    }
    758                    else if (orientation == PlotOrientation.VERTICAL) {
    759                        area.moveTo(x0, y0A);
    760                        area.lineTo(x1, y1A);
    761                        area.lineTo(x1, y1B);
    762                        area.lineTo(x0, y0B);
    763                        area.closePath();
    764                    }
    765                    result = area;
    766                }
    767                else {  // changed from negative to positive
    768                    float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
    769                    GeneralPath area = new GeneralPath();
    770                    if (orientation == PlotOrientation.HORIZONTAL) {
    771                        area.moveTo(y0A, x0);
    772                        area.lineTo(p[1], p[0]);
    773                        area.lineTo(y0B, x0);
    774                        area.closePath();
    775                    }
    776                    else if (orientation == PlotOrientation.VERTICAL) {
    777                        area.moveTo(x0, y0A);
    778                        area.lineTo(p[0], p[1]);
    779                        area.lineTo(x0, y0B);
    780                        area.closePath();
    781                    }
    782                    result = area;
    783                }
    784            }
    785            else {
    786                if (endsNegative) {
    787                    // changed from positive to negative
    788                    float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
    789                    GeneralPath area = new GeneralPath();
    790                    if (orientation == PlotOrientation.HORIZONTAL) {
    791                        area.moveTo(y1A, x1);
    792                        area.lineTo(p[1], p[0]);
    793                        area.lineTo(y1B, x1);
    794                        area.closePath();
    795                    }
    796                    else if (orientation == PlotOrientation.VERTICAL) {
    797                        area.moveTo(x1, y1A);
    798                        area.lineTo(p[0], p[1]);
    799                        area.lineTo(x1, y1B);
    800                        area.closePath();
    801                    }
    802                    result = area;
    803                }
    804                else {
    805                    // all negative - return null
    806                }
    807
    808            }
    809
    810            return result;
    811
    812        }
    813
    814        /**
    815         * Returns the intersection point of two lines.
    816         *
    817         * @param x1  x1
    818         * @param y1  y1
    819         * @param x2  x2
    820         * @param y2  y2
    821         * @param x3  x3
    822         * @param y3  y3
    823         * @param x4  x4
    824         * @param y4  y4
    825         *
    826         * @return The intersection point.
    827         */
    828        private float[] getIntersection(float x1, float y1, float x2, float y2,
    829                                        float x3, float y3, float x4, float y4) {
    830
    831            float n = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
    832            float d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
    833            float u = n / d;
    834
    835            float[] result = new float[2];
    836            result[0] = x1 + u * (x2 - x1);
    837            result[1] = y1 + u * (y2 - y1);
    838            return result;
    839
    840        }
    841
    842        /**
    843         * Returns a default legend item for the specified series.  Subclasses
    844         * should override this method to generate customised items.
    845         *
    846         * @param datasetIndex  the dataset index (zero-based).
    847         * @param series  the series index (zero-based).
    848         *
    849         * @return A legend item for the series.
    850         */
    851        public LegendItem getLegendItem(int datasetIndex, int series) {
    852            LegendItem result = null;
    853            XYPlot p = getPlot();
    854            if (p != null) {
    855                XYDataset dataset = p.getDataset(datasetIndex);
    856                if (dataset != null) {
    857                    if (getItemVisible(series, 0)) {
    858                        String label = getLegendItemLabelGenerator().generateLabel(
    859                            dataset, series
    860                        );
    861                        String description = label;
    862                        String toolTipText = null;
    863                        if (getLegendItemToolTipGenerator() != null) {
    864                            toolTipText = getLegendItemToolTipGenerator().generateLabel(
    865                                dataset, series
    866                            );
    867                        }
    868                        String urlText = null;
    869                        if (getLegendItemURLGenerator() != null) {
    870                            urlText = getLegendItemURLGenerator().generateLabel(
    871                                dataset, series
    872                            );
    873                        }
    874                        Paint paint = getSeriesPaint(series);
    875                        Stroke stroke = getSeriesStroke(series);
    876                        // TODO:  the following hard-coded line needs generalising
    877                        Line2D line = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
    878                        result = new LegendItem(label, description,
    879                                toolTipText, urlText, line, stroke, paint);
    880                    }
    881                }
    882
    883            }
    884
    885            return result;
    886
    887        }
    888
    889        /**
    890         * Tests this renderer for equality with an arbitrary object.
    891         *
    892         * @param obj  the object (<code>null</code> permitted).
    893         *
    894         * @return A boolean.
    895         */
    896        public boolean equals(Object obj) {
    897            if (obj == this) {
    898                return true;
    899            }
    900            if (!(obj instanceof XYDifferenceRenderer)) {
    901                return false;
    902            }
    903            if (!super.equals(obj)) {
    904                return false;
    905            }
    906            XYDifferenceRenderer that = (XYDifferenceRenderer) obj;
    907            if (!PaintUtilities.equal(this.positivePaint, that.positivePaint)) {
    908                return false;
    909            }
    910            if (!PaintUtilities.equal(this.negativePaint, that.negativePaint)) {
    911                return false;
    912            }
    913            if (this.shapesVisible != that.shapesVisible) {
    914                return false;
    915            }
    916            if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) {
    917                return false;
    918            }
    919            return true;
    920        }
    921
    922        /**
    923         * Returns a clone of the renderer.
    924         *
    925         * @return A clone.
    926         *
    927         * @throws CloneNotSupportedException  if the renderer cannot be cloned.
    928         */
    929        public Object clone() throws CloneNotSupportedException {
    930            return super.clone();
    931        }
    932
    933        /**
    934         * Provides serialization support.
    935         *
    936         * @param stream  the output stream.
    937         *
    938         * @throws IOException  if there is an I/O error.
    939         */
    940        private void writeObject(ObjectOutputStream stream) throws IOException {
    941            stream.defaultWriteObject();
    942            SerialUtilities.writePaint(this.positivePaint, stream);
    943            SerialUtilities.writePaint(this.negativePaint, stream);
    944            SerialUtilities.writeShape(this.legendLine, stream);
    945        }
    946
    947        /**
    948         * Provides serialization support.
    949         *
    950         * @param stream  the input stream.
    951         *
    952         * @throws IOException  if there is an I/O error.
    953         * @throws ClassNotFoundException  if there is a classpath problem.
    954         */
    955        private void readObject(ObjectInputStream stream)
    956            throws IOException, ClassNotFoundException {
    957            stream.defaultReadObject();
    958            this.positivePaint = SerialUtilities.readPaint(stream);
    959            this.negativePaint = SerialUtilities.readPaint(stream);
    960            this.legendLine = SerialUtilities.readShape(stream);
    961        }
    962
    963    }