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