001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors.
006     *
007     * Project Info:  http://www.jfree.org/jfreechart/index.html
008     *
009     * This library is free software; you can redistribute it and/or modify it
010     * under the terms of the GNU Lesser General Public License as published by
011     * the Free Software Foundation; either version 2.1 of the License, or
012     * (at your option) any later version.
013     *
014     * This library is distributed in the hope that it will be useful, but
015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017     * License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this library; if not, write to the Free Software
021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
022     * USA.
023     *
024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025     * in the United States and other countries.]
026     *
027     * --------------------
028     * XYAreaRenderer2.java
029     * --------------------
030     * (C) Copyright 2004-2008, 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     *
037     * Changes:
038     * --------
039     * 03-Apr-2002 : Version 1, contributed by Hari.  This class is based on the
040     *               StandardXYItemRenderer class (DG);
041     * 09-Apr-2002 : Removed the translated zero from the drawItem method -
042     *               overridden the initialise() method to calculate it (DG);
043     * 30-May-2002 : Added tool tip generator to constructor to match super
044     *               class (DG);
045     * 25-Jun-2002 : Removed unnecessary local variable (DG);
046     * 05-Aug-2002 : Small modification to drawItem method to support URLs for
047     *               HTML image maps (RA);
048     * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
049     * 07-Nov-2002 : Renamed AreaXYItemRenderer --> XYAreaRenderer (DG);
050     * 25-Mar-2003 : Implemented Serializable (DG);
051     * 01-May-2003 : Modified drawItem() method signature (DG);
052     * 27-Jul-2003 : Made line and polygon properties protected rather than
053     *               private (RA);
054     * 30-Jul-2003 : Modified entity constructor (CZ);
055     * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
056     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
057     * 07-Oct-2003 : Added renderer state (DG);
058     * 08-Dec-2003 : Modified hotspot for chart entity (DG);
059     * 10-Feb-2004 : Changed the drawItem() method to make cut-and-paste
060     *               overriding easier.  Also moved state class into this
061     *               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 only primitives from the dataset (DG);
068     * 21-Mar-2005 : Override getLegendItem() (DG);
069     * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
070     * ------------- JFREECHART 1.0.x ---------------------------------------------
071     * 30-Nov-2006 : Fixed equals() and clone() implementations (DG);
072     * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
073     * 20-Apr-2007 : Updated getLegendItem() and drawItem() for renderer
074     *               change (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     *
079     */
080    
081    package org.jfree.chart.renderer.xy;
082    
083    import java.awt.Graphics2D;
084    import java.awt.Paint;
085    import java.awt.Polygon;
086    import java.awt.Shape;
087    import java.awt.Stroke;
088    import java.awt.geom.GeneralPath;
089    import java.awt.geom.Rectangle2D;
090    import java.io.IOException;
091    import java.io.ObjectInputStream;
092    import java.io.ObjectOutputStream;
093    
094    import org.jfree.chart.LegendItem;
095    import org.jfree.chart.axis.ValueAxis;
096    import org.jfree.chart.entity.EntityCollection;
097    import org.jfree.chart.entity.XYItemEntity;
098    import org.jfree.chart.event.RendererChangeEvent;
099    import org.jfree.chart.labels.XYSeriesLabelGenerator;
100    import org.jfree.chart.labels.XYToolTipGenerator;
101    import org.jfree.chart.plot.CrosshairState;
102    import org.jfree.chart.plot.PlotOrientation;
103    import org.jfree.chart.plot.PlotRenderingInfo;
104    import org.jfree.chart.plot.XYPlot;
105    import org.jfree.chart.urls.XYURLGenerator;
106    import org.jfree.data.xy.XYDataset;
107    import org.jfree.io.SerialUtilities;
108    import org.jfree.util.PublicCloneable;
109    import org.jfree.util.ShapeUtilities;
110    
111    /**
112     * Area item renderer for an {@link XYPlot}. The example shown here is
113     * generated by the <code>XYAreaRenderer2Demo1.java</code> program included in
114     * the JFreeChart demo collection:
115     * <br><br>
116     * <img src="../../../../../images/XYAreaRenderer2Sample.png"
117     * alt="XYAreaRenderer2Sample.png" />
118     */
119    public class XYAreaRenderer2 extends AbstractXYItemRenderer
120            implements XYItemRenderer, PublicCloneable {
121    
122        /** For serialization. */
123        private static final long serialVersionUID = -7378069681579984133L;
124    
125        /** A flag that controls whether or not the outline is shown. */
126        private boolean showOutline;
127    
128        /**
129         * The shape used to represent an area in each legend item (this should
130         * never be <code>null</code>).
131         */
132        private transient Shape legendArea;
133    
134        /**
135         * Constructs a new renderer.
136         */
137        public XYAreaRenderer2() {
138            this(null, null);
139        }
140    
141        /**
142         * Constructs a new renderer.
143         *
144         * @param labelGenerator  the tool tip generator to use.  <code>null</code>
145         *                        is none.
146         * @param urlGenerator  the URL generator (null permitted).
147         */
148        public XYAreaRenderer2(XYToolTipGenerator labelGenerator,
149                               XYURLGenerator urlGenerator) {
150            super();
151            this.showOutline = false;
152            setBaseToolTipGenerator(labelGenerator);
153            setURLGenerator(urlGenerator);
154            GeneralPath area = new GeneralPath();
155            area.moveTo(0.0f, -4.0f);
156            area.lineTo(3.0f, -2.0f);
157            area.lineTo(4.0f, 4.0f);
158            area.lineTo(-4.0f, 4.0f);
159            area.lineTo(-3.0f, -2.0f);
160            area.closePath();
161            this.legendArea = area;
162        }
163    
164        /**
165         * Returns a flag that controls whether or not outlines of the areas are
166         * drawn.
167         *
168         * @return The flag.
169         *
170         * @see #setOutline(boolean)
171         */
172        public boolean isOutline() {
173            return this.showOutline;
174        }
175    
176        /**
177         * Sets a flag that controls whether or not outlines of the areas are
178         * drawn, and sends a {@link RendererChangeEvent} to all registered
179         * listeners.
180         *
181         * @param show  the flag.
182         *
183         * @see #isOutline()
184         */
185        public void setOutline(boolean show) {
186            this.showOutline = show;
187            fireChangeEvent();
188        }
189    
190        /**
191         * This method should not be used.
192         *
193         * @return <code>false</code> always.
194         *
195         * @deprecated This method was included in the API by mistake and serves
196         *     no useful purpose.  It has always returned <code>false</code>.
197         *
198         */
199        public boolean getPlotLines() {
200            return false;
201        }
202    
203        /**
204         * Returns the shape used to represent an area in the legend.
205         *
206         * @return The legend area (never <code>null</code>).
207         *
208         * @see #setLegendArea(Shape)
209         */
210        public Shape getLegendArea() {
211            return this.legendArea;
212        }
213    
214        /**
215         * Sets the shape used as an area in each legend item and sends a
216         * {@link RendererChangeEvent} to all registered listeners.
217         *
218         * @param area  the area (<code>null</code> not permitted).
219         *
220         * @see #getLegendArea()
221         */
222        public void setLegendArea(Shape area) {
223            if (area == null) {
224                throw new IllegalArgumentException("Null 'area' argument.");
225            }
226            this.legendArea = area;
227            fireChangeEvent();
228        }
229    
230        /**
231         * Returns a default legend item for the specified series.  Subclasses
232         * should override this method to generate customised items.
233         *
234         * @param datasetIndex  the dataset index (zero-based).
235         * @param series  the series index (zero-based).
236         *
237         * @return A legend item for the series.
238         */
239        public LegendItem getLegendItem(int datasetIndex, int series) {
240            LegendItem result = null;
241            XYPlot xyplot = getPlot();
242            if (xyplot != null) {
243                XYDataset dataset = xyplot.getDataset(datasetIndex);
244                if (dataset != null) {
245                    XYSeriesLabelGenerator lg = getLegendItemLabelGenerator();
246                    String label = lg.generateLabel(dataset, series);
247                    String description = label;
248                    String toolTipText = null;
249                    if (getLegendItemToolTipGenerator() != null) {
250                        toolTipText = getLegendItemToolTipGenerator().generateLabel(
251                                dataset, series);
252                    }
253                    String urlText = null;
254                    if (getLegendItemURLGenerator() != null) {
255                        urlText = getLegendItemURLGenerator().generateLabel(
256                                dataset, series);
257                    }
258                    Paint paint = lookupSeriesPaint(series);
259                    result = new LegendItem(label, description, toolTipText,
260                            urlText, this.legendArea, paint);
261                    result.setLabelFont(lookupLegendTextFont(series));
262                    Paint labelPaint = lookupLegendTextPaint(series);
263                    if (labelPaint != null) {
264                        result.setLabelPaint(labelPaint);
265                    }
266                    result.setDataset(dataset);
267                    result.setDatasetIndex(datasetIndex);
268                    result.setSeriesKey(dataset.getSeriesKey(series));
269                    result.setSeriesIndex(series);
270                }
271            }
272            return result;
273        }
274    
275        /**
276         * Draws the visual representation of a single data item.
277         *
278         * @param g2  the graphics device.
279         * @param state  the renderer state.
280         * @param dataArea  the area within which the data is being drawn.
281         * @param info  collects information about the drawing.
282         * @param plot  the plot (can be used to obtain standard color
283         *              information etc).
284         * @param domainAxis  the domain axis.
285         * @param rangeAxis  the range axis.
286         * @param dataset  the dataset.
287         * @param series  the series index (zero-based).
288         * @param item  the item index (zero-based).
289         * @param crosshairState  crosshair information for the plot
290         *                        (<code>null</code> permitted).
291         * @param pass  the pass index.
292         */
293        public void drawItem(Graphics2D g2,
294                             XYItemRendererState state,
295                             Rectangle2D dataArea,
296                             PlotRenderingInfo info,
297                             XYPlot plot,
298                             ValueAxis domainAxis,
299                             ValueAxis rangeAxis,
300                             XYDataset dataset,
301                             int series,
302                             int item,
303                             CrosshairState crosshairState,
304                             int pass) {
305    
306            if (!getItemVisible(series, item)) {
307                return;
308            }
309            // get the data point...
310            double x1 = dataset.getXValue(series, item);
311            double y1 = dataset.getYValue(series, item);
312            if (Double.isNaN(y1)) {
313                y1 = 0.0;
314            }
315    
316            double transX1 = domainAxis.valueToJava2D(x1, dataArea,
317                    plot.getDomainAxisEdge());
318            double transY1 = rangeAxis.valueToJava2D(y1, dataArea,
319                    plot.getRangeAxisEdge());
320    
321            // get the previous point and the next point so we can calculate a
322            // "hot spot" for the area (used by the chart entity)...
323            double x0 = dataset.getXValue(series, Math.max(item - 1, 0));
324            double y0 = dataset.getYValue(series, Math.max(item - 1, 0));
325            if (Double.isNaN(y0)) {
326                y0 = 0.0;
327            }
328            double transX0 = domainAxis.valueToJava2D(x0, dataArea,
329                    plot.getDomainAxisEdge());
330            double transY0 = rangeAxis.valueToJava2D(y0, dataArea,
331                    plot.getRangeAxisEdge());
332    
333            int itemCount = dataset.getItemCount(series);
334            double x2 = dataset.getXValue(series, Math.min(item + 1,
335                    itemCount - 1));
336            double y2 = dataset.getYValue(series, Math.min(item + 1,
337                    itemCount - 1));
338            if (Double.isNaN(y2)) {
339                y2 = 0.0;
340            }
341            double transX2 = domainAxis.valueToJava2D(x2, dataArea,
342                    plot.getDomainAxisEdge());
343            double transY2 = rangeAxis.valueToJava2D(y2, dataArea,
344                    plot.getRangeAxisEdge());
345    
346            double transZero = rangeAxis.valueToJava2D(0.0, dataArea,
347                    plot.getRangeAxisEdge());
348            Polygon hotspot = null;
349            if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
350                hotspot = new Polygon();
351                hotspot.addPoint((int) transZero,
352                        (int) ((transX0 + transX1) / 2.0));
353                hotspot.addPoint((int) ((transY0 + transY1) / 2.0),
354                        (int) ((transX0 + transX1) / 2.0));
355                hotspot.addPoint((int) transY1, (int) transX1);
356                hotspot.addPoint((int) ((transY1 + transY2) / 2.0),
357                        (int) ((transX1 + transX2) / 2.0));
358                hotspot.addPoint((int) transZero,
359                        (int) ((transX1 + transX2) / 2.0));
360            }
361            else {  // vertical orientation
362                hotspot = new Polygon();
363                hotspot.addPoint((int) ((transX0 + transX1) / 2.0),
364                        (int) transZero);
365                hotspot.addPoint((int) ((transX0 + transX1) / 2.0),
366                        (int) ((transY0 + transY1) / 2.0));
367                hotspot.addPoint((int) transX1, (int) transY1);
368                hotspot.addPoint((int) ((transX1 + transX2) / 2.0),
369                        (int) ((transY1 + transY2) / 2.0));
370                hotspot.addPoint((int) ((transX1 + transX2) / 2.0),
371                        (int) transZero);
372            }
373    
374            PlotOrientation orientation = plot.getOrientation();
375            Paint paint = getItemPaint(series, item);
376            Stroke stroke = getItemStroke(series, item);
377            g2.setPaint(paint);
378            g2.setStroke(stroke);
379    
380            // Check if the item is the last item for the series.
381            // and number of items > 0.  We can't draw an area for a single point.
382            g2.fill(hotspot);
383    
384            // draw an outline around the Area.
385            if (isOutline()) {
386                g2.setStroke(lookupSeriesOutlineStroke(series));
387                g2.setPaint(lookupSeriesOutlinePaint(series));
388                g2.draw(hotspot);
389            }
390            int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
391            int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
392            updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex,
393                    rangeAxisIndex, transX1, transY1, orientation);
394    
395            // collect entity and tool tip information...
396            if (state.getInfo() != null) {
397                EntityCollection entities = state.getEntityCollection();
398                if (entities != null && hotspot != null) {
399                    String tip = null;
400                    XYToolTipGenerator generator = getToolTipGenerator(series,
401                            item);
402                    if (generator != null) {
403                        tip = generator.generateToolTip(dataset, series, item);
404                    }
405                    String url = null;
406                    if (getURLGenerator() != null) {
407                        url = getURLGenerator().generateURL(dataset, series, item);
408                    }
409                    XYItemEntity entity = new XYItemEntity(hotspot, dataset,
410                            series, item, tip, url);
411                    entities.add(entity);
412                }
413            }
414    
415        }
416    
417        /**
418         * Tests this renderer for equality with an arbitrary object.
419         *
420         * @param obj  the object (<code>null</code> not permitted).
421         *
422         * @return A boolean.
423         */
424        public boolean equals(Object obj) {
425            if (obj == this) {
426                return true;
427            }
428            if (!(obj instanceof XYAreaRenderer2)) {
429                return false;
430            }
431            XYAreaRenderer2 that = (XYAreaRenderer2) obj;
432            if (this.showOutline != that.showOutline) {
433                return false;
434            }
435            if (!ShapeUtilities.equal(this.legendArea, that.legendArea)) {
436                return false;
437            }
438            return super.equals(obj);
439        }
440    
441        /**
442         * Returns a clone of the renderer.
443         *
444         * @return A clone.
445         *
446         * @throws CloneNotSupportedException  if the renderer cannot be cloned.
447         */
448        public Object clone() throws CloneNotSupportedException {
449            XYAreaRenderer2 clone = (XYAreaRenderer2) super.clone();
450            clone.legendArea = ShapeUtilities.clone(this.legendArea);
451            return clone;
452        }
453    
454        /**
455         * Provides serialization support.
456         *
457         * @param stream  the input stream.
458         *
459         * @throws IOException  if there is an I/O error.
460         * @throws ClassNotFoundException  if there is a classpath problem.
461         */
462        private void readObject(ObjectInputStream stream)
463                throws IOException, ClassNotFoundException {
464            stream.defaultReadObject();
465            this.legendArea = SerialUtilities.readShape(stream);
466        }
467    
468        /**
469         * Provides serialization support.
470         *
471         * @param stream  the output stream.
472         *
473         * @throws IOException  if there is an I/O error.
474         */
475        private void writeObject(ObjectOutputStream stream) throws IOException {
476            stream.defaultWriteObject();
477            SerialUtilities.writeShape(this.legendArea, stream);
478        }
479    
480    }
481