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