001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2007, 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-2007, 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     * $Id: XYAreaRenderer2.java,v 1.12.2.10 2007/05/18 10:28:31 mungady Exp $
038     *
039     * Changes:
040     * --------
041     * 03-Apr-2002 : Version 1, contributed by Hari.  This class is based on the 
042     *               StandardXYItemRenderer class (DG);
043     * 09-Apr-2002 : Removed the translated zero from the drawItem method - 
044     *               overridden the initialise() method to calculate it (DG);
045     * 30-May-2002 : Added tool tip generator to constructor to match super 
046     *               class (DG);
047     * 25-Jun-2002 : Removed unnecessary local variable (DG);
048     * 05-Aug-2002 : Small modification to drawItem method to support URLs for 
049     *               HTML image maps (RA);
050     * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
051     * 07-Nov-2002 : Renamed AreaXYItemRenderer --> XYAreaRenderer (DG);
052     * 25-Mar-2003 : Implemented Serializable (DG);
053     * 01-May-2003 : Modified drawItem() method signature (DG);
054     * 27-Jul-2003 : Made line and polygon properties protected rather than 
055     *               private (RA);
056     * 30-Jul-2003 : Modified entity constructor (CZ);
057     * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG);
058     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
059     * 07-Oct-2003 : Added renderer state (DG);
060     * 08-Dec-2003 : Modified hotspot for chart entity (DG);
061     * 10-Feb-2004 : Changed the drawItem() method to make cut-and-paste 
062     *               overriding easier.  Also moved state class into this 
063     *               class (DG);
064     * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState.  Renamed 
065     *               XYToolTipGenerator --> XYItemLabelGenerator (DG);
066     * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 
067     *               getYValue() (DG);
068     * 11-Nov-2004 : Now uses ShapeUtilities to translate shapes (DG);
069     * 19-Jan-2005 : Now accesses only primitives from the dataset (DG);
070     * 21-Mar-2005 : Override getLegendItem() (DG);
071     * 20-Apr-2005 : Use generators for legend tooltips and URLs (DG);
072     * ------------- JFREECHART 1.0.x ---------------------------------------------
073     * 30-Nov-2006 : Fixed equals() and clone() implementations (DG);
074     * 06-Feb-2007 : Fixed bug 1086307, crosshairs with multiple axes (DG);
075     * 20-Apr-2007 : Updated getLegendItem() and drawItem() for renderer 
076     *               change (DG); 
077     * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG);
078     * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
079     *
080     */
081    
082    package org.jfree.chart.renderer.xy;
083    
084    
085    import java.awt.Graphics2D;
086    import java.awt.Paint;
087    import java.awt.Polygon;
088    import java.awt.Shape;
089    import java.awt.Stroke;
090    import java.awt.geom.GeneralPath;
091    import java.awt.geom.Rectangle2D;
092    import java.io.IOException;
093    import java.io.ObjectInputStream;
094    import java.io.ObjectOutputStream;
095    import java.io.Serializable;
096    
097    import org.jfree.chart.LegendItem;
098    import org.jfree.chart.axis.ValueAxis;
099    import org.jfree.chart.entity.EntityCollection;
100    import org.jfree.chart.entity.XYItemEntity;
101    import org.jfree.chart.event.RendererChangeEvent;
102    import org.jfree.chart.labels.XYSeriesLabelGenerator;
103    import org.jfree.chart.labels.XYToolTipGenerator;
104    import org.jfree.chart.plot.CrosshairState;
105    import org.jfree.chart.plot.PlotOrientation;
106    import org.jfree.chart.plot.PlotRenderingInfo;
107    import org.jfree.chart.plot.XYPlot;
108    import org.jfree.chart.urls.XYURLGenerator;
109    import org.jfree.data.xy.XYDataset;
110    import org.jfree.io.SerialUtilities;
111    import org.jfree.util.PublicCloneable;
112    import org.jfree.util.ShapeUtilities;
113    
114    /**
115     * Area item renderer for an {@link XYPlot}.  
116     */
117    public class XYAreaRenderer2 extends AbstractXYItemRenderer 
118                                 implements XYItemRenderer, 
119                                            Cloneable,
120                                            PublicCloneable,
121                                            Serializable {
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            notifyListeners(new RendererChangeEvent(this));
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            notifyListeners(new RendererChangeEvent(this));
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.setDataset(dataset);
263                    result.setDatasetIndex(datasetIndex);
264                    result.setSeriesKey(dataset.getSeriesKey(series));
265                    result.setSeriesIndex(series);
266                }
267            }
268            return result;
269        }
270        
271        /**
272         * Draws the visual representation of a single data item.
273         *
274         * @param g2  the graphics device.
275         * @param state  the renderer state.
276         * @param dataArea  the area within which the data is being drawn.
277         * @param info  collects information about the drawing.
278         * @param plot  the plot (can be used to obtain standard color 
279         *              information etc).
280         * @param domainAxis  the domain axis.
281         * @param rangeAxis  the range axis.
282         * @param dataset  the dataset.
283         * @param series  the series index (zero-based).
284         * @param item  the item index (zero-based).
285         * @param crosshairState  crosshair information for the plot 
286         *                        (<code>null</code> permitted).
287         * @param pass  the pass index.
288         */
289        public void drawItem(Graphics2D g2,
290                             XYItemRendererState state,
291                             Rectangle2D dataArea,
292                             PlotRenderingInfo info,
293                             XYPlot plot,
294                             ValueAxis domainAxis,
295                             ValueAxis rangeAxis,
296                             XYDataset dataset,
297                             int series,
298                             int item,
299                             CrosshairState crosshairState,
300                             int pass) {
301            
302            if (!getItemVisible(series, item)) {
303                return;   
304            }
305            // get the data point...
306            double x1 = dataset.getXValue(series, item);
307            double y1 = dataset.getYValue(series, item);
308            if (Double.isNaN(y1)) {
309                y1 = 0.0;
310            }
311            
312            double transX1 = domainAxis.valueToJava2D(x1, dataArea, 
313                    plot.getDomainAxisEdge());
314            double transY1 = rangeAxis.valueToJava2D(y1, dataArea, 
315                    plot.getRangeAxisEdge());
316            
317            // get the previous point and the next point so we can calculate a 
318            // "hot spot" for the area (used by the chart entity)...
319            double x0 = dataset.getXValue(series, Math.max(item - 1, 0));
320            double y0 = dataset.getYValue(series, Math.max(item - 1, 0));
321            if (Double.isNaN(y0)) {
322                y0 = 0.0;
323            }
324            double transX0 = domainAxis.valueToJava2D(x0, dataArea, 
325                    plot.getDomainAxisEdge());
326            double transY0 = rangeAxis.valueToJava2D(y0, dataArea, 
327                    plot.getRangeAxisEdge());
328            
329            int itemCount = dataset.getItemCount(series);
330            double x2 = dataset.getXValue(series, Math.min(item + 1, 
331                    itemCount - 1));
332            double y2 = dataset.getYValue(series, Math.min(item + 1, 
333                    itemCount - 1));
334            if (Double.isNaN(y2)) {
335                y2 = 0.0;
336            }
337            double transX2 = domainAxis.valueToJava2D(x2, dataArea, 
338                    plot.getDomainAxisEdge());
339            double transY2 = rangeAxis.valueToJava2D(y2, dataArea, 
340                    plot.getRangeAxisEdge());
341            
342            double transZero = rangeAxis.valueToJava2D(0.0, dataArea, 
343                    plot.getRangeAxisEdge());
344            Polygon hotspot = null;
345            if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
346                hotspot = new Polygon();
347                hotspot.addPoint((int) transZero, 
348                        (int) ((transX0 + transX1) / 2.0));
349                hotspot.addPoint((int) ((transY0 + transY1) / 2.0), 
350                        (int) ((transX0 + transX1) / 2.0));
351                hotspot.addPoint((int) transY1, (int) transX1);
352                hotspot.addPoint((int) ((transY1 + transY2) / 2.0), 
353                        (int) ((transX1 + transX2) / 2.0));
354                hotspot.addPoint((int) transZero, 
355                        (int) ((transX1 + transX2) / 2.0));
356            }
357            else {  // vertical orientation
358                hotspot = new Polygon();
359                hotspot.addPoint((int) ((transX0 + transX1) / 2.0), 
360                        (int) transZero);
361                hotspot.addPoint((int) ((transX0 + transX1) / 2.0), 
362                        (int) ((transY0 + transY1) / 2.0));
363                hotspot.addPoint((int) transX1, (int) transY1);
364                hotspot.addPoint((int) ((transX1 + transX2) / 2.0), 
365                        (int) ((transY1 + transY2) / 2.0));
366                hotspot.addPoint((int) ((transX1 + transX2) / 2.0), 
367                        (int) transZero);
368            }
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            if (getPlotLines()) {
377                if (item > 0) {
378                    if (plot.getOrientation() == PlotOrientation.VERTICAL) {
379                        state.workingLine.setLine(transX0, transY0, transX1, 
380                                transY1);
381                    }
382                    else if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
383                        state.workingLine.setLine(transY0, transX0, transY1, 
384                                transX1);
385                    }
386                    g2.draw(state.workingLine);
387                }
388            }
389    
390            // Check if the item is the last item for the series.
391            // and number of items > 0.  We can't draw an area for a single point.
392            g2.fill(hotspot);
393    
394            // draw an outline around the Area.
395            if (isOutline()) {
396                g2.setStroke(lookupSeriesOutlineStroke(series));
397                g2.setPaint(lookupSeriesOutlinePaint(series));
398                g2.draw(hotspot);
399            }
400            int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
401            int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
402            updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex, 
403                    rangeAxisIndex, transX1, transY1, orientation);
404            
405            // collect entity and tool tip information...
406            if (state.getInfo() != null) {
407                EntityCollection entities = state.getEntityCollection();
408                if (entities != null && hotspot != null) {
409                    String tip = null;
410                    XYToolTipGenerator generator = getToolTipGenerator(
411                        series, item
412                    );
413                    if (generator != null) {
414                        tip = generator.generateToolTip(dataset, series, item);
415                    }
416                    String url = null;
417                    if (getURLGenerator() != null) {
418                        url = getURLGenerator().generateURL(dataset, series, item);
419                    }
420                    XYItemEntity entity = new XYItemEntity(hotspot, dataset, 
421                            series, item, tip, url);
422                    entities.add(entity);
423                }
424            }
425    
426        }
427    
428        /**
429         * Tests this renderer for equality with an arbitrary object.
430         * 
431         * @param obj  the object (<code>null</code> not permitted).
432         * 
433         * @return A boolean.
434         */
435        public boolean equals(Object obj) {
436            if (obj == this) {    
437                return true;
438            }
439            if (!(obj instanceof XYAreaRenderer2)) {
440                return false;
441            }
442            XYAreaRenderer2 that = (XYAreaRenderer2) obj;
443            if (this.showOutline != that.showOutline) {
444                return false;
445            }
446            if (!ShapeUtilities.equal(this.legendArea, that.legendArea)) {
447                return false;
448            }
449            return super.equals(obj);
450        }
451        
452        /**
453         * Returns a clone of the renderer.
454         * 
455         * @return A clone.
456         * 
457         * @throws CloneNotSupportedException  if the renderer cannot be cloned.
458         */
459        public Object clone() throws CloneNotSupportedException {
460            XYAreaRenderer2 clone = (XYAreaRenderer2) super.clone();
461            clone.legendArea = ShapeUtilities.clone(this.legendArea);
462            return clone;
463        }
464        
465        /**
466         * Provides serialization support.
467         *
468         * @param stream  the input stream.
469         *
470         * @throws IOException  if there is an I/O error.
471         * @throws ClassNotFoundException  if there is a classpath problem.
472         */
473        private void readObject(ObjectInputStream stream) 
474                throws IOException, ClassNotFoundException {
475            stream.defaultReadObject();
476            this.legendArea = SerialUtilities.readShape(stream);
477        }
478        
479        /**
480         * Provides serialization support.
481         *
482         * @param stream  the output stream.
483         *
484         * @throws IOException  if there is an I/O error.
485         */
486        private void writeObject(ObjectOutputStream stream) throws IOException {
487            stream.defaultWriteObject();
488            SerialUtilities.writeShape(this.legendArea, stream);
489        }
490    
491    }
492