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
080package org.jfree.chart.renderer.xy;
081
082
083import java.awt.Graphics2D;
084import java.awt.Paint;
085import java.awt.Polygon;
086import java.awt.Shape;
087import java.awt.Stroke;
088import java.awt.geom.GeneralPath;
089import java.awt.geom.Rectangle2D;
090import java.io.IOException;
091import java.io.ObjectInputStream;
092import java.io.ObjectOutputStream;
093import java.io.Serializable;
094
095import org.jfree.chart.LegendItem;
096import org.jfree.chart.axis.ValueAxis;
097import org.jfree.chart.entity.EntityCollection;
098import org.jfree.chart.entity.XYItemEntity;
099import org.jfree.chart.event.RendererChangeEvent;
100import org.jfree.chart.labels.XYSeriesLabelGenerator;
101import org.jfree.chart.labels.XYToolTipGenerator;
102import org.jfree.chart.plot.CrosshairState;
103import org.jfree.chart.plot.PlotOrientation;
104import org.jfree.chart.plot.PlotRenderingInfo;
105import org.jfree.chart.plot.XYPlot;
106import org.jfree.chart.urls.XYURLGenerator;
107import org.jfree.data.xy.XYDataset;
108import org.jfree.io.SerialUtilities;
109import org.jfree.util.PublicCloneable;
110import org.jfree.util.ShapeUtilities;
111
112/**
113 * Area item renderer for an {@link XYPlot}.  
114 */
115public 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        notifyListeners(new RendererChangeEvent(this));
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        notifyListeners(new RendererChangeEvent(this));
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