    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.6 2005/12/10 21:51:02 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            if (negativePaint == null) {
    148                throw new IllegalArgumentException(
    149                        "Null 'negativePaint' argument.");
    150            }
    151            this.positivePaint = positivePaint;
    152            this.negativePaint = negativePaint;
    153            this.shapesVisible = shapes;
    154            this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
    155        }
    156
    157        /**
    158         * Returns the paint used to highlight positive differences.
    159         *
    160         * @return The paint (never <code>null</code>).
    161         */
    162        public Paint getPositivePaint() {
    163            return this.positivePaint;
    164        }
    165
    166        /**
    167         * Sets the paint used to highlight positive differences.
    168         *
    169         * @param paint  the paint (<code>null</code> not permitted).
    170         */
    171        public void setPositivePaint(Paint paint) {
    172            if (paint == null) {
    173                throw new IllegalArgumentException("Null 'paint' argument.");
    174            }
    175            this.positivePaint = paint;
    176            notifyListeners(new RendererChangeEvent(this));
    177        }
    178
    179        /**
    180         * Returns the paint used to highlight negative differences.
    181         *
    182         * @return The paint (never <code>null</code>).
    183         */
    184        public Paint getNegativePaint() {
    185            return this.negativePaint;
    186        }
    187
    188        /**
    189         * Sets the paint used to highlight negative differences.
    190         *
    191         * @param paint  the paint (<code>null</code> not permitted).
    192         */
    193        public void setNegativePaint(Paint paint) {
    194            if (paint == null) {
    195                throw new IllegalArgumentException("Null 'paint' argument.");
    196            }
    197            this.negativePaint = paint;
    198            notifyListeners(new RendererChangeEvent(this));
    199        }
    200
    201        /**
    202         * Returns a flag that controls whether or not shapes are drawn for each
    203         * data value.
    204         *
    205         * @return A boolean.
    206         */
    207        public boolean getShapesVisible() {
    208            return this.shapesVisible;
    209        }
    210
    211        /**
    212         * Sets a flag that controls whether or not shapes are drawn for each
    213         * data value.
    214         *
    215         * @param flag  the flag.
    216         */
    217        public void setShapesVisible(boolean flag) {
    218            this.shapesVisible = flag;
    219            notifyListeners(new RendererChangeEvent(this));
    220        }
    221
    222        /**
    223         * Returns the shape used to represent a line in the legend.
    224         *
    225         * @return The legend line (never <code>null</code>).
    226         */
    227        public Shape getLegendLine() {
    228            return this.legendLine;
    229        }
    230
    231        /**
    232         * Sets the shape used as a line in each legend item and sends a
    233         * {@link RendererChangeEvent} to all registered listeners.
    234         *
    235         * @param line  the line (<code>null</code> not permitted).
    236         */
    237        public void setLegendLine(Shape line) {
    238            if (line == null) {
    239                throw new IllegalArgumentException("Null 'line' argument.");
    240            }
    241            this.legendLine = line;
    242            notifyListeners(new RendererChangeEvent(this));
    243        }
    244
    245        /**
    246         * Initialises the renderer and returns a state object that should be
    247         * passed to subsequent calls to the drawItem() method.  This method will
    248         * be called before the first item is rendered, giving the renderer an
    249         * opportunity to initialise any state information it wants to maintain.
    250         * The renderer can do nothing if it chooses.
    251         *
    252         * @param g2  the graphics device.
    253         * @param dataArea  the area inside the axes.
    254         * @param plot  the plot.
    255         * @param data  the data.
    256         * @param info  an optional info collection object to return data back to
    257         *              the caller.
    258         *
    259         * @return A state object.
    260         */
    261        public XYItemRendererState initialise(Graphics2D g2,
    262                                              Rectangle2D dataArea,
    263                                              XYPlot plot,
    264                                              XYDataset data,
    265                                              PlotRenderingInfo info) {
    266
    267            return super.initialise(g2, dataArea, plot, data, info);
    268
    269        }
    270
    271        /**
    272         * Returns <code>2</code>, the number of passes required by the renderer.
    273         * The {@link XYPlot} will run through the dataset this number of times.
    274         *
    275         * @return The number of passes required by the renderer.
    276         */
    277        public int getPassCount() {
    278            return 2;
    279        }
    280
    281        /**
    282         * Draws the visual representation of a single data item.
    283         *
    284         * @param g2  the graphics device.
    285         * @param state  the renderer state.
    286         * @param dataArea  the area within which the data is being drawn.
    287         * @param info  collects information about the drawing.
    288         * @param plot  the plot (can be used to obtain standard color
    289         *              information etc).
    290         * @param domainAxis  the domain (horizontal) axis.
    291         * @param rangeAxis  the range (vertical) axis.
    292         * @param dataset  the dataset.
    293         * @param series  the series index (zero-based).
    294         * @param item  the item index (zero-based).
    295         * @param crosshairState  crosshair information for the plot
    296         *                        (<code>null</code> permitted).
    297         * @param pass  the pass index.
    298         */
    299        public void drawItem(Graphics2D g2,
    300                             XYItemRendererState state,
    301                             Rectangle2D dataArea,
    302                             PlotRenderingInfo info,
    303                             XYPlot plot,
    304                             ValueAxis domainAxis,
    305                             ValueAxis rangeAxis,
    306                             XYDataset dataset,
    307                             int series,
    308                             int item,
    309                             CrosshairState crosshairState,
    310                             int pass) {
    311
    312            if (pass == 0) {
    313                drawItemPass0(g2, dataArea, info, plot, domainAxis, rangeAxis,
    314                        dataset, series, item, crosshairState);
    315            }
    316            else if (pass == 1) {
    317                drawItemPass1(g2, dataArea, info, plot, domainAxis, rangeAxis,
    318                        dataset, series, item, crosshairState);
    319            }
    320
    321        }
    322
    323        /**
    324         * Draws the visual representation of a single data item, first pass.
    325         *
    326         * @param g2  the graphics device.
    327         * @param dataArea  the area within which the data is being drawn.
    328         * @param info  collects information about the drawing.
    329         * @param plot  the plot (can be used to obtain standard color
    330         *              information etc).
    331         * @param domainAxis  the domain (horizontal) axis.
    332         * @param rangeAxis  the range (vertical) axis.
    333         * @param dataset  the dataset.
    334         * @param series  the series index (zero-based).
    335         * @param item  the item index (zero-based).
    336         * @param crosshairState  crosshair information for the plot
    337         *                        (<code>null</code> permitted).
    338         */
    339        protected void drawItemPass0(Graphics2D g2,
    340                                     Rectangle2D dataArea,
    341                                     PlotRenderingInfo info,
    342                                     XYPlot plot,
    343                                     ValueAxis domainAxis,
    344                                     ValueAxis rangeAxis,
    345                                     XYDataset dataset,
    346                                     int series,
    347                                     int item,
    348                                     CrosshairState crosshairState) {
    349
    350            if (series == 0) {
    351
    352                PlotOrientation orientation = plot.getOrientation();
    353                RectangleEdge domainAxisLocation = plot.getDomainAxisEdge();
    354                RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();
    355
    356                double y0 = dataset.getYValue(0, item);
    357                double x1 = dataset.getXValue(1, item);
    358                double y1 = dataset.getYValue(1, item);
    359
    360                double transY0 = rangeAxis.valueToJava2D(y0, dataArea,
    361                        rangeAxisLocation);
    362                double transX1 = domainAxis.valueToJava2D(x1, dataArea,
    363                        domainAxisLocation);
    364                double transY1 = rangeAxis.valueToJava2D(y1, dataArea,
    365                        rangeAxisLocation);
    366
    367                if (item > 0) {
    368                    double prevx0 = dataset.getXValue(0, item - 1);
    369                    double prevy0 = dataset.getYValue(0, item - 1);
    370                    double prevy1 = dataset.getYValue(1, item - 1);
    371
    372                    double prevtransX0 = domainAxis.valueToJava2D(prevx0, dataArea,
    373                            domainAxisLocation);
    374                    double prevtransY0 = rangeAxis.valueToJava2D(prevy0, dataArea,
    375                            rangeAxisLocation);
    376                    double prevtransY1 = rangeAxis.valueToJava2D(prevy1, dataArea,
    377                            rangeAxisLocation);
    378
    379                    Shape positive = getPositiveArea((float) prevtransX0,
    380                            (float) prevtransY0, (float) prevtransY1,
    381                            (float) transX1, (float) transY0, (float) transY1,
    382                            orientation);
    383                    if (positive != null) {
    384                        g2.setPaint(getPositivePaint());
    385                        g2.fill(positive);
    386                    }
    387
    388                    Shape negative = getNegativeArea((float) prevtransX0,
    389                            (float) prevtransY0, (float) prevtransY1,
    390                            (float) transX1, (float) transY0, (float) transY1,
    391                            orientation);
    392
    393                    if (negative != null) {
    394                        g2.setPaint(getNegativePaint());
    395                        g2.fill(negative);
    396                    }
    397                }
    398            }
    399
    400        }
    401
    402        /**
    403         * Draws the visual representation of a single data item, second pass.  In
    404         * the second pass, the renderer draws the lines and shapes for the
    405         * individual points in the two series.
    406         *
    407         * @param g2  the graphics device.
    408         * @param dataArea  the area within which the data is being drawn.
    409         * @param info  collects information about the drawing.
    410         * @param plot  the plot (can be used to obtain standard color information
    411         *              etc).
    412         * @param domainAxis  the domain (horizontal) axis.
    413         * @param rangeAxis  the range (vertical) axis.
    414         * @param dataset  the dataset.
    415         * @param series  the series index (zero-based).
    416         * @param item  the item index (zero-based).
    417         * @param crosshairState  crosshair information for the plot
    418         *                        (<code>null</code> permitted).
    419         */
    420        protected void drawItemPass1(Graphics2D g2,
    421                                     Rectangle2D dataArea,
    422                                     PlotRenderingInfo info,
    423                                     XYPlot plot,
    424                                     ValueAxis domainAxis,
    425                                     ValueAxis rangeAxis,
    426                                     XYDataset dataset,
    427                                     int series,
    428                                     int item,
    429                                     CrosshairState crosshairState) {
    430
    431            Shape entityArea = null;
    432            EntityCollection entities = null;
    433            if (info != null) {
    434                entities = info.getOwner().getEntityCollection();
    435            }
    436
    437            Paint seriesPaint = getItemPaint(series, item);
    438            Stroke seriesStroke = getItemStroke(series, item);
    439            g2.setPaint(seriesPaint);
    440            g2.setStroke(seriesStroke);
    441
    442            if (series == 0) {
    443
    444                PlotOrientation orientation = plot.getOrientation();
    445                RectangleEdge domainAxisLocation = plot.getDomainAxisEdge();
    446                RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();
    447
    448                double x0 = dataset.getXValue(0, item);
    449                double y0 = dataset.getYValue(0, item);
    450                double x1 = dataset.getXValue(1, item);
    451                double y1 = dataset.getYValue(1, item);
    452
    453                double transX0 = domainAxis.valueToJava2D(x0, dataArea,
    454                        domainAxisLocation);
    455                double transY0 = rangeAxis.valueToJava2D(y0, dataArea,
    456                        rangeAxisLocation);
    457                double transX1 = domainAxis.valueToJava2D(x1, dataArea,
    458                        domainAxisLocation);
    459                double transY1 = rangeAxis.valueToJava2D(y1, dataArea,
    460                        rangeAxisLocation);
    461
    462                if (item > 0) {
    463                    // get the previous data points...
    464                    double prevx0 = dataset.getXValue(0, item - 1);
    465                    double prevy0 = dataset.getYValue(0, item - 1);
    466                    double prevx1 = dataset.getXValue(1, item - 1);
    467                    double prevy1 = dataset.getYValue(1, item - 1);
    468
    469                    double prevtransX0 = domainAxis.valueToJava2D(prevx0, dataArea,
    470                            domainAxisLocation);
    471                    double prevtransY0 = rangeAxis.valueToJava2D(prevy0, dataArea,
    472                            rangeAxisLocation);
    473                    double prevtransX1 = domainAxis.valueToJava2D(prevx1, dataArea,
    474                            domainAxisLocation);
    475                    double prevtransY1 = rangeAxis.valueToJava2D(prevy1, dataArea,
    476                            rangeAxisLocation);
    477
    478                    Line2D line0 = null;
    479                    Line2D line1 = null;
    480                    if (orientation == PlotOrientation.HORIZONTAL) {
    481                        line0 = new Line2D.Double(transY0, transX0, prevtransY0,
    482                                prevtransX0);
    483                        line1 = new Line2D.Double(transY1, transX1, prevtransY1,
    484                                prevtransX1);
    485                    }
    486                    else if (orientation == PlotOrientation.VERTICAL) {
    487                        line0 = new Line2D.Double(transX0, transY0, prevtransX0,
    488                                prevtransY0);
    489                        line1 = new Line2D.Double(transX1, transY1, prevtransX1,
    490                                prevtransY1);
    491                    }
    492                    if (line0 != null && line0.intersects(dataArea)) {
    493                        g2.setPaint(getItemPaint(series, item));
    494                        g2.setStroke(getItemStroke(series, item));
    495                        g2.draw(line0);
    496                    }
    497                    if (line1 != null && line1.intersects(dataArea)) {
    498                        g2.setPaint(getItemPaint(1, item));
    499                        g2.setStroke(getItemStroke(1, item));
    500                        g2.draw(line1);
    501                    }
    502                }
    503
    504                if (getShapesVisible()) {
    505                    Shape shape0 = getItemShape(series, item);
    506                    if (orientation == PlotOrientation.HORIZONTAL) {
    507                        shape0 = ShapeUtilities.createTranslatedShape(shape0,
    508                                transY0, transX0);
    509                    }
    510                    else {  // vertical
    511                        shape0 = ShapeUtilities.createTranslatedShape(shape0,
    512                                transX0, transY0);
    513                    }
    514                    if (shape0.intersects(dataArea)) {
    515                        g2.setPaint(getItemPaint(series, item));
    516                        g2.fill(shape0);
    517                    }
    518                    entityArea = shape0;
    519
    520                    // add an entity for the item...
    521                    if (entities != null) {
    522                        if (entityArea == null) {
    523                            entityArea = new Rectangle2D.Double(transX0 - 2,
    524                                    transY0 - 2, 4, 4);
    525                        }
    526                        String tip = null;
    527                        XYToolTipGenerator generator = getToolTipGenerator(series,
    528                                item);
    529                        if (generator != null) {
    530                            tip = generator.generateToolTip(dataset, series, item);
    531                        }
    532                        String url = null;
    533                        if (getURLGenerator() != null) {
    534                            url = getURLGenerator().generateURL(dataset, series,
    535                                    item);
    536                        }
    537                        XYItemEntity entity = new XYItemEntity(entityArea, dataset,
    538                                series, item, tip, url);
    539                        entities.add(entity);
    540                    }
    541
    542                    Shape shape1 = getItemShape(series + 1, item);
    543                    if (orientation == PlotOrientation.HORIZONTAL) {
    544                        shape1 = ShapeUtilities.createTranslatedShape(shape1,
    545                                transY1, transX1);
    546                    }
    547                    else {  // vertical
    548                        shape1 = ShapeUtilities.createTranslatedShape(shape1,
    549                                transX1, transY1);
    550                    }
    551                    if (shape1.intersects(dataArea)) {
    552                        g2.setPaint(getItemPaint(series + 1, item));
    553                        g2.fill(shape1);
    554                    }
    555                    entityArea = shape1;
    556
    557                    // add an entity for the item...
    558                    if (entities != null) {
    559                        if (entityArea == null) {
    560                            entityArea = new Rectangle2D.Double(transX1 - 2,
    561                                    transY1 - 2, 4, 4);
    562                        }
    563                        String tip = null;
    564                        XYToolTipGenerator generator = getToolTipGenerator(series,
    565                                item);
    566                        if (generator != null) {
    567                            tip = generator.generateToolTip(dataset, series + 1,
    568                                    item);
    569                        }
    570                        String url = null;
    571                        if (getURLGenerator() != null) {
    572                            url = getURLGenerator().generateURL(dataset,
    573                                    series + 1, item);
    574                        }
    575                        XYItemEntity entity = new XYItemEntity(entityArea, dataset,
    576                                series + 1, item, tip, url);
    577                        entities.add(entity);
    578                    }
    579                }
    580                updateCrosshairValues(crosshairState, x1, y1, transX1, transY1,
    581                        orientation);
    582            }
    583
    584        }
    585
    586        /**
    587         * Returns the positive area for a crossover point.
    588         *
    589         * @param x0  x coordinate.
    590         * @param y0A  y coordinate A.
    591         * @param y0B  y coordinate B.
    592         * @param x1  x coordinate.
    593         * @param y1A  y coordinate A.
    594         * @param y1B  y coordinate B.
    595         * @param orientation  the plot orientation.
    596         *
    597         * @return The positive area.
    598         */
    599        protected Shape getPositiveArea(float x0, float y0A, float y0B,
    600                                        float x1, float y1A, float y1B,
    601                                        PlotOrientation orientation) {
    602
    603            Shape result = null;
    604
    605            boolean startsNegative = (y0A >= y0B);
    606            boolean endsNegative = (y1A >= y1B);
    607            if (orientation == PlotOrientation.HORIZONTAL) {
    608                startsNegative = (y0B >= y0A);
    609                endsNegative = (y1B >= y1A);
    610            }
    611
    612            if (startsNegative) {  // starts negative
    613                if (endsNegative) {
    614                    // all negative - return null
    615                    result = null;
    616                }
    617                else {
    618                    // changed from negative to positive
    619                    float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
    620                    GeneralPath area = new GeneralPath();
    621                    if (orientation == PlotOrientation.HORIZONTAL) {
    622                        area.moveTo(y1A, x1);
    623                        area.lineTo(p[1], p[0]);
    624                        area.lineTo(y1B, x1);
    625                        area.closePath();
    626                    }
    627                    else if (orientation == PlotOrientation.VERTICAL) {
    628                        area.moveTo(x1, y1A);
    629                        area.lineTo(p[0], p[1]);
    630                        area.lineTo(x1, y1B);
    631                        area.closePath();
    632                    }
    633                    result = area;
    634                }
    635            }
    636            else {  // starts positive
    637                if (endsNegative) {
    638                    // changed from positive to negative
    639                    float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
    640                    GeneralPath area = new GeneralPath();
    641                    if (orientation == PlotOrientation.HORIZONTAL) {
    642                        area.moveTo(y0A, x0);
    643                        area.lineTo(p[1], p[0]);
    644                        area.lineTo(y0B, x0);
    645                        area.closePath();
    646                    }
    647                    else if (orientation == PlotOrientation.VERTICAL) {
    648                        area.moveTo(x0, y0A);
    649                        area.lineTo(p[0], p[1]);
    650                        area.lineTo(x0, y0B);
    651                        area.closePath();
    652                    }
    653                    result = area;
    654
    655                }
    656                else {
    657                    GeneralPath area = new GeneralPath();
    658                    if (orientation == PlotOrientation.HORIZONTAL) {
    659                        area.moveTo(y0A, x0);
    660                        area.lineTo(y1A, x1);
    661                        area.lineTo(y1B, x1);
    662                        area.lineTo(y0B, x0);
    663                        area.closePath();
    664                    }
    665                    else if (orientation == PlotOrientation.VERTICAL) {
    666                        area.moveTo(x0, y0A);
    667                        area.lineTo(x1, y1A);
    668                        area.lineTo(x1, y1B);
    669                        area.lineTo(x0, y0B);
    670                        area.closePath();
    671                    }
    672                    result = area;
    673                }
    674
    675            }
    676
    677            return result;
    678
    679        }
    680
    681        /**
    682         * Returns the negative area for a cross-over section.
    683         *
    684         * @param x0  x coordinate.
    685         * @param y0A  y coordinate A.
    686         * @param y0B  y coordinate B.
    687         * @param x1  x coordinate.
    688         * @param y1A  y coordinate A.
    689         * @param y1B  y coordinate B.
    690         * @param orientation  the plot orientation.
    691         *
    692         * @return The negative area.
    693         */
    694        protected Shape getNegativeArea(float x0, float y0A, float y0B,
    695                                        float x1, float y1A, float y1B,
    696                                        PlotOrientation orientation) {
    697
    698            Shape result = null;
    699
    700            boolean startsNegative = (y0A >= y0B);
    701            boolean endsNegative = (y1A >= y1B);
    702            if (orientation == PlotOrientation.HORIZONTAL) {
    703                startsNegative = (y0B >= y0A);
    704                endsNegative = (y1B >= y1A);
    705            }
    706            if (startsNegative) {  // starts negative
    707                if (endsNegative) {  // all negative
    708                    GeneralPath area = new GeneralPath();
    709                    if (orientation == PlotOrientation.HORIZONTAL) {
    710                        area.moveTo(y0A, x0);
    711                        area.lineTo(y1A, x1);
    712                        area.lineTo(y1B, x1);
    713                        area.lineTo(y0B, x0);
    714                        area.closePath();
    715                    }
    716                    else if (orientation == PlotOrientation.VERTICAL) {
    717                        area.moveTo(x0, y0A);
    718                        area.lineTo(x1, y1A);
    719                        area.lineTo(x1, y1B);
    720                        area.lineTo(x0, y0B);
    721                        area.closePath();
    722                    }
    723                    result = area;
    724                }
    725                else {  // changed from negative to positive
    726                    float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
    727                    GeneralPath area = new GeneralPath();
    728                    if (orientation == PlotOrientation.HORIZONTAL) {
    729                        area.moveTo(y0A, x0);
    730                        area.lineTo(p[1], p[0]);
    731                        area.lineTo(y0B, x0);
    732                        area.closePath();
    733                    }
    734                    else if (orientation == PlotOrientation.VERTICAL) {
    735                        area.moveTo(x0, y0A);
    736                        area.lineTo(p[0], p[1]);
    737                        area.lineTo(x0, y0B);
    738                        area.closePath();
    739                    }
    740                    result = area;
    741                }
    742            }
    743            else {
    744                if (endsNegative) {
    745                    // changed from positive to negative
    746                    float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
    747                    GeneralPath area = new GeneralPath();
    748                    if (orientation == PlotOrientation.HORIZONTAL) {
    749                        area.moveTo(y1A, x1);
    750                        area.lineTo(p[1], p[0]);
    751                        area.lineTo(y1B, x1);
    752                        area.closePath();
    753                    }
    754                    else if (orientation == PlotOrientation.VERTICAL) {
    755                        area.moveTo(x1, y1A);
    756                        area.lineTo(p[0], p[1]);
    757                        area.lineTo(x1, y1B);
    758                        area.closePath();
    759                    }
    760                    result = area;
    761                }
    762                else {
    763                    // all negative - return null
    764                }
    765
    766            }
    767
    768            return result;
    769
    770        }
    771
    772        /**
    773         * Returns the intersection point of two lines.
    774         *
    775         * @param x1  x1
    776         * @param y1  y1
    777         * @param x2  x2
    778         * @param y2  y2
    779         * @param x3  x3
    780         * @param y3  y3
    781         * @param x4  x4
    782         * @param y4  y4
    783         *
    784         * @return The intersection point.
    785         */
    786        private float[] getIntersection(float x1, float y1, float x2, float y2,
    787                                        float x3, float y3, float x4, float y4) {
    788
    789            float n = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
    790            float d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
    791            float u = n / d;
    792
    793            float[] result = new float[2];
    794            result[0] = x1 + u * (x2 - x1);
    795            result[1] = y1 + u * (y2 - y1);
    796            return result;
    797
    798        }
    799
    800        /**
    801         * Returns a default legend item for the specified series.  Subclasses
    802         * should override this method to generate customised items.
    803         *
    804         * @param datasetIndex  the dataset index (zero-based).
    805         * @param series  the series index (zero-based).
    806         *
    807         * @return A legend item for the series.
    808         */
    809        public LegendItem getLegendItem(int datasetIndex, int series) {
    810            LegendItem result = null;
    811            XYPlot p = getPlot();
    812            if (p != null) {
    813                XYDataset dataset = p.getDataset(datasetIndex);
    814                if (dataset != null) {
    815                    if (getItemVisible(series, 0)) {
    816                        String label = getLegendItemLabelGenerator().generateLabel(
    817                                dataset, series);
    818                        String description = label;
    819                        String toolTipText = null;
    820                        if (getLegendItemToolTipGenerator() != null) {
    821                            toolTipText
    822                                = getLegendItemToolTipGenerator().generateLabel(
    823                                        dataset, series);
    824                        }
    825                        String urlText = null;
    826                        if (getLegendItemURLGenerator() != null) {
    827                            urlText = getLegendItemURLGenerator().generateLabel(
    828                                    dataset, series);
    829                        }
    830                        Paint paint = getSeriesPaint(series);
    831                        Stroke stroke = getSeriesStroke(series);
    832                        // TODO:  the following hard-coded line needs generalising
    833                        Line2D line = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
    834                        result = new LegendItem(label, description,
    835                                toolTipText, urlText, line, stroke, paint);
    836                    }
    837                }
    838
    839            }
    840
    841            return result;
    842
    843        }
    844
    845        /**
    846         * Tests this renderer for equality with an arbitrary object.
    847         *
    848         * @param obj  the object (<code>null</code> permitted).
    849         *
    850         * @return A boolean.
    851         */
    852        public boolean equals(Object obj) {
    853            if (obj == this) {
    854                return true;
    855            }
    856            if (!(obj instanceof XYDifferenceRenderer)) {
    857                return false;
    858            }
    859            if (!super.equals(obj)) {
    860                return false;
    861            }
    862            XYDifferenceRenderer that = (XYDifferenceRenderer) obj;
    863            if (!PaintUtilities.equal(this.positivePaint, that.positivePaint)) {
    864                return false;
    865            }
    866            if (!PaintUtilities.equal(this.negativePaint, that.negativePaint)) {
    867                return false;
    868            }
    869            if (this.shapesVisible != that.shapesVisible) {
    870                return false;
    871            }
    872            if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) {
    873                return false;
    874            }
    875            return true;
    876        }
    877
    878        /**
    879         * Returns a clone of the renderer.
    880         *
    881         * @return A clone.
    882         *
    883         * @throws CloneNotSupportedException  if the renderer cannot be cloned.
    884         */
    885        public Object clone() throws CloneNotSupportedException {
    886            return super.clone();
    887        }
    888
    889        /**
    890         * Provides serialization support.
    891         *
    892         * @param stream  the output stream.
    893         *
    894         * @throws IOException  if there is an I/O error.
    895         */
    896        private void writeObject(ObjectOutputStream stream) throws IOException {
    897            stream.defaultWriteObject();
    898            SerialUtilities.writePaint(this.positivePaint, stream);
    899            SerialUtilities.writePaint(this.negativePaint, stream);
    900            SerialUtilities.writeShape(this.legendLine, stream);
    901        }
    902
    903        /**
    904         * Provides serialization support.
    905         *
    906         * @param stream  the input stream.
    907         *
    908         * @throws IOException  if there is an I/O error.
    909         * @throws ClassNotFoundException  if there is a classpath problem.
    910         */
    911        private void readObject(ObjectInputStream stream)
    912            throws IOException, ClassNotFoundException {
    913            stream.defaultReadObject();
    914            this.positivePaint = SerialUtilities.readPaint(stream);
    915            this.negativePaint = SerialUtilities.readPaint(stream);
    916            this.legendLine = SerialUtilities.readShape(stream);
    917        }
    918
    919    }