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 * AreaRenderer.java
029 * -----------------
030 * (C) Copyright 2002-2014, 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 * 26-May-2009 : Support AreaRendererEndType.LEVEL (DG);
070 * 27-May-2009 : Fixed item label anchor for horizontal orientation (DG);
071 * 03-Jul-2013 : Use ParamChecks (DG);
072 * 
073 */
074
075package org.jfree.chart.renderer.category;
076
077import java.awt.Graphics2D;
078import java.awt.Paint;
079import java.awt.Shape;
080import java.awt.Stroke;
081import java.awt.geom.GeneralPath;
082import java.awt.geom.Rectangle2D;
083import java.io.Serializable;
084
085import org.jfree.chart.LegendItem;
086import org.jfree.chart.axis.CategoryAxis;
087import org.jfree.chart.axis.ValueAxis;
088import org.jfree.chart.entity.EntityCollection;
089import org.jfree.chart.event.RendererChangeEvent;
090import org.jfree.chart.plot.CategoryPlot;
091import org.jfree.chart.plot.PlotOrientation;
092import org.jfree.chart.renderer.AreaRendererEndType;
093import org.jfree.chart.util.ParamChecks;
094import org.jfree.data.category.CategoryDataset;
095import org.jfree.ui.RectangleEdge;
096import org.jfree.util.PublicCloneable;
097
098/**
099 * A category item renderer that draws area charts.  You can use this renderer
100 * with the {@link CategoryPlot} class.  The example shown here is generated
101 * by the <code>AreaChartDemo1.java</code> program included in the JFreeChart
102 * Demo Collection:
103 * <br><br>
104 * <img src="../../../../../images/AreaRendererSample.png"
105 * alt="AreaRendererSample.png">
106 */
107public class AreaRenderer extends AbstractCategoryItemRenderer
108        implements Cloneable, PublicCloneable, Serializable {
109
110    /** For serialization. */
111    private static final long serialVersionUID = -4231878281385812757L;
112
113    /** A flag that controls how the ends of the areas are drawn. */
114    private AreaRendererEndType endType;
115
116    /**
117     * Creates a new renderer.
118     */
119    public AreaRenderer() {
120        super();
121        this.endType = AreaRendererEndType.TAPER;
122        setBaseLegendShape(new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0));
123    }
124
125    /**
126     * Returns a token that controls how the renderer draws the end points.
127     * The default value is {@link AreaRendererEndType#TAPER}.
128     *
129     * @return The end type (never <code>null</code>).
130     *
131     * @see #setEndType
132     */
133    public AreaRendererEndType getEndType() {
134        return this.endType;
135    }
136
137    /**
138     * Sets a token that controls how the renderer draws the end points, and
139     * sends a {@link RendererChangeEvent} to all registered listeners.
140     *
141     * @param type  the end type (<code>null</code> not permitted).
142     *
143     * @see #getEndType()
144     */
145    public void setEndType(AreaRendererEndType type) {
146        ParamChecks.nullNotPermitted(type, "type");
147        this.endType = type;
148        fireChangeEvent();
149    }
150
151    /**
152     * Returns a legend item for a series.
153     *
154     * @param datasetIndex  the dataset index (zero-based).
155     * @param series  the series index (zero-based).
156     *
157     * @return The legend item.
158     */
159    @Override
160    public LegendItem getLegendItem(int datasetIndex, int series) {
161
162        // if there is no plot, there is no dataset to access...
163        CategoryPlot cp = getPlot();
164        if (cp == null) {
165            return null;
166        }
167
168        // check that a legend item needs to be displayed...
169        if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) {
170            return null;
171        }
172
173        CategoryDataset dataset = cp.getDataset(datasetIndex);
174        String label = getLegendItemLabelGenerator().generateLabel(dataset,
175                series);
176        String description = label;
177        String toolTipText = null;
178        if (getLegendItemToolTipGenerator() != null) {
179            toolTipText = getLegendItemToolTipGenerator().generateLabel(
180                    dataset, series);
181        }
182        String urlText = null;
183        if (getLegendItemURLGenerator() != null) {
184            urlText = getLegendItemURLGenerator().generateLabel(dataset,
185                    series);
186        }
187        Shape shape = lookupLegendShape(series);
188        Paint paint = lookupSeriesPaint(series);
189        Paint outlinePaint = lookupSeriesOutlinePaint(series);
190        Stroke outlineStroke = lookupSeriesOutlineStroke(series);
191
192        LegendItem result = new LegendItem(label, description, toolTipText,
193                urlText, shape, paint, outlineStroke, outlinePaint);
194        result.setLabelFont(lookupLegendTextFont(series));
195        Paint labelPaint = lookupLegendTextPaint(series);
196        if (labelPaint != null) {
197            result.setLabelPaint(labelPaint);
198        }
199        result.setDataset(dataset);
200        result.setDatasetIndex(datasetIndex);
201        result.setSeriesKey(dataset.getRowKey(series));
202        result.setSeriesIndex(series);
203        return result;
204
205    }
206
207    /**
208     * Draw a single data item.
209     *
210     * @param g2  the graphics device.
211     * @param state  the renderer state.
212     * @param dataArea  the data plot area.
213     * @param plot  the plot.
214     * @param domainAxis  the domain axis.
215     * @param rangeAxis  the range axis.
216     * @param dataset  the dataset.
217     * @param row  the row index (zero-based).
218     * @param column  the column index (zero-based).
219     * @param pass  the pass index.
220     */
221    @Override
222    public void drawItem(Graphics2D g2, CategoryItemRendererState state,
223            Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis,
224            ValueAxis rangeAxis, CategoryDataset dataset, int row, int column,
225            int pass) {
226
227        // do nothing if item is not visible or null
228        if (!getItemVisible(row, column)) {
229            return;
230        }
231        Number value = dataset.getValue(row, column);
232        if (value == null) {
233            return;
234        }
235        PlotOrientation orientation = plot.getOrientation();
236        RectangleEdge axisEdge = plot.getDomainAxisEdge();
237        int count = dataset.getColumnCount();
238        float x0 = (float) domainAxis.getCategoryStart(column, count, dataArea,
239                axisEdge);
240        float x1 = (float) domainAxis.getCategoryMiddle(column, count,
241                dataArea, axisEdge);
242        float x2 = (float) domainAxis.getCategoryEnd(column, count, dataArea,
243                axisEdge);
244
245        x0 = Math.round(x0);
246        x1 = Math.round(x1);
247        x2 = Math.round(x2);
248
249        if (this.endType == AreaRendererEndType.TRUNCATE) {
250            if (column == 0) {
251                x0 = x1;
252            }
253            else if (column == getColumnCount() - 1) {
254                x2 = x1;
255            }
256        }
257
258        double yy1 = value.doubleValue();
259
260        double yy0 = 0.0;
261        if (this.endType == AreaRendererEndType.LEVEL) {
262            yy0 = yy1;
263        }
264        if (column > 0) {
265            Number n0 = dataset.getValue(row, column - 1);
266            if (n0 != null) {
267                yy0 = (n0.doubleValue() + yy1) / 2.0;
268            }
269        }
270
271        double yy2 = 0.0;
272        if (column < dataset.getColumnCount() - 1) {
273            Number n2 = dataset.getValue(row, column + 1);
274            if (n2 != null) {
275                yy2 = (n2.doubleValue() + yy1) / 2.0;
276            }
277        }
278        else if (this.endType == AreaRendererEndType.LEVEL) {
279            yy2 = yy1;
280        }
281
282        RectangleEdge edge = plot.getRangeAxisEdge();
283        float y0 = (float) rangeAxis.valueToJava2D(yy0, dataArea, edge);
284        float y1 = (float) rangeAxis.valueToJava2D(yy1, dataArea, edge);
285        float y2 = (float) rangeAxis.valueToJava2D(yy2, dataArea, edge);
286        float yz = (float) rangeAxis.valueToJava2D(0.0, dataArea, edge);
287        double labelXX = x1;
288        double labelYY = y1;
289        g2.setPaint(getItemPaint(row, column));
290        g2.setStroke(getItemStroke(row, column));
291
292        GeneralPath area = new GeneralPath();
293
294        if (orientation == PlotOrientation.VERTICAL) {
295            area.moveTo(x0, yz);
296            area.lineTo(x0, y0);
297            area.lineTo(x1, y1);
298            area.lineTo(x2, y2);
299            area.lineTo(x2, yz);
300        }
301        else if (orientation == PlotOrientation.HORIZONTAL) {
302            area.moveTo(yz, x0);
303            area.lineTo(y0, x0);
304            area.lineTo(y1, x1);
305            area.lineTo(y2, x2);
306            area.lineTo(yz, x2);
307            double temp = labelXX;
308            labelXX = labelYY;
309            labelYY = temp;
310        }
311        area.closePath();
312
313        g2.setPaint(getItemPaint(row, column));
314        g2.fill(area);
315
316        // draw the item labels if there are any...
317        if (isItemLabelVisible(row, column)) {
318            drawItemLabel(g2, orientation, dataset, row, column, labelXX,
319                    labelYY, (value.doubleValue() < 0.0));
320        }
321
322        // submit the current data point as a crosshair candidate
323        int datasetIndex = plot.indexOf(dataset);
324        updateCrosshairValues(state.getCrosshairState(), 
325                dataset.getRowKey(row), dataset.getColumnKey(column), yy1,
326                datasetIndex, x1, y1, orientation);
327
328        // add an item entity, if this information is being collected
329        EntityCollection entities = state.getEntityCollection();
330        if (entities != null) {
331            addItemEntity(entities, dataset, row, column, area);
332        }
333
334    }
335
336    /**
337     * Tests this instance for equality with an arbitrary object.
338     *
339     * @param obj  the object to test (<code>null</code> permitted).
340     *
341     * @return A boolean.
342     */
343    @Override
344    public boolean equals(Object obj) {
345        if (obj == this) {
346            return true;
347        }
348        if (!(obj instanceof AreaRenderer)) {
349            return false;
350        }
351        AreaRenderer that = (AreaRenderer) obj;
352        if (!this.endType.equals(that.endType)) {
353            return false;
354        }
355        return super.equals(obj);
356    }
357
358    /**
359     * Returns an independent copy of the renderer.
360     *
361     * @return A clone.
362     *
363     * @throws CloneNotSupportedException  should not happen.
364     */
365    @Override
366    public Object clone() throws CloneNotSupportedException {
367        return super.clone();
368    }
369
370}