1 /* ===========================================================
2  * JFreeChart : a free chart library for the Java(tm) platform
3  * ===========================================================
4  *
5  * (C) Copyright 2000-2013, by Object Refinery Limited and Contributors.
6  *
7  * Project Info:  http://www.jfree.org/jfreechart/index.html
8  *
9  * This library is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
17  * License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
22  * USA.
23  *
24  * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
25  * Other names may be trademarks of their respective owners.]
26  *
27  * -------------------
28  * XYAreaRenderer.java
29  * -------------------
30  * (C) Copyright 2002-2013, by Hari and Contributors.
31  *
32  * Original Author:  Hari (ourhari@hotmail.com);
33  * Contributor(s):   David Gilbert (for Object Refinery Limited);
34  *                   Richard Atkinson;
35  *                   Christian W. Zuckschwerdt;
36  *                   Martin Krauskopf;
37  *
38  * Changes:
39  * --------
40  * 03-Apr-2002 : Version 1, contributed by Hari.  This class is based on the
41  *               StandardXYItemRenderer class (DG);
42  * 09-Apr-2002 : Removed the translated zero from the drawItem method -
43  *               overridden the initialise() method to calculate it (DG);
44  * 30-May-2002 : Added tool tip generator to constructor to match super
45  *               class (DG);
46  * 25-Jun-2002 : Removed unnecessary local variable (DG);
47  * 05-Aug-2002 : Small modification to drawItem method to support URLs for HTML
48  *               image maps (RA);
49  * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
50  * 07-Nov-2002 : Renamed AreaXYItemRenderer --> XYAreaRenderer (DG);
51  * 25-Mar-2003 : Implemented Serializable (DG);
52  * 01-May-2003 : Modified drawItem() method signature (DG);
53  * 27-Jul-2003 : Made line and polygon properties protected rather than
54  *               private (RA);
55  * 30-Jul-2003 : Modified entity constructor (CZ);
56  * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
57  * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
58  * 07-Oct-2003 : Added renderer state (DG);
59  * 08-Dec-2003 : Modified hotspot for chart entity (DG);
60  * 10-Feb-2004 : Changed the drawItem() method to make cut-and-paste overriding
61  *               easier.  Also moved state class into this class (DG);
62  * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState.  Renamed
63  *               XYToolTipGenerator --> XYItemLabelGenerator (DG);
64  * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
65  *               getYValue() (DG);
66  * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
67  * 19-Jan-2005 : Now accesses primitives only from dataset (DG);
68  * 21-Mar-2005 : Override getLegendItem() and equals() methods (DG);
69  * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
70  * ------------- JFREECHART 1.0.x ---------------------------------------------
71  * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
72  * 14-Feb-2007 : Fixed bug in clone() (DG);
73  * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
74  * 04-May-2007 : Set processVisibleItemsOnly flag to false (DG);
75  * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG);
76  * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
77  * 17-Jun-2008 : Apply legend font and paint attributes (DG);
78  * 31-Dec-2008 : Fix for bug 2471906 - dashed outlines performance issue (DG);
79  * 11-Jun-2009 : Added a useFillPaint flag and a GradientPaintTransformer for
80  *               the paint under the series (DG);
81  * 06-Oct-2011 : Avoid GeneralPath methods requiring Java 1.5 (MK);
82  * 03-Jul-2013 : Use ParamChecks (DG);
83  *
84  */
85 
86 package org.jfree.chart.renderer.xy;
87 
88 import java.awt.BasicStroke;
89 import java.awt.GradientPaint;
90 import java.awt.Graphics2D;
91 import java.awt.Paint;
92 import java.awt.Shape;
93 import java.awt.Stroke;
94 import java.awt.geom.Area;
95 import java.awt.geom.GeneralPath;
96 import java.awt.geom.Line2D;
97 import java.awt.geom.Rectangle2D;
98 import java.io.IOException;
99 import java.io.ObjectInputStream;
100 import java.io.ObjectOutputStream;
101 
102 import org.jfree.chart.HashUtilities;
103 import org.jfree.chart.LegendItem;
104 import org.jfree.chart.axis.ValueAxis;
105 import org.jfree.chart.entity.EntityCollection;
106 import org.jfree.chart.event.RendererChangeEvent;
107 import org.jfree.chart.labels.XYSeriesLabelGenerator;
108 import org.jfree.chart.labels.XYToolTipGenerator;
109 import org.jfree.chart.plot.CrosshairState;
110 import org.jfree.chart.plot.PlotOrientation;
111 import org.jfree.chart.plot.PlotRenderingInfo;
112 import org.jfree.chart.plot.XYPlot;
113 import org.jfree.chart.urls.XYURLGenerator;
114 import org.jfree.chart.util.ParamChecks;
115 import org.jfree.data.xy.XYDataset;
116 import org.jfree.io.SerialUtilities;
117 import org.jfree.ui.GradientPaintTransformer;
118 import org.jfree.ui.StandardGradientPaintTransformer;
119 import org.jfree.util.PublicCloneable;
120 import 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  */
132 public 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          */
XYAreaRendererState(PlotRenderingInfo info)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      */
XYAreaRenderer()218     public XYAreaRenderer() {
219         this(AREA);
220     }
221 
222     /**
223      * Constructs a new renderer.
224      *
225      * @param type  the type of the renderer.
226      */
XYAreaRenderer(int type)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      */
XYAreaRenderer(int type, XYToolTipGenerator toolTipGenerator, XYURLGenerator urlGenerator)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      */
getPlotShapes()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      */
getPlotLines()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      */
getPlotArea()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      */
isOutline()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      */
setOutline(boolean show)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      */
getLegendArea()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      */
setLegendArea(Shape area)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      */
getUseFillPaint()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      */
setUseFillPaint(boolean use)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      */
getGradientTransformer()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      */
setGradientTransformer(GradientPaintTransformer transformer)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
initialise(Graphics2D g2, Rectangle2D dataArea, XYPlot plot, XYDataset data, PlotRenderingInfo info)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
getLegendItem(int datasetIndex, int series)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
drawItem(Graphics2D g2, XYItemRendererState state, Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, int series, int item, CrosshairState crosshairState, int pass)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
clone()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
equals(Object obj)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
hashCode()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      */
readObject(ObjectInputStream stream)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      */
writeObject(ObjectOutputStream stream)775     private void writeObject(ObjectOutputStream stream) throws IOException {
776         stream.defaultWriteObject();
777         SerialUtilities.writeShape(this.legendArea, stream);
778     }
779 }
780