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