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