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     * XYAreaRenderer2.java
029     * --------------------
030     * (C) Copyright 2004-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
048     *               HTML 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
061     *               overriding easier.  Also moved state class into this
062     *               class (DG);
063     * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState.  Renamed
064     *               XYToolTipGenerator --> XYItemLabelGenerator (DG);
065     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with
066     *               getYValue() (DG);
067     * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
068     * 19-Jan-2005 : Now accesses only primitives from the dataset (DG);
069     * 21-Mar-2005 : Override getLegendItem() (DG);
070     * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
071     * ------------- JFREECHART 1.0.x ---------------------------------------------
072     * 30-Nov-2006 : Fixed equals() and clone() implementations (DG);
073     * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
074     * 20-Apr-2007 : Updated getLegendItem() and drawItem() for renderer
075     *               change (DG);
076     * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG);
077     * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
078     * 17-Jun-2008 : Apply legend font and paint attributes (DG);
079     * 06-Oct-2011 : Avoid GeneralPath methods requiring Java 1.5 (MK);
080     *
081     */
082    
083    package org.jfree.chart.renderer.xy;
084    
085    import java.awt.Graphics2D;
086    import java.awt.Paint;
087    import java.awt.Shape;
088    import java.awt.Stroke;
089    import java.awt.geom.GeneralPath;
090    import java.awt.geom.Rectangle2D;
091    import java.io.IOException;
092    import java.io.ObjectInputStream;
093    import java.io.ObjectOutputStream;
094    
095    import org.jfree.chart.LegendItem;
096    import org.jfree.chart.axis.ValueAxis;
097    import org.jfree.chart.entity.EntityCollection;
098    import org.jfree.chart.entity.XYItemEntity;
099    import org.jfree.chart.event.RendererChangeEvent;
100    import org.jfree.chart.labels.XYSeriesLabelGenerator;
101    import org.jfree.chart.labels.XYToolTipGenerator;
102    import org.jfree.chart.plot.CrosshairState;
103    import org.jfree.chart.plot.PlotOrientation;
104    import org.jfree.chart.plot.PlotRenderingInfo;
105    import org.jfree.chart.plot.XYPlot;
106    import org.jfree.chart.urls.XYURLGenerator;
107    import org.jfree.data.xy.XYDataset;
108    import org.jfree.io.SerialUtilities;
109    import org.jfree.util.PublicCloneable;
110    import org.jfree.util.ShapeUtilities;
111    
112    /**
113     * Area item renderer for an {@link XYPlot}. The example shown here is
114     * generated by the <code>XYAreaRenderer2Demo1.java</code> program included in
115     * the JFreeChart demo collection:
116     * <br><br>
117     * <img src="../../../../../images/XYAreaRenderer2Sample.png"
118     * alt="XYAreaRenderer2Sample.png" />
119     */
120    public class XYAreaRenderer2 extends AbstractXYItemRenderer
121            implements XYItemRenderer, PublicCloneable {
122    
123        /** For serialization. */
124        private static final long serialVersionUID = -7378069681579984133L;
125    
126        /** A flag that controls whether or not the outline is shown. */
127        private boolean showOutline;
128    
129        /**
130         * The shape used to represent an area in each legend item (this should
131         * never be <code>null</code>).
132         */
133        private transient Shape legendArea;
134    
135        /**
136         * Constructs a new renderer.
137         */
138        public XYAreaRenderer2() {
139            this(null, null);
140        }
141    
142        /**
143         * Constructs a new renderer.
144         *
145         * @param labelGenerator  the tool tip generator to use.  <code>null</code>
146         *                        is none.
147         * @param urlGenerator  the URL generator (null permitted).
148         */
149        public XYAreaRenderer2(XYToolTipGenerator labelGenerator,
150                               XYURLGenerator urlGenerator) {
151            super();
152            this.showOutline = false;
153            setBaseToolTipGenerator(labelGenerator);
154            setURLGenerator(urlGenerator);
155            GeneralPath area = new GeneralPath();
156            area.moveTo(0.0f, -4.0f);
157            area.lineTo(3.0f, -2.0f);
158            area.lineTo(4.0f, 4.0f);
159            area.lineTo(-4.0f, 4.0f);
160            area.lineTo(-3.0f, -2.0f);
161            area.closePath();
162            this.legendArea = area;
163        }
164    
165        /**
166         * Returns a flag that controls whether or not outlines of the areas are
167         * drawn.
168         *
169         * @return The flag.
170         *
171         * @see #setOutline(boolean)
172         */
173        public boolean isOutline() {
174            return this.showOutline;
175        }
176    
177        /**
178         * Sets a flag that controls whether or not outlines of the areas are
179         * drawn, and sends a {@link RendererChangeEvent} to all registered
180         * listeners.
181         *
182         * @param show  the flag.
183         *
184         * @see #isOutline()
185         */
186        public void setOutline(boolean show) {
187            this.showOutline = show;
188            fireChangeEvent();
189        }
190    
191        /**
192         * This method should not be used.
193         *
194         * @return <code>false</code> always.
195         *
196         * @deprecated This method was included in the API by mistake and serves
197         *     no useful purpose.  It has always returned <code>false</code>.
198         *
199         */
200        public boolean getPlotLines() {
201            return false;
202        }
203    
204        /**
205         * Returns the shape used to represent an area in the legend.
206         *
207         * @return The legend area (never <code>null</code>).
208         *
209         * @see #setLegendArea(Shape)
210         */
211        public Shape getLegendArea() {
212            return this.legendArea;
213        }
214    
215        /**
216         * Sets the shape used as an area in each legend item and sends a
217         * {@link RendererChangeEvent} to all registered listeners.
218         *
219         * @param area  the area (<code>null</code> not permitted).
220         *
221         * @see #getLegendArea()
222         */
223        public void setLegendArea(Shape area) {
224            if (area == null) {
225                throw new IllegalArgumentException("Null 'area' argument.");
226            }
227            this.legendArea = area;
228            fireChangeEvent();
229        }
230    
231        /**
232         * Returns a default legend item for the specified series.  Subclasses
233         * should override this method to generate customised items.
234         *
235         * @param datasetIndex  the dataset index (zero-based).
236         * @param series  the series index (zero-based).
237         *
238         * @return A legend item for the series.
239         */
240        public LegendItem getLegendItem(int datasetIndex, int series) {
241            LegendItem result = null;
242            XYPlot xyplot = getPlot();
243            if (xyplot != null) {
244                XYDataset dataset = xyplot.getDataset(datasetIndex);
245                if (dataset != null) {
246                    XYSeriesLabelGenerator lg = getLegendItemLabelGenerator();
247                    String label = lg.generateLabel(dataset, series);
248                    String description = label;
249                    String toolTipText = null;
250                    if (getLegendItemToolTipGenerator() != null) {
251                        toolTipText = getLegendItemToolTipGenerator().generateLabel(
252                                dataset, series);
253                    }
254                    String urlText = null;
255                    if (getLegendItemURLGenerator() != null) {
256                        urlText = getLegendItemURLGenerator().generateLabel(
257                                dataset, series);
258                    }
259                    Paint paint = lookupSeriesPaint(series);
260                    result = new LegendItem(label, description, toolTipText,
261                            urlText, this.legendArea, paint);
262                    result.setLabelFont(lookupLegendTextFont(series));
263                    Paint labelPaint = lookupLegendTextPaint(series);
264                    if (labelPaint != null) {
265                        result.setLabelPaint(labelPaint);
266                    }
267                    result.setDataset(dataset);
268                    result.setDatasetIndex(datasetIndex);
269                    result.setSeriesKey(dataset.getSeriesKey(series));
270                    result.setSeriesIndex(series);
271                }
272            }
273            return result;
274        }
275    
276        /**
277         * Draws the visual representation of a single data item.
278         *
279         * @param g2  the graphics device.
280         * @param state  the renderer state.
281         * @param dataArea  the area within which the data is being drawn.
282         * @param info  collects information about the drawing.
283         * @param plot  the plot (can be used to obtain standard color
284         *              information etc).
285         * @param domainAxis  the domain axis.
286         * @param rangeAxis  the range axis.
287         * @param dataset  the dataset.
288         * @param series  the series index (zero-based).
289         * @param item  the item index (zero-based).
290         * @param crosshairState  crosshair information for the plot
291         *                        (<code>null</code> permitted).
292         * @param pass  the pass index.
293         */
294        public void drawItem(Graphics2D g2,
295                             XYItemRendererState state,
296                             Rectangle2D dataArea,
297                             PlotRenderingInfo info,
298                             XYPlot plot,
299                             ValueAxis domainAxis,
300                             ValueAxis rangeAxis,
301                             XYDataset dataset,
302                             int series,
303                             int item,
304                             CrosshairState crosshairState,
305                             int pass) {
306    
307            if (!getItemVisible(series, item)) {
308                return;
309            }
310            // get the data point...
311            double x1 = dataset.getXValue(series, item);
312            double y1 = dataset.getYValue(series, item);
313            if (Double.isNaN(y1)) {
314                y1 = 0.0;
315            }
316    
317            double transX1 = domainAxis.valueToJava2D(x1, dataArea,
318                    plot.getDomainAxisEdge());
319            double transY1 = rangeAxis.valueToJava2D(y1, dataArea,
320                    plot.getRangeAxisEdge());
321    
322            // get the previous point and the next point so we can calculate a
323            // "hot spot" for the area (used by the chart entity)...
324            double x0 = dataset.getXValue(series, Math.max(item - 1, 0));
325            double y0 = dataset.getYValue(series, Math.max(item - 1, 0));
326            if (Double.isNaN(y0)) {
327                y0 = 0.0;
328            }
329            double transX0 = domainAxis.valueToJava2D(x0, dataArea,
330                    plot.getDomainAxisEdge());
331            double transY0 = rangeAxis.valueToJava2D(y0, dataArea,
332                    plot.getRangeAxisEdge());
333    
334            int itemCount = dataset.getItemCount(series);
335            double x2 = dataset.getXValue(series, Math.min(item + 1,
336                    itemCount - 1));
337            double y2 = dataset.getYValue(series, Math.min(item + 1,
338                    itemCount - 1));
339            if (Double.isNaN(y2)) {
340                y2 = 0.0;
341            }
342            double transX2 = domainAxis.valueToJava2D(x2, dataArea,
343                    plot.getDomainAxisEdge());
344            double transY2 = rangeAxis.valueToJava2D(y2, dataArea,
345                    plot.getRangeAxisEdge());
346    
347            double transZero = rangeAxis.valueToJava2D(0.0, dataArea,
348                    plot.getRangeAxisEdge());
349            GeneralPath hotspot = new GeneralPath();
350            if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
351                moveTo(hotspot, transZero, ((transX0 + transX1) / 2.0));
352                lineTo(hotspot, ((transY0 + transY1) / 2.0),
353                                ((transX0 + transX1) / 2.0));
354                lineTo(hotspot, transY1, transX1);
355                lineTo(hotspot, ((transY1 + transY2) / 2.0),
356                                ((transX1 + transX2) / 2.0));
357                lineTo(hotspot, transZero, ((transX1 + transX2) / 2.0));
358            }
359            else {  // vertical orientation
360                moveTo(hotspot, ((transX0 + transX1) / 2.0), transZero);
361                lineTo(hotspot, ((transX0 + transX1) / 2.0),
362                                ((transY0 + transY1) / 2.0));
363                lineTo(hotspot, transX1, transY1);
364                lineTo(hotspot, ((transX1 + transX2) / 2.0),
365                                ((transY1 + transY2) / 2.0));
366                lineTo(hotspot, ((transX1 + transX2) / 2.0), transZero);
367            }
368            hotspot.closePath();
369    
370            PlotOrientation orientation = plot.getOrientation();
371            Paint paint = getItemPaint(series, item);
372            Stroke stroke = getItemStroke(series, item);
373            g2.setPaint(paint);
374            g2.setStroke(stroke);
375    
376            // Check if the item is the last item for the series.
377            // and number of items > 0.  We can't draw an area for a single point.
378            g2.fill(hotspot);
379    
380            // draw an outline around the Area.
381            if (isOutline()) {
382                g2.setStroke(lookupSeriesOutlineStroke(series));
383                g2.setPaint(lookupSeriesOutlinePaint(series));
384                g2.draw(hotspot);
385            }
386            int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
387            int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
388            updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex,
389                    rangeAxisIndex, transX1, transY1, orientation);
390    
391            // collect entity and tool tip information...
392            if (state.getInfo() != null) {
393                EntityCollection entities = state.getEntityCollection();
394                if (entities != null) {
395                    String tip = null;
396                    XYToolTipGenerator generator = getToolTipGenerator(series,
397                            item);
398                    if (generator != null) {
399                        tip = generator.generateToolTip(dataset, series, item);
400                    }
401                    String url = null;
402                    if (getURLGenerator() != null) {
403                        url = getURLGenerator().generateURL(dataset, series, item);
404                    }
405                    XYItemEntity entity = new XYItemEntity(hotspot, dataset,
406                            series, item, tip, url);
407                    entities.add(entity);
408                }
409            }
410    
411        }
412    
413        /**
414         * Tests this renderer for equality with an arbitrary object.
415         *
416         * @param obj  the object (<code>null</code> not permitted).
417         *
418         * @return A boolean.
419         */
420        public boolean equals(Object obj) {
421            if (obj == this) {
422                return true;
423            }
424            if (!(obj instanceof XYAreaRenderer2)) {
425                return false;
426            }
427            XYAreaRenderer2 that = (XYAreaRenderer2) obj;
428            if (this.showOutline != that.showOutline) {
429                return false;
430            }
431            if (!ShapeUtilities.equal(this.legendArea, that.legendArea)) {
432                return false;
433            }
434            return super.equals(obj);
435        }
436    
437        /**
438         * Returns a clone of the renderer.
439         *
440         * @return A clone.
441         *
442         * @throws CloneNotSupportedException  if the renderer cannot be cloned.
443         */
444        public Object clone() throws CloneNotSupportedException {
445            XYAreaRenderer2 clone = (XYAreaRenderer2) super.clone();
446            clone.legendArea = ShapeUtilities.clone(this.legendArea);
447            return clone;
448        }
449    
450        /**
451         * Provides serialization support.
452         *
453         * @param stream  the input stream.
454         *
455         * @throws IOException  if there is an I/O error.
456         * @throws ClassNotFoundException  if there is a classpath problem.
457         */
458        private void readObject(ObjectInputStream stream)
459                throws IOException, ClassNotFoundException {
460            stream.defaultReadObject();
461            this.legendArea = SerialUtilities.readShape(stream);
462        }
463    
464        /**
465         * Provides serialization support.
466         *
467         * @param stream  the output stream.
468         *
469         * @throws IOException  if there is an I/O error.
470         */
471        private void writeObject(ObjectOutputStream stream) throws IOException {
472            stream.defaultWriteObject();
473            SerialUtilities.writeShape(this.legendArea, stream);
474        }
475    
476    }
477