    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, by Object Refinery Limited and Contributors.
    006 *
    007 * Project Info:  http://www.jfree.org/jfreechart/index.html
    008 *
    009 * This library is free software; you can redistribute it and/or modify it
    010 * under the terms of the GNU Lesser General Public License as published by
    011 * the Free Software Foundation; either version 2.1 of the License, or
    012 * (at your option) any later version.
    013 *
    014 * This library is distributed in the hope that it will be useful, but
    015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017 * License for more details.
    018 *
    019 * You should have received a copy of the GNU Lesser General Public
    020 * License along with this library; if not, write to the Free Software
    021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022 * USA.
    023 *
    024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    025 * Other names may be trademarks of their respective owners.]
    026 *
    027 * -------------------
    028 * XYAreaRenderer.java
    029 * -------------------
    030 * (C) Copyright 2002-2011, by Hari and Contributors.
    031 *
    032 * Original Author:  Hari (ourhari@hotmail.com);
    033 * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034 *                   Richard Atkinson;
    035 *                   Christian W. Zuckschwerdt;
    036 *                   Martin Krauskopf;
    037 *
    038 * Changes:
    039 * --------
    040 * 03-Apr-2002 : Version 1, contributed by Hari.  This class is based on the
    041 *               StandardXYItemRenderer class (DG);
    042 * 09-Apr-2002 : Removed the translated zero from the drawItem method -
    043 *               overridden the initialise() method to calculate it (DG);
    044 * 30-May-2002 : Added tool tip generator to constructor to match super
    045 *               class (DG);
    046 * 25-Jun-2002 : Removed unnecessary local variable (DG);
    047 * 05-Aug-2002 : Small modification to drawItem method to support URLs for HTML
    048 *               image maps (RA);
    049 * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
    050 * 07-Nov-2002 : Renamed AreaXYItemRenderer --> XYAreaRenderer (DG);
    051 * 25-Mar-2003 : Implemented Serializable (DG);
    052 * 01-May-2003 : Modified drawItem() method signature (DG);
    053 * 27-Jul-2003 : Made line and polygon properties protected rather than
    054 *               private (RA);
    055 * 30-Jul-2003 : Modified entity constructor (CZ);
    056 * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
    057 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
    058 * 07-Oct-2003 : Added renderer state (DG);
    059 * 08-Dec-2003 : Modified hotspot for chart entity (DG);
    060 * 10-Feb-2004 : Changed the drawItem() method to make cut-and-paste overriding
    061 *               easier.  Also moved state class into this class (DG);
    062 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState.  Renamed
    063 *               XYToolTipGenerator --> XYItemLabelGenerator (DG);
    064 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
    065 *               getYValue() (DG);
    066 * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
    067 * 19-Jan-2005 : Now accesses primitives only from dataset (DG);
    068 * 21-Mar-2005 : Override getLegendItem() and equals() methods (DG);
    069 * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
    070 * ------------- JFREECHART 1.0.x ---------------------------------------------
    071 * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
    072 * 14-Feb-2007 : Fixed bug in clone() (DG);
    073 * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
    074 * 04-May-2007 : Set processVisibleItemsOnly flag to false (DG);
    075 * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG);
    076 * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
    077 * 17-Jun-2008 : Apply legend font and paint attributes (DG);
    078 * 31-Dec-2008 : Fix for bug 2471906 - dashed outlines performance issue (DG);
    079 * 11-Jun-2009 : Added a useFillPaint flag and a GradientPaintTransformer for
    080 *               the paint under the series (DG);
    081 * 06-Oct-2011 : Avoid GeneralPath methods requiring Java 1.5 (MK);
    082 *
    083 */
    084
    085package org.jfree.chart.renderer.xy;
    086
    087import java.awt.BasicStroke;
    088import java.awt.GradientPaint;
    089import java.awt.Graphics2D;
    090import java.awt.Paint;
    091import java.awt.Shape;
    092import java.awt.Stroke;
    093import java.awt.geom.Area;
    094import java.awt.geom.GeneralPath;
    095import java.awt.geom.Line2D;
    096import java.awt.geom.Rectangle2D;
    097import java.io.IOException;
    098import java.io.ObjectInputStream;
    099import java.io.ObjectOutputStream;
    100
    101import org.jfree.chart.HashUtilities;
    102import org.jfree.chart.LegendItem;
    103import org.jfree.chart.axis.ValueAxis;
    104import org.jfree.chart.entity.EntityCollection;
    105import org.jfree.chart.event.RendererChangeEvent;
    106import org.jfree.chart.labels.XYSeriesLabelGenerator;
    107import org.jfree.chart.labels.XYToolTipGenerator;
    108import org.jfree.chart.plot.CrosshairState;
    109import org.jfree.chart.plot.PlotOrientation;
    110import org.jfree.chart.plot.PlotRenderingInfo;
    111import org.jfree.chart.plot.XYPlot;
    112import org.jfree.chart.urls.XYURLGenerator;
    113import org.jfree.data.xy.XYDataset;
    114import org.jfree.io.SerialUtilities;
    115import org.jfree.ui.GradientPaintTransformer;
    116import org.jfree.ui.StandardGradientPaintTransformer;
    117import org.jfree.util.PublicCloneable;
    118import org.jfree.util.ShapeUtilities;
    119
    120/**
    121 * Area item renderer for an {@link XYPlot}.  This class can draw (a) shapes at
    122 * each point, or (b) lines between points, or (c) both shapes and lines,
    123 * or (d) filled areas, or (e) filled areas and shapes. The example shown here
    124 * is generated by the <code>XYAreaRendererDemo1.java</code> program included
    125 * in the JFreeChart demo collection:
    126 * <br><br>
    127 * <img src="../../../../../images/XYAreaRendererSample.png"
    128 * alt="XYAreaRendererSample.png" />
    129 */
    130public class XYAreaRenderer extends AbstractXYItemRenderer
    131        implements XYItemRenderer, PublicCloneable {
    132
    133    /** For serialization. */
    134    private static final long serialVersionUID = -4481971353973876747L;
    135
    136    /**
    137     * A state object used by this renderer.
    138     */
    139    static class XYAreaRendererState extends XYItemRendererState {
    140
    141        /** Working storage for the area under one series. */
    142        public GeneralPath area;
    143
    144        /** Working line that can be recycled. */
    145        public Line2D line;
    146
    147        /**
    148         * Creates a new state.
    149         *
    150         * @param info  the plot rendering info.
    151         */
    152        public XYAreaRendererState(PlotRenderingInfo info) {
    153            super(info);
    154            this.area = new GeneralPath();
    155            this.line = new Line2D.Double();
    156        }
    157
    158    }
    159
    160    /** Useful constant for specifying the type of rendering (shapes only). */
    161    public static final int SHAPES = 1;
    162
    163    /** Useful constant for specifying the type of rendering (lines only). */
    164    public static final int LINES = 2;
    165
    166    /**
    167     * Useful constant for specifying the type of rendering (shapes and lines).
    168     */
    169    public static final int SHAPES_AND_LINES = 3;
    170
    171    /** Useful constant for specifying the type of rendering (area only). */
    172    public static final int AREA = 4;
    173
    174    /**
    175     * Useful constant for specifying the type of rendering (area and shapes).
    176     */
    177    public static final int AREA_AND_SHAPES = 5;
    178
    179    /** A flag indicating whether or not shapes are drawn at each XY point. */
    180    private boolean plotShapes;
    181
    182    /** A flag indicating whether or not lines are drawn between XY points. */
    183    private boolean plotLines;
    184
    185    /** A flag indicating whether or not Area are drawn at each XY point. */
    186    private boolean plotArea;
    187
    188    /** A flag that controls whether or not the outline is shown. */
    189    private boolean showOutline;
    190
    191    /**
    192     * The shape used to represent an area in each legend item (this should
    193     * never be <code>null</code>).
    194     */
    195    private transient Shape legendArea;
    196
    197    /**
    198     * A flag that can be set to specify that the fill paint should be used
    199     * to fill the area under the renderer.
    200     *
    201     * @since 1.0.14
    202     */
    203    private boolean useFillPaint;
    204
    205    /**
    206     * A transformer that is applied to the paint used to fill under the
    207     * area *if* it is an instance of GradientPaint.
    208     *
    209     * @since 1.0.14
    210     */
    211    private GradientPaintTransformer gradientTransformer;
    212
    213    /**
    214     * Constructs a new renderer.
    215     */
    216    public XYAreaRenderer() {
    217        this(AREA);
    218    }
    219
    220    /**
    221     * Constructs a new renderer.
    222     *
    223     * @param type  the type of the renderer.
    224     */
    225    public XYAreaRenderer(int type) {
    226        this(type, null, null);
    227    }
    228
    229    /**
    230     * Constructs a new renderer.  To specify the type of renderer, use one of
    231     * the constants: <code>SHAPES</code>, <code>LINES</code>,
    232     * <code>SHAPES_AND_LINES</code>, <code>AREA</code> or
    233     * <code>AREA_AND_SHAPES</code>.
    234     *
    235     * @param type  the type of renderer.
    236     * @param toolTipGenerator  the tool tip generator to use
    237     *                          (<code>null</code> permitted).
    238     * @param urlGenerator  the URL generator (<code>null</code> permitted).
    239     */
    240    public XYAreaRenderer(int type, XYToolTipGenerator toolTipGenerator,
    241                          XYURLGenerator urlGenerator) {
    242
    243        super();
    244        setBaseToolTipGenerator(toolTipGenerator);
    245        setURLGenerator(urlGenerator);
    246
    247        if (type == SHAPES) {
    248            this.plotShapes = true;
    249        }
    250        if (type == LINES) {
    251            this.plotLines = true;
    252        }
    253        if (type == SHAPES_AND_LINES) {
    254            this.plotShapes = true;
    255            this.plotLines = true;
    256        }
    257        if (type == AREA) {
    258            this.plotArea = true;
    259        }
    260        if (type == AREA_AND_SHAPES) {
    261            this.plotArea = true;
    262            this.plotShapes = true;
    263        }
    264        this.showOutline = false;
    265        GeneralPath area = new GeneralPath();
    266        area.moveTo(0.0f, -4.0f);
    267        area.lineTo(3.0f, -2.0f);
    268        area.lineTo(4.0f, 4.0f);
    269        area.lineTo(-4.0f, 4.0f);
    270        area.lineTo(-3.0f, -2.0f);
    271        area.closePath();
    272        this.legendArea = area;
    273        this.useFillPaint = false;
    274        this.gradientTransformer = new StandardGradientPaintTransformer();
    275    }
    276
    277    /**
    278     * Returns true if shapes are being plotted by the renderer.
    279     *
    280     * @return <code>true</code> if shapes are being plotted by the renderer.
    281     */
    282    public boolean getPlotShapes() {
    283        return this.plotShapes;
    284    }
    285
    286    /**
    287     * Returns true if lines are being plotted by the renderer.
    288     *
    289     * @return <code>true</code> if lines are being plotted by the renderer.
    290     */
    291    public boolean getPlotLines() {
    292        return this.plotLines;
    293    }
    294
    295    /**
    296     * Returns true if Area is being plotted by the renderer.
    297     *
    298     * @return <code>true</code> if Area is being plotted by the renderer.
    299     */
    300    public boolean getPlotArea() {
    301        return this.plotArea;
    302    }
    303
    304    /**
    305     * Returns a flag that controls whether or not outlines of the areas are
    306     * drawn.
    307     *
    308     * @return The flag.
    309     *
    310     * @see #setOutline(boolean)
    311     */
    312    public boolean isOutline() {
    313        return this.showOutline;
    314    }
    315
    316    /**
    317     * Sets a flag that controls whether or not outlines of the areas are drawn
    318     * and sends a {@link RendererChangeEvent} to all registered listeners.
    319     *
    320     * @param show  the flag.
    321     *
    322     * @see #isOutline()
    323     */
    324    public void setOutline(boolean show) {
    325        this.showOutline = show;
    326        fireChangeEvent();
    327    }
    328
    329    /**
    330     * Returns the shape used to represent an area in the legend.
    331     *
    332     * @return The legend area (never <code>null</code>).
    333     */
    334    public Shape getLegendArea() {
    335        return this.legendArea;
    336    }
    337
    338    /**
    339     * Sets the shape used as an area in each legend item and sends a
    340     * {@link RendererChangeEvent} to all registered listeners.
    341     *
    342     * @param area  the area (<code>null</code> not permitted).
    343     */
    344    public void setLegendArea(Shape area) {
    345        if (area == null) {
    346            throw new IllegalArgumentException("Null 'area' argument.");
    347        }
    348        this.legendArea = area;
    349        fireChangeEvent();
    350    }
    351
    352    /**
    353     * Returns the flag that controls whether the series fill paint is used to
    354     * fill the area under the line.
    355     *
    356     * @return A boolean.
    357     *
    358     * @since 1.0.14
    359     */
    360    public boolean getUseFillPaint() {
    361        return this.useFillPaint;
    362    }
    363
    364    /**
    365     * Sets the flag that controls whether or not the series fill paint is
    366     * used to fill the area under the line and sends a
    367     * {@link RendererChangeEvent} to all listeners.
    368     *
    369     * @param use  the new flag value.
    370     *
    371     * @since 1.0.14
    372     */
    373    public void setUseFillPaint(boolean use) {
    374        this.useFillPaint = use;
    375        fireChangeEvent();
    376    }
    377
    378    /**
    379     * Returns the gradient paint transformer.
    380     *
    381     * @return The gradient paint transformer (never <code>null</code>).
    382     *
    383     * @since 1.0.14
    384     */
    385    public GradientPaintTransformer getGradientTransformer() {
    386        return this.gradientTransformer;
    387    }
    388
    389    /**
    390     * Sets the gradient paint transformer and sends a
    391     * {@link RendererChangeEvent} to all registered listeners.
    392     *
    393     * @param transformer  the transformer (<code>null</code> not permitted).
    394     *
    395     * @since 1.0.14
    396     */
    397    public void setGradientTransformer(GradientPaintTransformer transformer) {
    398        if (transformer == null) {
    399            throw new IllegalArgumentException("Null 'transformer' argument.");
    400        }
    401        this.gradientTransformer = transformer;
    402        fireChangeEvent();
    403    }
    404
    405    /**
    406     * Initialises the renderer and returns a state object that should be
    407     * passed to all subsequent calls to the drawItem() method.
    408     *
    409     * @param g2  the graphics device.
    410     * @param dataArea  the area inside the axes.
    411     * @param plot  the plot.
    412     * @param data  the data.
    413     * @param info  an optional info collection object to return data back to
    414     *              the caller.
    415     *
    416     * @return A state object for use by the renderer.
    417     */
    418    public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea,
    419            XYPlot plot, XYDataset data, PlotRenderingInfo info) {
    420        XYAreaRendererState state = new XYAreaRendererState(info);
    421
    422        // in the rendering process, there is special handling for item
    423        // zero, so we can't support processing of visible data items only
    424        state.setProcessVisibleItemsOnly(false);
    425        return state;
    426    }
    427
    428    /**
    429     * Returns a default legend item for the specified series.  Subclasses
    430     * should override this method to generate customised items.
    431     *
    432     * @param datasetIndex  the dataset index (zero-based).
    433     * @param series  the series index (zero-based).
    434     *
    435     * @return A legend item for the series.
    436     */
    437    public LegendItem getLegendItem(int datasetIndex, int series) {
    438        LegendItem result = null;
    439        XYPlot xyplot = getPlot();
    440        if (xyplot != null) {
    441            XYDataset dataset = xyplot.getDataset(datasetIndex);
    442            if (dataset != null) {
    443                XYSeriesLabelGenerator lg = getLegendItemLabelGenerator();
    444                String label = lg.generateLabel(dataset, series);
    445                String description = label;
    446                String toolTipText = null;
    447                if (getLegendItemToolTipGenerator() != null) {
    448                    toolTipText = getLegendItemToolTipGenerator().generateLabel(
    449                            dataset, series);
    450                }
    451                String urlText = null;
    452                if (getLegendItemURLGenerator() != null) {
    453                    urlText = getLegendItemURLGenerator().generateLabel(
    454                            dataset, series);
    455                }
    456                Paint paint = lookupSeriesPaint(series);
    457                result = new LegendItem(label, description, toolTipText,
    458                        urlText, this.legendArea, paint);
    459                result.setLabelFont(lookupLegendTextFont(series));
    460                Paint labelPaint = lookupLegendTextPaint(series);
    461                if (labelPaint != null) {
    462                    result.setLabelPaint(labelPaint);
    463                }
    464                result.setDataset(dataset);
    465                result.setDatasetIndex(datasetIndex);
    466                result.setSeriesKey(dataset.getSeriesKey(series));
    467                result.setSeriesIndex(series);
    468            }
    469        }
    470        return result;
    471    }
    472
    473    /**
    474     * Draws the visual representation of a single data item.
    475     *
    476     * @param g2  the graphics device.
    477     * @param state  the renderer state.
    478     * @param dataArea  the area within which the data is being drawn.
    479     * @param info  collects information about the drawing.
    480     * @param plot  the plot (can be used to obtain standard color information
    481     *              etc).
    482     * @param domainAxis  the domain axis.
    483     * @param rangeAxis  the range axis.
    484     * @param dataset  the dataset.
    485     * @param series  the series index (zero-based).
    486     * @param item  the item index (zero-based).
    487     * @param crosshairState  crosshair information for the plot
    488     *                        (<code>null</code> permitted).
    489     * @param pass  the pass index.
    490     */
    491    public void drawItem(Graphics2D g2, XYItemRendererState state,
    492            Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
    493            ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset,
    494            int series, int item, CrosshairState crosshairState, int pass) {
    495
    496        if (!getItemVisible(series, item)) {
    497            return;
    498        }
    499        XYAreaRendererState areaState = (XYAreaRendererState) state;
    500
    501        // get the data point...
    502        double x1 = dataset.getXValue(series, item);
    503        double y1 = dataset.getYValue(series, item);
    504        if (Double.isNaN(y1)) {
    505            y1 = 0.0;
    506        }
    507        double transX1 = domainAxis.valueToJava2D(x1, dataArea,
    508                plot.getDomainAxisEdge());
    509        double transY1 = rangeAxis.valueToJava2D(y1, dataArea,
    510                plot.getRangeAxisEdge());
    511
    512        // get the previous point and the next point so we can calculate a
    513        // "hot spot" for the area (used by the chart entity)...
    514        int itemCount = dataset.getItemCount(series);
    515        double x0 = dataset.getXValue(series, Math.max(item - 1, 0));
    516        double y0 = dataset.getYValue(series, Math.max(item - 1, 0));
    517        if (Double.isNaN(y0)) {
    518            y0 = 0.0;
    519        }
    520        double transX0 = domainAxis.valueToJava2D(x0, dataArea,
    521                plot.getDomainAxisEdge());
    522        double transY0 = rangeAxis.valueToJava2D(y0, dataArea,
    523                plot.getRangeAxisEdge());
    524
    525        double x2 = dataset.getXValue(series, Math.min(item + 1,
    526                itemCount - 1));
    527        double y2 = dataset.getYValue(series, Math.min(item + 1,
    528                itemCount - 1));
    529        if (Double.isNaN(y2)) {
    530            y2 = 0.0;
    531        }
    532        double transX2 = domainAxis.valueToJava2D(x2, dataArea,
    533                plot.getDomainAxisEdge());
    534        double transY2 = rangeAxis.valueToJava2D(y2, dataArea,
    535                plot.getRangeAxisEdge());
    536
    537        double transZero = rangeAxis.valueToJava2D(0.0, dataArea,
    538                plot.getRangeAxisEdge());
    539        GeneralPath hotspot = new GeneralPath();
    540        if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
    541            moveTo(hotspot, transZero, ((transX0 + transX1) / 2.0));
    542            lineTo(hotspot, ((transY0 + transY1) / 2.0),
    543                            ((transX0 + transX1) / 2.0));
    544            lineTo(hotspot, transY1, transX1);
    545            lineTo(hotspot, ((transY1 + transY2) / 2.0),
    546                            ((transX1 + transX2) / 2.0));
    547            lineTo(hotspot, transZero, ((transX1 + transX2) / 2.0));
    548        }
    549        else {  // vertical orientation
    550            moveTo(hotspot, ((transX0 + transX1) / 2.0), transZero);
    551            lineTo(hotspot, ((transX0 + transX1) / 2.0),
    552                            ((transY0 + transY1) / 2.0));
    553            lineTo(hotspot, transX1, transY1);
    554            lineTo(hotspot, ((transX1 + transX2) / 2.0),
    555                            ((transY1 + transY2) / 2.0));
    556            lineTo(hotspot, ((transX1 + transX2) / 2.0), transZero);
    557        }
    558        hotspot.closePath();
    559
    560        if (item == 0) {  // create a new area polygon for the series
    561            areaState.area = new GeneralPath();
    562            // the first point is (x, 0)
    563            double zero = rangeAxis.valueToJava2D(0.0, dataArea,
    564                    plot.getRangeAxisEdge());
    565            if (plot.getOrientation() == PlotOrientation.VERTICAL) {
    566                moveTo(areaState.area, transX1, zero);
    567            }
    568            else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
    569                moveTo(areaState.area, zero, transX1);
    570            }
    571        }
    572
    573        // Add each point to Area (x, y)
    574        if (plot.getOrientation() == PlotOrientation.VERTICAL) {
    575            lineTo(areaState.area, transX1, transY1);
    576        }
    577        else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
    578            lineTo(areaState.area, transY1, transX1);
    579        }
    580
    581        PlotOrientation orientation = plot.getOrientation();
    582        Paint paint = getItemPaint(series, item);
    583        Stroke stroke = getItemStroke(series, item);
    584        g2.setPaint(paint);
    585        g2.setStroke(stroke);
    586
    587        Shape shape = null;
    588        if (getPlotShapes()) {
    589            shape = getItemShape(series, item);
    590            if (orientation == PlotOrientation.VERTICAL) {
    591                shape = ShapeUtilities.createTranslatedShape(shape, transX1,
    592                        transY1);
    593            }
    594            else if (orientation == PlotOrientation.HORIZONTAL) {
    595                shape = ShapeUtilities.createTranslatedShape(shape, transY1,
    596                        transX1);
    597            }
    598            g2.draw(shape);
    599        }
    600
    601        if (getPlotLines()) {
    602            if (item > 0) {
    603                if (plot.getOrientation() == PlotOrientation.VERTICAL) {
    604                    areaState.line.setLine(transX0, transY0, transX1, transY1);
    605                }
    606                else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
    607                    areaState.line.setLine(transY0, transX0, transY1, transX1);
    608                }
    609                g2.draw(areaState.line);
    610            }
    611        }
    612
    613        // Check if the item is the last item for the series.
    614        // and number of items > 0.  We can't draw an area for a single point.
    615        if (getPlotArea() && item > 0 && item == (itemCount - 1)) {
    616
    617            if (orientation == PlotOrientation.VERTICAL) {
    618                // Add the last point (x,0)
    619                lineTo(areaState.area, transX1, transZero);
    620                areaState.area.closePath();
    621            }
    622            else if (orientation == PlotOrientation.HORIZONTAL) {
    623                // Add the last point (x,0)
    624                lineTo(areaState.area, transZero, transX1);
    625                areaState.area.closePath();
    626            }
    627
    628            if (this.useFillPaint) {
    629                paint = lookupSeriesFillPaint(series);
    630            }
    631            if (paint instanceof GradientPaint) {
    632                GradientPaint gp = (GradientPaint) paint;
    633                GradientPaint adjGP = this.gradientTransformer.transform(gp,
    634                        dataArea);
    635                g2.setPaint(adjGP);
    636            }
    637            g2.fill(areaState.area);
    638
    639            // draw an outline around the Area.
    640            if (isOutline()) {
    641                Shape area = areaState.area;
    642
    643                // Java2D has some issues drawing dashed lines around "large"
    644                // geometrical shapes - for example, see bug 6620013 in the
    645                // Java bug database.  So, we'll check if the outline is
    646                // dashed and, if it is, do our own clipping before drawing
    647                // the outline...
    648                Stroke outlineStroke = lookupSeriesOutlineStroke(series);
    649                if (outlineStroke instanceof BasicStroke) {
    650                    BasicStroke bs = (BasicStroke) outlineStroke;
    651                    if (bs.getDashArray() != null) {
    652                        Area poly = new Area(areaState.area);
    653                        // we make the clip region slightly larger than the
    654                        // dataArea so that the clipped edges don't show lines
    655                        // on the chart
    656                        Area clip = new Area(new Rectangle2D.Double(
    657                                dataArea.getX() - 5.0, dataArea.getY() - 5.0,
    658                                dataArea.getWidth() + 10.0,
    659                                dataArea.getHeight() + 10.0));
    660                        poly.intersect(clip);
    661                        area = poly;
    662                    }
    663                } // end of workaround
    664
    665                g2.setStroke(outlineStroke);
    666                g2.setPaint(lookupSeriesOutlinePaint(series));
    667                g2.draw(area);
    668            }
    669        }
    670
    671        int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
    672        int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
    673        updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex,
    674                rangeAxisIndex, transX1, transY1, orientation);
    675
    676        // collect entity and tool tip information...
    677        EntityCollection entities = state.getEntityCollection();
    678        if (entities != null) {
    679            addEntity(entities, hotspot, dataset, series, item, 0.0, 0.0);
    680        }
    681
    682    }
    683
    684    /**
    685     * Returns a clone of the renderer.
    686     *
    687     * @return A clone.
    688     *
    689     * @throws CloneNotSupportedException  if the renderer cannot be cloned.
    690     */
    691    public Object clone() throws CloneNotSupportedException {
    692        XYAreaRenderer clone = (XYAreaRenderer) super.clone();
    693        clone.legendArea = ShapeUtilities.clone(this.legendArea);
    694        return clone;
    695    }
    696
    697    /**
    698     * Tests this renderer for equality with an arbitrary object.
    699     *
    700     * @param obj  the object (<code>null</code> permitted).
    701     *
    702     * @return A boolean.
    703     */
    704    public boolean equals(Object obj) {
    705        if (obj == this) {
    706            return true;
    707        }
    708        if (!(obj instanceof XYAreaRenderer)) {
    709            return false;
    710        }
    711        XYAreaRenderer that = (XYAreaRenderer) obj;
    712        if (this.plotArea != that.plotArea) {
    713            return false;
    714        }
    715        if (this.plotLines != that.plotLines) {
    716            return false;
    717        }
    718        if (this.plotShapes != that.plotShapes) {
    719            return false;
    720        }
    721        if (this.showOutline != that.showOutline) {
    722            return false;
    723        }
    724        if (this.useFillPaint != that.useFillPaint) {
    725            return false;
    726        }
    727        if (!this.gradientTransformer.equals(that.gradientTransformer)) {
    728            return false;
    729        }
    730        if (!ShapeUtilities.equal(this.legendArea, that.legendArea)) {
    731            return false;
    732        }
    733        return true;
    734    }
    735
    736    /**
    737     * Returns a hash code for this instance.
    738     *
    739     * @return A hash code.
    740     */
    741    public int hashCode() {
    742        int result = super.hashCode();
    743        result = HashUtilities.hashCode(result, this.plotArea);
    744        result = HashUtilities.hashCode(result, this.plotLines);
    745        result = HashUtilities.hashCode(result, this.plotShapes);
    746        result = HashUtilities.hashCode(result, this.useFillPaint);
    747        return result;
    748    }
    749
    750    /**
    751     * Provides serialization support.
    752     *
    753     * @param stream  the input stream.
    754     *
    755     * @throws IOException  if there is an I/O error.
    756     * @throws ClassNotFoundException  if there is a classpath problem.
    757     */
    758    private void readObject(ObjectInputStream stream)
    759            throws IOException, ClassNotFoundException {
    760        stream.defaultReadObject();
    761        this.legendArea = SerialUtilities.readShape(stream);
    762    }
    763
    764    /**
    765     * Provides serialization support.
    766     *
    767     * @param stream  the output stream.
    768     *
    769     * @throws IOException  if there is an I/O error.
    770     */
    771    private void writeObject(ObjectOutputStream stream) throws IOException {
    772        stream.defaultWriteObject();
    773        SerialUtilities.writeShape(this.legendArea, stream);
    774    }
    775}