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 * AreaRenderer.java
029 * -----------------
030 * (C) Copyright 2002-2008, by Jon Iles and Contributors.
031 *
032 * Original Author:  Jon Iles;
033 * Contributor(s):   David Gilbert (for Object Refinery Limited);
034 *                   Christian W. Zuckschwerdt;
035 *
036 * Changes:
037 * --------
038 * 21-May-2002 : Version 1, contributed by John Iles (DG);
039 * 29-May-2002 : Now extends AbstractCategoryItemRenderer (DG);
040 * 11-Jun-2002 : Updated Javadoc comments (DG);
041 * 25-Jun-2002 : Removed unnecessary imports (DG);
042 * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
043 * 10-Oct-2002 : Added constructors and basic entity support (DG);
044 * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and
045 *               CategoryToolTipGenerator interface (DG);
046 * 05-Nov-2002 : Replaced references to CategoryDataset with TableDataset (DG);
047 * 06-Nov-2002 : Renamed drawCategoryItem() --> drawItem() and now using axis
048 *               for category spacing.  Renamed AreaCategoryItemRenderer
049 *               --> AreaRenderer (DG);
050 * 17-Jan-2003 : Moved plot classes into a separate package (DG);
051 * 25-Mar-2003 : Implemented Serializable (DG);
052 * 10-Apr-2003 : Changed CategoryDataset to KeyedValues2DDataset in
053 *               drawItem() method (DG);
054 * 12-May-2003 : Modified to take into account the plot orientation (DG);
055 * 30-Jul-2003 : Modified entity constructor (CZ);
056 * 13-Aug-2003 : Implemented Cloneable (DG);
057 * 07-Oct-2003 : Added renderer state (DG);
058 * 05-Nov-2004 : Modified drawItem() signature (DG);
059 * 20-Apr-2005 : Apply tooltips and URLs to legend items (DG);
060 * 09-Jun-2005 : Use addItemEntity() method from superclass (DG);
061 * ------------- JFREECHART 1.0.x ---------------------------------------------
062 * 11-Oct-2006 : Fixed bug in equals() method (DG);
063 * 30-Nov-2006 : Added checks for series visibility (DG);
064 * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
065 * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG);
066 * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
067 * 17-Jun-2008 : Apply legend shape, font and paint attributes (DG);
068 * 26-Jun-2008 : Added crosshair support (DG);
069 *
070 */
071
072package org.jfree.chart.renderer.category;
073
074import java.awt.Graphics2D;
075import java.awt.Paint;
076import java.awt.Shape;
077import java.awt.Stroke;
078import java.awt.geom.GeneralPath;
079import java.awt.geom.Rectangle2D;
080import java.io.Serializable;
081
082import org.jfree.chart.LegendItem;
083import org.jfree.chart.axis.CategoryAxis;
084import org.jfree.chart.axis.ValueAxis;
085import org.jfree.chart.entity.EntityCollection;
086import org.jfree.chart.event.RendererChangeEvent;
087import org.jfree.chart.plot.CategoryPlot;
088import org.jfree.chart.plot.PlotOrientation;
089import org.jfree.chart.renderer.AreaRendererEndType;
090import org.jfree.data.category.CategoryDataset;
091import org.jfree.ui.RectangleEdge;
092import org.jfree.util.PublicCloneable;
093
094/**
095 * A category item renderer that draws area charts.  You can use this renderer
096 * with the {@link CategoryPlot} class.  The example shown here is generated
097 * by the <code>AreaChartDemo1.java</code> program included in the JFreeChart
098 * Demo Collection:
099 * <br><br>
100 * <img src="../../../../../images/AreaRendererSample.png"
101 * alt="AreaRendererSample.png" />
102 */
103public class AreaRenderer extends AbstractCategoryItemRenderer
104        implements Cloneable, PublicCloneable, Serializable {
105
106    /** For serialization. */
107    private static final long serialVersionUID = -4231878281385812757L;
108
109    /** A flag that controls how the ends of the areas are drawn. */
110    private AreaRendererEndType endType;
111
112    /**
113     * Creates a new renderer.
114     */
115    public AreaRenderer() {
116        super();
117        this.endType = AreaRendererEndType.TAPER;
118        setBaseLegendShape(new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0));
119    }
120
121    /**
122     * Returns a token that controls how the renderer draws the end points.
123     * The default value is {@link AreaRendererEndType#TAPER}.
124     *
125     * @return The end type (never <code>null</code>).
126     *
127     * @see #setEndType
128     */
129    public AreaRendererEndType getEndType() {
130        return this.endType;
131    }
132
133    /**
134     * Sets a token that controls how the renderer draws the end points, and
135     * sends a {@link RendererChangeEvent} to all registered listeners.
136     *
137     * @param type  the end type (<code>null</code> not permitted).
138     *
139     * @see #getEndType()
140     */
141    public void setEndType(AreaRendererEndType type) {
142        if (type == null) {
143            throw new IllegalArgumentException("Null 'type' argument.");
144        }
145        this.endType = type;
146        fireChangeEvent();
147    }
148
149    /**
150     * Returns a legend item for a series.
151     *
152     * @param datasetIndex  the dataset index (zero-based).
153     * @param series  the series index (zero-based).
154     *
155     * @return The legend item.
156     */
157    public LegendItem getLegendItem(int datasetIndex, int series) {
158
159        // if there is no plot, there is no dataset to access...
160        CategoryPlot cp = getPlot();
161        if (cp == null) {
162            return null;
163        }
164
165        // check that a legend item needs to be displayed...
166        if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) {
167            return null;
168        }
169
170        CategoryDataset dataset = cp.getDataset(datasetIndex);
171        String label = getLegendItemLabelGenerator().generateLabel(dataset,
172                series);
173        String description = label;
174        String toolTipText = null;
175        if (getLegendItemToolTipGenerator() != null) {
176            toolTipText = getLegendItemToolTipGenerator().generateLabel(
177                    dataset, series);
178        }
179        String urlText = null;
180        if (getLegendItemURLGenerator() != null) {
181            urlText = getLegendItemURLGenerator().generateLabel(dataset,
182                    series);
183        }
184        Shape shape = lookupLegendShape(series);
185        Paint paint = lookupSeriesPaint(series);
186        Paint outlinePaint = lookupSeriesOutlinePaint(series);
187        Stroke outlineStroke = lookupSeriesOutlineStroke(series);
188
189        LegendItem result = new LegendItem(label, description, toolTipText,
190                urlText, shape, paint, outlineStroke, outlinePaint);
191        result.setLabelFont(lookupLegendTextFont(series));
192        Paint labelPaint = lookupLegendTextPaint(series);
193        if (labelPaint != null) {
194            result.setLabelPaint(labelPaint);
195        }
196        result.setDataset(dataset);
197        result.setDatasetIndex(datasetIndex);
198        result.setSeriesKey(dataset.getRowKey(series));
199        result.setSeriesIndex(series);
200        return result;
201
202    }
203
204    /**
205     * Draw a single data item.
206     *
207     * @param g2  the graphics device.
208     * @param state  the renderer state.
209     * @param dataArea  the data plot area.
210     * @param plot  the plot.
211     * @param domainAxis  the domain axis.
212     * @param rangeAxis  the range axis.
213     * @param dataset  the dataset.
214     * @param row  the row index (zero-based).
215     * @param column  the column index (zero-based).
216     * @param pass  the pass index.
217     */
218    public void drawItem(Graphics2D g2,
219                         CategoryItemRendererState state,
220                         Rectangle2D dataArea,
221                         CategoryPlot plot,
222                         CategoryAxis domainAxis,
223                         ValueAxis rangeAxis,
224                         CategoryDataset dataset,
225                         int row,
226                         int column,
227                         int pass) {
228
229        // do nothing if item is not visible
230        if (!getItemVisible(row, column)) {
231            return;
232        }
233
234        // plot non-null values only...
235        Number value = dataset.getValue(row, column);
236        if (value != null) {
237            PlotOrientation orientation = plot.getOrientation();
238            RectangleEdge axisEdge = plot.getDomainAxisEdge();
239            int count = dataset.getColumnCount();
240            float x0 = (float) domainAxis.getCategoryStart(column, count,
241                    dataArea, axisEdge);
242            float x1 = (float) domainAxis.getCategoryMiddle(column, count,
243                    dataArea, axisEdge);
244            float x2 = (float) domainAxis.getCategoryEnd(column, count,
245                    dataArea, axisEdge);
246
247            x0 = Math.round(x0);
248            x1 = Math.round(x1);
249            x2 = Math.round(x2);
250
251            if (this.endType == AreaRendererEndType.TRUNCATE) {
252                if (column == 0) {
253                    x0 = x1;
254                }
255                else if (column == getColumnCount() - 1) {
256                    x2 = x1;
257                }
258            }
259
260            double yy1 = value.doubleValue();
261
262            double yy0 = 0.0;
263            if (column > 0) {
264                Number n0 = dataset.getValue(row, column - 1);
265                if (n0 != null) {
266                    yy0 = (n0.doubleValue() + yy1) / 2.0;
267                }
268            }
269
270            double yy2 = 0.0;
271            if (column < dataset.getColumnCount() - 1) {
272                Number n2 = dataset.getValue(row, column + 1);
273                if (n2 != null) {
274                    yy2 = (n2.doubleValue() + yy1) / 2.0;
275                }
276            }
277
278            RectangleEdge edge = plot.getRangeAxisEdge();
279            float y0 = (float) rangeAxis.valueToJava2D(yy0, dataArea, edge);
280            float y1 = (float) rangeAxis.valueToJava2D(yy1, dataArea, edge);
281            float y2 = (float) rangeAxis.valueToJava2D(yy2, dataArea, edge);
282            float yz = (float) rangeAxis.valueToJava2D(0.0, dataArea, edge);
283
284            g2.setPaint(getItemPaint(row, column));
285            g2.setStroke(getItemStroke(row, column));
286
287            GeneralPath area = new GeneralPath();
288
289            if (orientation == PlotOrientation.VERTICAL) {
290                area.moveTo(x0, yz);
291                area.lineTo(x0, y0);
292                area.lineTo(x1, y1);
293                area.lineTo(x2, y2);
294                area.lineTo(x2, yz);
295            }
296            else if (orientation == PlotOrientation.HORIZONTAL) {
297                area.moveTo(yz, x0);
298                area.lineTo(y0, x0);
299                area.lineTo(y1, x1);
300                area.lineTo(y2, x2);
301                area.lineTo(yz, x2);
302            }
303            area.closePath();
304
305            g2.setPaint(getItemPaint(row, column));
306            g2.fill(area);
307
308            // draw the item labels if there are any...
309            if (isItemLabelVisible(row, column)) {
310                drawItemLabel(g2, orientation, dataset, row, column, x1, y1,
311                        (value.doubleValue() < 0.0));
312            }
313
314            // submit the current data point as a crosshair candidate
315            int datasetIndex = plot.indexOf(dataset);
316            updateCrosshairValues(state.getCrosshairState(),
317                    dataset.getRowKey(row), dataset.getColumnKey(column),
318                    yy1, datasetIndex, x1, y1, orientation);
319
320            // add an item entity, if this information is being collected
321            EntityCollection entities = state.getEntityCollection();
322            if (entities != null) {
323                addItemEntity(entities, dataset, row, column, area);
324            }
325        }
326
327    }
328
329    /**
330     * Tests this instance for equality with an arbitrary object.
331     *
332     * @param obj  the object to test (<code>null</code> permitted).
333     *
334     * @return A boolean.
335     */
336    public boolean equals(Object obj) {
337        if (obj == this) {
338            return true;
339        }
340        if (!(obj instanceof AreaRenderer)) {
341            return false;
342        }
343        AreaRenderer that = (AreaRenderer) obj;
344        if (!this.endType.equals(that.endType)) {
345            return false;
346        }
347        return super.equals(obj);
348    }
349
350    /**
351     * Returns an independent copy of the renderer.
352     *
353     * @return A clone.
354     *
355     * @throws CloneNotSupportedException  should not happen.
356     */
357    public Object clone() throws CloneNotSupportedException {
358        return super.clone();
359    }
360
361}