001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-present, by David Gilbert 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-present, by Hari and Contributors.
031 *
032 * Original Author:  Hari (ourhari@hotmail.com);
033 * Contributor(s):   David Gilbert;
034 *                   Richard Atkinson;
035 *                   Christian W. Zuckschwerdt;
036 *                   Martin Krauskopf;
037 *                   Ulrich Voigt (patch #312);
038 */
039
040package org.jfree.chart.renderer.xy;
041
042import java.awt.Graphics2D;
043import java.awt.Paint;
044import java.awt.Shape;
045import java.awt.Stroke;
046import java.awt.geom.Area;
047import java.awt.geom.GeneralPath;
048import java.awt.geom.Rectangle2D;
049import java.io.IOException;
050import java.io.ObjectInputStream;
051import java.io.ObjectOutputStream;
052
053import org.jfree.chart.LegendItem;
054import org.jfree.chart.axis.ValueAxis;
055import org.jfree.chart.entity.EntityCollection;
056import org.jfree.chart.entity.XYItemEntity;
057import org.jfree.chart.event.RendererChangeEvent;
058import org.jfree.chart.labels.XYSeriesLabelGenerator;
059import org.jfree.chart.labels.XYToolTipGenerator;
060import org.jfree.chart.plot.CrosshairState;
061import org.jfree.chart.plot.PlotOrientation;
062import org.jfree.chart.plot.PlotRenderingInfo;
063import org.jfree.chart.plot.XYPlot;
064import org.jfree.chart.urls.XYURLGenerator;
065import org.jfree.chart.util.Args;
066import org.jfree.chart.util.PublicCloneable;
067import org.jfree.chart.util.SerialUtils;
068import org.jfree.chart.util.ShapeUtils;
069import org.jfree.data.xy.XYDataset;
070
071/**
072 * Area item renderer for an {@link XYPlot}. The example shown here is
073 * generated by the {@code XYAreaRenderer2Demo1.java} program included in
074 * the JFreeChart demo collection:
075 * <br><br>
076 * <img src="doc-files/XYAreaRenderer2Sample.png"
077 * alt="XYAreaRenderer2Sample.png">
078 */
079public class XYAreaRenderer2 extends AbstractXYItemRenderer
080        implements XYItemRenderer, PublicCloneable {
081
082    /** For serialization. */
083    private static final long serialVersionUID = -7378069681579984133L;
084
085    /** A flag that controls whether or not the outline is shown. */
086    private boolean showOutline;
087
088    /**
089     * The shape used to represent an area in each legend item (this should
090     * never be {@code null}).
091     */
092    private transient Shape legendArea;
093
094    /**
095     * Constructs a new renderer.
096     */
097    public XYAreaRenderer2() {
098        this(null, null);
099    }
100
101    /**
102     * Constructs a new renderer.
103     *
104     * @param labelGenerator  the tool tip generator to use ({@code null} 
105     *     permitted).
106     * @param urlGenerator  the URL generator ({@code null} permitted).
107     */
108    public XYAreaRenderer2(XYToolTipGenerator labelGenerator,
109                           XYURLGenerator urlGenerator) {
110        super();
111        this.showOutline = false;
112        setDefaultToolTipGenerator(labelGenerator);
113        setURLGenerator(urlGenerator);
114        GeneralPath area = new GeneralPath();
115        area.moveTo(0.0f, -4.0f);
116        area.lineTo(3.0f, -2.0f);
117        area.lineTo(4.0f, 4.0f);
118        area.lineTo(-4.0f, 4.0f);
119        area.lineTo(-3.0f, -2.0f);
120        area.closePath();
121        this.legendArea = area;
122    }
123
124    /**
125     * Returns a flag that controls whether or not outlines of the areas are
126     * drawn.
127     *
128     * @return The flag.
129     *
130     * @see #setOutline(boolean)
131     */
132    public boolean isOutline() {
133        return this.showOutline;
134    }
135
136    /**
137     * Sets a flag that controls whether or not outlines of the areas are
138     * drawn, and sends a {@link RendererChangeEvent} to all registered
139     * listeners.
140     *
141     * @param show  the flag.
142     *
143     * @see #isOutline()
144     */
145    public void setOutline(boolean show) {
146        this.showOutline = show;
147        fireChangeEvent();
148    }
149
150    /**
151     * Returns the shape used to represent an area in the legend.
152     *
153     * @return The legend area (never {@code null}).
154     *
155     * @see #setLegendArea(Shape)
156     */
157    public Shape getLegendArea() {
158        return this.legendArea;
159    }
160
161    /**
162     * Sets the shape used as an area in each legend item and sends a
163     * {@link RendererChangeEvent} to all registered listeners.
164     *
165     * @param area  the area ({@code null} not permitted).
166     *
167     * @see #getLegendArea()
168     */
169    public void setLegendArea(Shape area) {
170        Args.nullNotPermitted(area, "area");
171        this.legendArea = area;
172        fireChangeEvent();
173    }
174
175    /**
176     * Returns a default legend item for the specified series.  Subclasses
177     * should override this method to generate customised items.
178     *
179     * @param datasetIndex  the dataset index (zero-based).
180     * @param series  the series index (zero-based).
181     *
182     * @return A legend item for the series.
183     */
184    @Override
185    public LegendItem getLegendItem(int datasetIndex, int series) {
186        LegendItem result = null;
187        XYPlot xyplot = getPlot();
188        if (xyplot != null) {
189            XYDataset dataset = xyplot.getDataset(datasetIndex);
190            if (dataset != null) {
191                XYSeriesLabelGenerator lg = getLegendItemLabelGenerator();
192                String label = lg.generateLabel(dataset, series);
193                String description = label;
194                String toolTipText = null;
195                if (getLegendItemToolTipGenerator() != null) {
196                    toolTipText = getLegendItemToolTipGenerator().generateLabel(
197                            dataset, series);
198                }
199                String urlText = null;
200                if (getLegendItemURLGenerator() != null) {
201                    urlText = getLegendItemURLGenerator().generateLabel(
202                            dataset, series);
203                }
204                Paint paint = lookupSeriesPaint(series);
205                result = new LegendItem(label, description, toolTipText,
206                        urlText, this.legendArea, paint);
207                result.setLabelFont(lookupLegendTextFont(series));
208                Paint labelPaint = lookupLegendTextPaint(series);
209                if (labelPaint != null) {
210                    result.setLabelPaint(labelPaint);
211                }
212                result.setDataset(dataset);
213                result.setDatasetIndex(datasetIndex);
214                result.setSeriesKey(dataset.getSeriesKey(series));
215                result.setSeriesIndex(series);
216            }
217        }
218        return result;
219    }
220
221    /**
222     * Draws the visual representation of a single data item.
223     *
224     * @param g2  the graphics device.
225     * @param state  the renderer state.
226     * @param dataArea  the area within which the data is being drawn.
227     * @param info  collects information about the drawing.
228     * @param plot  the plot (can be used to obtain standard color
229     *              information etc).
230     * @param domainAxis  the domain axis.
231     * @param rangeAxis  the range axis.
232     * @param dataset  the dataset.
233     * @param series  the series index (zero-based).
234     * @param item  the item index (zero-based).
235     * @param crosshairState  crosshair information for the plot
236     *                        ({@code null} permitted).
237     * @param pass  the pass index.
238     */
239    @Override
240    public void drawItem(Graphics2D g2, XYItemRendererState state,
241         Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
242         ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset,
243         int series, int item, CrosshairState crosshairState, int pass) {
244
245        if (!getItemVisible(series, item)) {
246            return;
247        }
248        // get the data point...
249        double x1 = dataset.getXValue(series, item);
250        double y1 = dataset.getYValue(series, item);
251        if (Double.isNaN(y1)) {
252            y1 = 0.0;
253        }
254
255        double transX1 = domainAxis.valueToJava2D(x1, dataArea,
256                plot.getDomainAxisEdge());
257        double transY1 = rangeAxis.valueToJava2D(y1, dataArea,
258                plot.getRangeAxisEdge());
259
260        // get the previous point and the next point so we can calculate a
261        // "hot spot" for the area (used by the chart entity)...
262        double x0 = dataset.getXValue(series, Math.max(item - 1, 0));
263        double y0 = dataset.getYValue(series, Math.max(item - 1, 0));
264        if (Double.isNaN(y0)) {
265            y0 = 0.0;
266        }
267        double transX0 = domainAxis.valueToJava2D(x0, dataArea,
268                plot.getDomainAxisEdge());
269        double transY0 = rangeAxis.valueToJava2D(y0, dataArea,
270                plot.getRangeAxisEdge());
271
272        int itemCount = dataset.getItemCount(series);
273        double x2 = dataset.getXValue(series, Math.min(item + 1,
274                itemCount - 1));
275        double y2 = dataset.getYValue(series, Math.min(item + 1,
276                itemCount - 1));
277        if (Double.isNaN(y2)) {
278            y2 = 0.0;
279        }
280        double transX2 = domainAxis.valueToJava2D(x2, dataArea,
281                plot.getDomainAxisEdge());
282        double transY2 = rangeAxis.valueToJava2D(y2, dataArea,
283                plot.getRangeAxisEdge());
284
285        double transZero = rangeAxis.valueToJava2D(0.0, dataArea,
286                plot.getRangeAxisEdge());
287        GeneralPath hotspot = new GeneralPath();
288        if (plot.getOrientation() == PlotOrientation.HORIZONTAL) {
289            moveTo(hotspot, transZero, ((transX0 + transX1) / 2.0));
290            lineTo(hotspot, ((transY0 + transY1) / 2.0),
291                            ((transX0 + transX1) / 2.0));
292            lineTo(hotspot, transY1, transX1);
293            lineTo(hotspot, ((transY1 + transY2) / 2.0),
294                            ((transX1 + transX2) / 2.0));
295            lineTo(hotspot, transZero, ((transX1 + transX2) / 2.0));
296        }
297        else {  // vertical orientation
298            moveTo(hotspot, ((transX0 + transX1) / 2.0), transZero);
299            lineTo(hotspot, ((transX0 + transX1) / 2.0),
300                            ((transY0 + transY1) / 2.0));
301            lineTo(hotspot, transX1, transY1);
302            lineTo(hotspot, ((transX1 + transX2) / 2.0),
303                            ((transY1 + transY2) / 2.0));
304            lineTo(hotspot, ((transX1 + transX2) / 2.0), transZero);
305        }
306        hotspot.closePath();
307
308        PlotOrientation orientation = plot.getOrientation();
309        Paint paint = getItemPaint(series, item);
310        Stroke stroke = getItemStroke(series, item);
311        g2.setPaint(paint);
312        g2.setStroke(stroke);
313
314        // Check if the item is the last item for the series.
315        // and number of items > 0.  We can't draw an area for a single point.
316        g2.fill(hotspot);
317
318        // draw an outline around the Area.
319        if (isOutline()) {
320            g2.setStroke(lookupSeriesOutlineStroke(series));
321            g2.setPaint(lookupSeriesOutlinePaint(series));
322            g2.draw(hotspot);
323        }
324        int datasetIndex = plot.indexOf(dataset);
325        updateCrosshairValues(crosshairState, x1, y1, datasetIndex,
326                transX1, transY1, orientation);
327
328        // collect entity and tool tip information...
329        if (state.getInfo() != null) {
330            EntityCollection entities = state.getEntityCollection();
331            if (entities != null) {
332                // limit the entity hotspot area to the data area
333                Area dataAreaHotspot = new Area(hotspot);
334                dataAreaHotspot.intersect(new Area(dataArea));
335                if (!dataAreaHotspot.isEmpty()) {
336                    String tip = null;
337                    XYToolTipGenerator generator = getToolTipGenerator(series,
338                         item);
339                    if (generator != null) {
340                        tip = generator.generateToolTip(dataset, series, item);
341                    }
342                    String url = null;
343                    if (getURLGenerator() != null) {
344                        url = getURLGenerator().generateURL(dataset, series, 
345                                item);
346                    }
347                    XYItemEntity entity = new XYItemEntity(dataAreaHotspot, 
348                            dataset, series, item, tip, url);
349                    entities.add(entity);
350                }
351            }
352        }
353
354    }
355
356    /**
357     * Tests this renderer for equality with an arbitrary object.
358     *
359     * @param obj  the object ({@code null} not permitted).
360     *
361     * @return A boolean.
362     */
363    @Override
364    public boolean equals(Object obj) {
365        if (obj == this) {
366            return true;
367        }
368        if (!(obj instanceof XYAreaRenderer2)) {
369            return false;
370        }
371        XYAreaRenderer2 that = (XYAreaRenderer2) obj;
372        if (this.showOutline != that.showOutline) {
373            return false;
374        }
375        if (!ShapeUtils.equal(this.legendArea, that.legendArea)) {
376            return false;
377        }
378        return super.equals(obj);
379    }
380
381    /**
382     * Returns a clone of the renderer.
383     *
384     * @return A clone.
385     *
386     * @throws CloneNotSupportedException  if the renderer cannot be cloned.
387     */
388    @Override
389    public Object clone() throws CloneNotSupportedException {
390        XYAreaRenderer2 clone = (XYAreaRenderer2) super.clone();
391        clone.legendArea = ShapeUtils.clone(this.legendArea);
392        return clone;
393    }
394
395    /**
396     * Provides serialization support.
397     *
398     * @param stream  the input stream.
399     *
400     * @throws IOException  if there is an I/O error.
401     * @throws ClassNotFoundException  if there is a classpath problem.
402     */
403    private void readObject(ObjectInputStream stream)
404            throws IOException, ClassNotFoundException {
405        stream.defaultReadObject();
406        this.legendArea = SerialUtils.readShape(stream);
407    }
408
409    /**
410     * Provides serialization support.
411     *
412     * @param stream  the output stream.
413     *
414     * @throws IOException  if there is an I/O error.
415     */
416    private void writeObject(ObjectOutputStream stream) throws IOException {
417        stream.defaultWriteObject();
418        SerialUtils.writeShape(this.legendArea, stream);
419    }
420
421}
422