001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2014, 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-2014, 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 * 03-Jul-2013 : Use ParamChecks (DG);
083 *
084 */
085
086package org.jfree.chart.renderer.xy;
087
088import java.awt.BasicStroke;
089import java.awt.GradientPaint;
090import java.awt.Graphics2D;
091import java.awt.Paint;
092import java.awt.Shape;
093import java.awt.Stroke;
094import java.awt.geom.Area;
095import java.awt.geom.GeneralPath;
096import java.awt.geom.Line2D;
097import java.awt.geom.Rectangle2D;
098import java.io.IOException;
099import java.io.ObjectInputStream;
100import java.io.ObjectOutputStream;
101
102import org.jfree.chart.HashUtilities;
103import org.jfree.chart.LegendItem;
104import org.jfree.chart.axis.ValueAxis;
105import org.jfree.chart.entity.EntityCollection;
106import org.jfree.chart.event.RendererChangeEvent;
107import org.jfree.chart.labels.XYSeriesLabelGenerator;
108import org.jfree.chart.labels.XYToolTipGenerator;
109import org.jfree.chart.plot.CrosshairState;
110import org.jfree.chart.plot.PlotOrientation;
111import org.jfree.chart.plot.PlotRenderingInfo;
112import org.jfree.chart.plot.XYPlot;
113import org.jfree.chart.urls.XYURLGenerator;
114import org.jfree.chart.util.ParamChecks;
115import org.jfree.data.xy.XYDataset;
116import org.jfree.io.SerialUtilities;
117import org.jfree.ui.GradientPaintTransformer;
118import org.jfree.ui.StandardGradientPaintTransformer;
119import org.jfree.util.PublicCloneable;
120import org.jfree.util.ShapeUtilities;
121
122/**
123 * Area item renderer for an {@link XYPlot}.  This class can draw (a) shapes at
124 * each point, or (b) lines between points, or (c) both shapes and lines,
125 * or (d) filled areas, or (e) filled areas and shapes. The example shown here
126 * is generated by the <code>XYAreaRendererDemo1.java</code> program included
127 * in the JFreeChart demo collection:
128 * <br><br>
129 * <img src="../../../../../images/XYAreaRendererSample.png"
130 * alt="XYAreaRendererSample.png">
131 */
132public class XYAreaRenderer extends AbstractXYItemRenderer
133        implements XYItemRenderer, PublicCloneable {
134
135    /** For serialization. */
136    private static final long serialVersionUID = -4481971353973876747L;
137
138    /**
139     * A state object used by this renderer.
140     */
141    static class XYAreaRendererState extends XYItemRendererState {
142
143        /** Working storage for the area under one series. */
144        public GeneralPath area;
145
146        /** Working line that can be recycled. */
147        public Line2D line;
148
149        /**
150         * Creates a new state.
151         *
152         * @param info  the plot rendering info.
153         */
154        public XYAreaRendererState(PlotRenderingInfo info) {
155            super(info);
156            this.area = new GeneralPath();
157            this.line = new Line2D.Double();
158        }
159
160    }
161
162    /** Useful constant for specifying the type of rendering (shapes only). */
163    public static final int SHAPES = 1;
164
165    /** Useful constant for specifying the type of rendering (lines only). */
166    public static final int LINES = 2;
167
168    /**
169     * Useful constant for specifying the type of rendering (shapes and lines).
170     */
171    public static final int SHAPES_AND_LINES = 3;
172
173    /** Useful constant for specifying the type of rendering (area only). */
174    public static final int AREA = 4;
175
176    /**
177     * Useful constant for specifying the type of rendering (area and shapes).
178     */
179    public static final int AREA_AND_SHAPES = 5;
180
181    /** A flag indicating whether or not shapes are drawn at each XY point. */
182    private boolean plotShapes;
183
184    /** A flag indicating whether or not lines are drawn between XY points. */
185    private boolean plotLines;
186
187    /** A flag indicating whether or not Area are drawn at each XY point. */
188    private boolean plotArea;
189
190    /** A flag that controls whether or not the outline is shown. */
191    private boolean showOutline;
192
193    /**
194     * The shape used to represent an area in each legend item (this should
195     * never be <code>null</code>).
196     */
197    private transient Shape legendArea;
198
199    /**
200     * A flag that can be set to specify that the fill paint should be used
201     * to fill the area under the renderer.
202     * 
203     * @since 1.0.14
204     */
205    private boolean useFillPaint;
206
207    /**
208     * A transformer that is applied to the paint used to fill under the
209     * area *if* it is an instance of GradientPaint.
210     *
211     * @since 1.0.14
212     */
213    private GradientPaintTransformer gradientTransformer;
214
215    /**
216     * Constructs a new renderer.
217     */
218    public XYAreaRenderer() {
219        this(AREA);
220    }
221
222    /**
223     * Constructs a new renderer.
224     *
225     * @param type  the type of the renderer.
226     */
227    public XYAreaRenderer(int type) {
228        this(type, null, null);
229    }
230
231    /**
232     * Constructs a new renderer.  To specify the type of renderer, use one of
233     * the constants: <code>SHAPES</code>, <code>LINES</code>,
234     * <code>SHAPES_AND_LINES</code>, <code>AREA</code> or
235     * <code>AREA_AND_SHAPES</code>.
236     *
237     * @param type  the type of renderer.
238     * @param toolTipGenerator  the tool tip generator to use
239     *                          (<code>null</code> permitted).
240     * @param urlGenerator  the URL generator (<code>null</code> permitted).
241     */
242    public XYAreaRenderer(int type, XYToolTipGenerator toolTipGenerator,
243                          XYURLGenerator urlGenerator) {
244
245        super();
246        setBaseToolTipGenerator(toolTipGenerator);
247        setURLGenerator(urlGenerator);
248
249        if (type == SHAPES) {
250            this.plotShapes = true;
251        }
252        if (type == LINES) {
253            this.plotLines = true;
254        }
255        if (type == SHAPES_AND_LINES) {
256            this.plotShapes = true;
257            this.plotLines = true;
258        }
259        if (type == AREA) {
260            this.plotArea = true;
261        }
262        if (type == AREA_AND_SHAPES) {
263            this.plotArea = true;
264            this.plotShapes = true;
265        }
266        this.showOutline = false;
267        GeneralPath area = new GeneralPath();
268        area.moveTo(0.0f, -4.0f);
269        area.lineTo(3.0f, -2.0f);
270        area.lineTo(4.0f, 4.0f);
271        area.lineTo(-4.0f, 4.0f);
272        area.lineTo(-3.0f, -2.0f);
273        area.closePath();
274        this.legendArea = area;
275        this.useFillPaint = false;
276        this.gradientTransformer = new StandardGradientPaintTransformer();
277    }
278
279    /**
280     * Returns true if shapes are being plotted by the renderer.
281     *
282     * @return <code>true</code> if shapes are being plotted by the renderer.
283     */
284    public boolean getPlotShapes() {
285        return this.plotShapes;
286    }
287
288    /**
289     * Returns true if lines are being plotted by the renderer.
290     *
291     * @return <code>true</code> if lines are being plotted by the renderer.
292     */
293    public boolean getPlotLines() {
294        return this.plotLines;
295    }
296
297    /**
298     * Returns true if Area is being plotted by the renderer.
299     *
300     * @return <code>true</code> if Area is being plotted by the renderer.
301     */
302    public boolean getPlotArea() {
303        return this.plotArea;
304    }
305
306    /**
307     * Returns a flag that controls whether or not outlines of the areas are
308     * drawn.
309     *
310     * @return The flag.
311     *
312     * @see #setOutline(boolean)
313     */
314    public boolean isOutline() {
315        return this.showOutline;
316    }
317
318    /**
319     * Sets a flag that controls whether or not outlines of the areas are drawn
320     * and sends a {@link RendererChangeEvent} to all registered listeners.
321     *
322     * @param show  the flag.
323     *
324     * @see #isOutline()
325     */
326    public void setOutline(boolean show) {
327        this.showOutline = show;
328        fireChangeEvent();
329    }
330
331    /**
332     * Returns the shape used to represent an area in the legend.
333     *
334     * @return The legend area (never <code>null</code>).
335     */
336    public Shape getLegendArea() {
337        return this.legendArea;
338    }
339
340    /**
341     * Sets the shape used as an area in each legend item and sends a
342     * {@link RendererChangeEvent} to all registered listeners.
343     *
344     * @param area  the area (<code>null</code> not permitted).
345     */
346    public void setLegendArea(Shape area) {
347        ParamChecks.nullNotPermitted(area, "area");
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        ParamChecks.nullNotPermitted(transformer, "transformer");
399        this.gradientTransformer = transformer;
400        fireChangeEvent();
401    }
402
403    /**
404     * Initialises the renderer and returns a state object that should be
405     * passed to all subsequent calls to the drawItem() method.
406     *
407     * @param g2  the graphics device.
408     * @param dataArea  the area inside the axes.
409     * @param plot  the plot.
410     * @param data  the data.
411     * @param info  an optional info collection object to return data back to
412     *              the caller.
413     *
414     * @return A state object for use by the renderer.
415     */
416    @Override
417    public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea,
418            XYPlot plot, XYDataset data, PlotRenderingInfo info) {
419        XYAreaRendererState state = new XYAreaRendererState(info);
420
421        // in the rendering process, there is special handling for item
422        // zero, so we can't support processing of visible data items only
423        state.setProcessVisibleItemsOnly(false);
424        return state;
425    }
426
427    /**
428     * Returns a default legend item for the specified series.  Subclasses
429     * should override this method to generate customised items.
430     *
431     * @param datasetIndex  the dataset index (zero-based).
432     * @param series  the series index (zero-based).
433     *
434     * @return A legend item for the series.
435     */
436    @Override
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    @Override
492    public void drawItem(Graphics2D g2, XYItemRendererState state,
493            Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
494            ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset,
495            int series, int item, CrosshairState crosshairState, int pass) {
496
497        if (!getItemVisible(series, item)) {
498            return;
499        }
500        XYAreaRendererState areaState = (XYAreaRendererState) state;
501
502        // get the data point...
503        double x1 = dataset.getXValue(series, item);
504        double y1 = dataset.getYValue(series, item);
505        if (Double.isNaN(y1)) {
506            y1 = 0.0;
507        }
508        double transX1 = domainAxis.valueToJava2D(x1, dataArea,
509                plot.getDomainAxisEdge());
510        double transY1 = rangeAxis.valueToJava2D(y1, dataArea,
511                plot.getRangeAxisEdge());
512
513        // get the previous point and the next point so we can calculate a
514        // "hot spot" for the area (used by the chart entity)...
515        int itemCount = dataset.getItemCount(series);
516        double x0 = dataset.getXValue(series, Math.max(item - 1, 0));
517        double y0 = dataset.getYValue(series, Math.max(item - 1, 0));
518        if (Double.isNaN(y0)) {
519            y0 = 0.0;
520        }
521        double transX0 = domainAxis.valueToJava2D(x0, dataArea,
522                plot.getDomainAxisEdge());
523        double transY0 = rangeAxis.valueToJava2D(y0, dataArea,
524                plot.getRangeAxisEdge());
525
526        double x2 = dataset.getXValue(series, Math.min(item + 1,
527                itemCount - 1));
528        double y2 = dataset.getYValue(series, Math.min(item + 1,
529                itemCount - 1));
530        if (Double.isNaN(y2)) {
531            y2 = 0.0;
532        }
533        double transX2 = domainAxis.valueToJava2D(x2, dataArea,
534                plot.getDomainAxisEdge());
535        double transY2 = rangeAxis.valueToJava2D(y2, dataArea,
536                plot.getRangeAxisEdge());
537
538        double transZero = rangeAxis.valueToJava2D(0.0, dataArea,
539                plot.getRangeAxisEdge());
540        GeneralPath hotspot = new GeneralPath();
541        if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
542            moveTo(hotspot, transZero, ((transX0 + transX1) / 2.0));
543            lineTo(hotspot, ((transY0 + transY1) / 2.0), 
544                            ((transX0 + transX1) / 2.0));
545            lineTo(hotspot, transY1, transX1);
546            lineTo(hotspot, ((transY1 + transY2) / 2.0), 
547                            ((transX1 + transX2) / 2.0));
548            lineTo(hotspot, transZero, ((transX1 + transX2) / 2.0));
549        }
550        else {  // vertical orientation
551            moveTo(hotspot, ((transX0 + transX1) / 2.0), transZero);
552            lineTo(hotspot, ((transX0 + transX1) / 2.0),
553                            ((transY0 + transY1) / 2.0));
554            lineTo(hotspot, transX1, transY1);
555            lineTo(hotspot, ((transX1 + transX2) / 2.0),
556                            ((transY1 + transY2) / 2.0));
557            lineTo(hotspot, ((transX1 + transX2) / 2.0), transZero);
558        }
559        hotspot.closePath();
560
561        if (item == 0) {  // create a new area polygon for the series
562            areaState.area = new GeneralPath();
563            // the first point is (x, 0)
564            double zero = rangeAxis.valueToJava2D(0.0, dataArea,
565                    plot.getRangeAxisEdge());
566            if (plot.getOrientation() == PlotOrientation.VERTICAL) {
567                moveTo(areaState.area, transX1, zero);
568            }
569            else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
570                moveTo(areaState.area, zero, transX1);
571            }
572        }
573
574        // Add each point to Area (x, y)
575        if (plot.getOrientation() == PlotOrientation.VERTICAL) {
576            lineTo(areaState.area, transX1, transY1);
577        }
578        else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
579            lineTo(areaState.area, transY1, transX1);
580        }
581
582        PlotOrientation orientation = plot.getOrientation();
583        Paint paint = getItemPaint(series, item);
584        Stroke stroke = getItemStroke(series, item);
585        g2.setPaint(paint);
586        g2.setStroke(stroke);
587
588        Shape shape;
589        if (getPlotShapes()) {
590            shape = getItemShape(series, item);
591            if (orientation == PlotOrientation.VERTICAL) {
592                shape = ShapeUtilities.createTranslatedShape(shape, transX1,
593                        transY1);
594            }
595            else if (orientation == PlotOrientation.HORIZONTAL) {
596                shape = ShapeUtilities.createTranslatedShape(shape, transY1,
597                        transX1);
598            }
599            g2.draw(shape);
600        }
601
602        if (getPlotLines()) {
603            if (item > 0) {
604                if (plot.getOrientation() == PlotOrientation.VERTICAL) {
605                    areaState.line.setLine(transX0, transY0, transX1, transY1);
606                }
607                else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
608                    areaState.line.setLine(transY0, transX0, transY1, transX1);
609                }
610                g2.draw(areaState.line);
611            }
612        }
613
614        // Check if the item is the last item for the series.
615        // and number of items > 0.  We can't draw an area for a single point.
616        if (getPlotArea() && item > 0 && item == (itemCount - 1)) {
617
618            if (orientation == PlotOrientation.VERTICAL) {
619                // Add the last point (x,0)
620                lineTo(areaState.area, transX1, transZero);
621                areaState.area.closePath();
622            }
623            else if (orientation == PlotOrientation.HORIZONTAL) {
624                // Add the last point (x,0)
625                lineTo(areaState.area, transZero, transX1);
626                areaState.area.closePath();
627            }
628
629            if (this.useFillPaint) {
630                paint = lookupSeriesFillPaint(series);
631            }
632            if (paint instanceof GradientPaint) {
633                GradientPaint gp = (GradientPaint) paint;
634                GradientPaint adjGP = this.gradientTransformer.transform(gp,
635                        dataArea);
636                g2.setPaint(adjGP);
637            }
638            g2.fill(areaState.area);
639
640            // draw an outline around the Area.
641            if (isOutline()) {
642                Shape area = areaState.area;
643
644                // Java2D has some issues drawing dashed lines around "large"
645                // geometrical shapes - for example, see bug 6620013 in the
646                // Java bug database.  So, we'll check if the outline is
647                // dashed and, if it is, do our own clipping before drawing
648                // the outline...
649                Stroke outlineStroke = lookupSeriesOutlineStroke(series);
650                if (outlineStroke instanceof BasicStroke) {
651                    BasicStroke bs = (BasicStroke) outlineStroke;
652                    if (bs.getDashArray() != null) {
653                        Area poly = new Area(areaState.area);
654                        // we make the clip region slightly larger than the
655                        // dataArea so that the clipped edges don't show lines
656                        // on the chart
657                        Area clip = new Area(new Rectangle2D.Double(
658                                dataArea.getX() - 5.0, dataArea.getY() - 5.0,
659                                dataArea.getWidth() + 10.0,
660                                dataArea.getHeight() + 10.0));
661                        poly.intersect(clip);
662                        area = poly;
663                    }
664                } // end of workaround
665
666                g2.setStroke(outlineStroke);
667                g2.setPaint(lookupSeriesOutlinePaint(series));
668                g2.draw(area);
669            }
670        }
671
672        int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
673        int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
674        updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex,
675                rangeAxisIndex, transX1, transY1, orientation);
676
677        // collect entity and tool tip information...
678        EntityCollection entities = state.getEntityCollection();
679        if (entities != null) {
680            addEntity(entities, hotspot, dataset, series, item, 0.0, 0.0);
681        }
682
683    }
684
685    /**
686     * Returns a clone of the renderer.
687     *
688     * @return A clone.
689     *
690     * @throws CloneNotSupportedException  if the renderer cannot be cloned.
691     */
692    @Override
693    public Object clone() throws CloneNotSupportedException {
694        XYAreaRenderer clone = (XYAreaRenderer) super.clone();
695        clone.legendArea = ShapeUtilities.clone(this.legendArea);
696        return clone;
697    }
698
699    /**
700     * Tests this renderer for equality with an arbitrary object.
701     *
702     * @param obj  the object (<code>null</code> permitted).
703     *
704     * @return A boolean.
705     */
706    @Override
707    public boolean equals(Object obj) {
708        if (obj == this) {
709            return true;
710        }
711        if (!(obj instanceof XYAreaRenderer)) {
712            return false;
713        }
714        XYAreaRenderer that = (XYAreaRenderer) obj;
715        if (this.plotArea != that.plotArea) {
716            return false;
717        }
718        if (this.plotLines != that.plotLines) {
719            return false;
720        }
721        if (this.plotShapes != that.plotShapes) {
722            return false;
723        }
724        if (this.showOutline != that.showOutline) {
725            return false;
726        }
727        if (this.useFillPaint != that.useFillPaint) {
728            return false;
729        }
730        if (!this.gradientTransformer.equals(that.gradientTransformer)) {
731            return false;
732        }
733        if (!ShapeUtilities.equal(this.legendArea, that.legendArea)) {
734            return false;
735        }
736        return true;
737    }
738
739    /**
740     * Returns a hash code for this instance.
741     *
742     * @return A hash code.
743     */
744    @Override
745    public int hashCode() {
746        int result = super.hashCode();
747        result = HashUtilities.hashCode(result, this.plotArea);
748        result = HashUtilities.hashCode(result, this.plotLines);
749        result = HashUtilities.hashCode(result, this.plotShapes);
750        result = HashUtilities.hashCode(result, this.useFillPaint);
751        return result;
752    }
753
754    /**
755     * Provides serialization support.
756     *
757     * @param stream  the input stream.
758     *
759     * @throws IOException  if there is an I/O error.
760     * @throws ClassNotFoundException  if there is a classpath problem.
761     */
762    private void readObject(ObjectInputStream stream)
763            throws IOException, ClassNotFoundException {
764        stream.defaultReadObject();
765        this.legendArea = SerialUtilities.readShape(stream);
766    }
767
768    /**
769     * Provides serialization support.
770     *
771     * @param stream  the output stream.
772     *
773     * @throws IOException  if there is an I/O error.
774     */
775    private void writeObject(ObjectOutputStream stream) throws IOException {
776        stream.defaultWriteObject();
777        SerialUtilities.writeShape(this.legendArea, stream);
778    }
779}