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