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