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     * StatisticalLineAndShapeRenderer.java
029     * ------------------------------------
030     * (C) Copyright 2005-2008, by Object Refinery Limited and Contributors.
031     *
032     * Original Author:  Mofeed Shahin;
033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
034     *
035     * Changes
036     * -------
037     * 01-Feb-2005 : Version 1, contributed by Mofeed Shahin (DG);
038     * 16-Jun-2005 : Added errorIndicatorPaint to be consistent with
039     *               StatisticalBarRenderer (DG);
040     * ------------- JFREECHART 1.0.x ---------------------------------------------
041     * 11-Apr-2006 : Fixed bug 1468794, error bars drawn incorrectly when rendering
042     *               plots with horizontal orientation (DG);
043     * 25-Sep-2006 : Fixed bug 1562759, constructor ignoring arguments (DG);
044     * 01-Jun-2007 : Return early from drawItem() method if item is not
045     *               visible (DG);
046     * 14-Jun-2007 : If the dataset is not a StatisticalCategoryDataset, revert
047     *               to the drawing behaviour of LineAndShapeRenderer (DG);
048     * 27-Sep-2007 : Added offset option to match new option in
049     *               LineAndShapeRenderer (DG);
050     *
051     */
052    
053    package org.jfree.chart.renderer.category;
054    
055    import java.awt.Graphics2D;
056    import java.awt.Paint;
057    import java.awt.Shape;
058    import java.awt.geom.Line2D;
059    import java.awt.geom.Rectangle2D;
060    import java.io.IOException;
061    import java.io.ObjectInputStream;
062    import java.io.ObjectOutputStream;
063    import java.io.Serializable;
064    
065    import org.jfree.chart.axis.CategoryAxis;
066    import org.jfree.chart.axis.ValueAxis;
067    import org.jfree.chart.entity.EntityCollection;
068    import org.jfree.chart.event.RendererChangeEvent;
069    import org.jfree.chart.plot.CategoryPlot;
070    import org.jfree.chart.plot.PlotOrientation;
071    import org.jfree.data.category.CategoryDataset;
072    import org.jfree.data.statistics.StatisticalCategoryDataset;
073    import org.jfree.io.SerialUtilities;
074    import org.jfree.ui.RectangleEdge;
075    import org.jfree.util.PaintUtilities;
076    import org.jfree.util.PublicCloneable;
077    import org.jfree.util.ShapeUtilities;
078    
079    /**
080     * A renderer that draws shapes for each data item, and lines between data
081     * items.  Each point has a mean value and a standard deviation line. For use
082     * with the {@link CategoryPlot} class.
083     */
084    public class StatisticalLineAndShapeRenderer extends LineAndShapeRenderer
085            implements Cloneable, PublicCloneable, Serializable {
086    
087        /** For serialization. */
088        private static final long serialVersionUID = -3557517173697777579L;
089    
090        /** The paint used to show the error indicator. */
091        private transient Paint errorIndicatorPaint;
092    
093        /**
094         * Constructs a default renderer (draws shapes and lines).
095         */
096        public StatisticalLineAndShapeRenderer() {
097            this(true, true);
098        }
099    
100        /**
101         * Constructs a new renderer.
102         *
103         * @param linesVisible  draw lines?
104         * @param shapesVisible  draw shapes?
105         */
106        public StatisticalLineAndShapeRenderer(boolean linesVisible,
107                                               boolean shapesVisible) {
108            super(linesVisible, shapesVisible);
109            this.errorIndicatorPaint = null;
110        }
111    
112        /**
113         * Returns the paint used for the error indicators.
114         *
115         * @return The paint used for the error indicators (possibly
116         *         <code>null</code>).
117         *
118         * @see #setErrorIndicatorPaint(Paint)
119         */
120        public Paint getErrorIndicatorPaint() {
121            return this.errorIndicatorPaint;
122        }
123    
124        /**
125         * Sets the paint used for the error indicators (if <code>null</code>,
126         * the item outline paint is used instead) and sends a
127         * {@link RendererChangeEvent} to all registered listeners.
128         *
129         * @param paint  the paint (<code>null</code> permitted).
130         *
131         * @see #getErrorIndicatorPaint()
132         */
133        public void setErrorIndicatorPaint(Paint paint) {
134            this.errorIndicatorPaint = paint;
135            fireChangeEvent();
136        }
137    
138        /**
139         * Draw a single data item.
140         *
141         * @param g2  the graphics device.
142         * @param state  the renderer state.
143         * @param dataArea  the area in which the data is drawn.
144         * @param plot  the plot.
145         * @param domainAxis  the domain axis.
146         * @param rangeAxis  the range axis.
147         * @param dataset  the dataset (a {@link StatisticalCategoryDataset} is
148         *                 required).
149         * @param row  the row index (zero-based).
150         * @param column  the column index (zero-based).
151         * @param pass  the pass.
152         */
153        public void drawItem(Graphics2D g2,
154                             CategoryItemRendererState state,
155                             Rectangle2D dataArea,
156                             CategoryPlot plot,
157                             CategoryAxis domainAxis,
158                             ValueAxis rangeAxis,
159                             CategoryDataset dataset,
160                             int row,
161                             int column,
162                             int pass) {
163    
164            // do nothing if item is not visible
165            if (!getItemVisible(row, column)) {
166                return;
167            }
168    
169            // nothing is drawn for null...
170            Number v = dataset.getValue(row, column);
171            if (v == null) {
172                return;
173            }
174    
175            // if the dataset is not a StatisticalCategoryDataset then just revert
176            // to the superclass (LineAndShapeRenderer) behaviour...
177            if (!(dataset instanceof StatisticalCategoryDataset)) {
178                super.drawItem(g2, state, dataArea, plot, domainAxis, rangeAxis,
179                        dataset, row, column, pass);
180                return;
181            }
182    
183            StatisticalCategoryDataset statData
184                    = (StatisticalCategoryDataset) dataset;
185    
186            Number meanValue = statData.getMeanValue(row, column);
187    
188            PlotOrientation orientation = plot.getOrientation();
189    
190            // current data point...
191            double x1;
192            if (getUseSeriesOffset()) {
193                x1 = domainAxis.getCategorySeriesMiddle(dataset.getColumnKey(
194                        column), dataset.getRowKey(row), dataset, getItemMargin(),
195                        dataArea, plot.getDomainAxisEdge());
196            }
197            else {
198                x1 = domainAxis.getCategoryMiddle(column, getColumnCount(),
199                        dataArea, plot.getDomainAxisEdge());
200            }
201    
202            double y1 = rangeAxis.valueToJava2D(meanValue.doubleValue(), dataArea,
203                    plot.getRangeAxisEdge());
204    
205            Shape shape = getItemShape(row, column);
206            if (orientation == PlotOrientation.HORIZONTAL) {
207                shape = ShapeUtilities.createTranslatedShape(shape, y1, x1);
208            }
209            else if (orientation == PlotOrientation.VERTICAL) {
210                shape = ShapeUtilities.createTranslatedShape(shape, x1, y1);
211            }
212            if (getItemShapeVisible(row, column)) {
213    
214                if (getItemShapeFilled(row, column)) {
215                    g2.setPaint(getItemPaint(row, column));
216                    g2.fill(shape);
217                }
218                else {
219                    if (getUseOutlinePaint()) {
220                        g2.setPaint(getItemOutlinePaint(row, column));
221                    }
222                    else {
223                        g2.setPaint(getItemPaint(row, column));
224                    }
225                    g2.setStroke(getItemOutlineStroke(row, column));
226                    g2.draw(shape);
227                }
228            }
229    
230            if (getItemLineVisible(row, column)) {
231                if (column != 0) {
232    
233                    Number previousValue = statData.getValue(row, column - 1);
234                    if (previousValue != null) {
235    
236                        // previous data point...
237                        double previous = previousValue.doubleValue();
238                        double x0;
239                        if (getUseSeriesOffset()) {
240                            x0 = domainAxis.getCategorySeriesMiddle(
241                                    dataset.getColumnKey(column - 1),
242                                    dataset.getRowKey(row), dataset,
243                                    getItemMargin(), dataArea,
244                                    plot.getDomainAxisEdge());
245                        }
246                        else {
247                            x0 = domainAxis.getCategoryMiddle(column - 1,
248                                    getColumnCount(), dataArea,
249                                    plot.getDomainAxisEdge());
250                        }
251                        double y0 = rangeAxis.valueToJava2D(previous, dataArea,
252                                plot.getRangeAxisEdge());
253    
254                        Line2D line = null;
255                        if (orientation == PlotOrientation.HORIZONTAL) {
256                            line = new Line2D.Double(y0, x0, y1, x1);
257                        }
258                        else if (orientation == PlotOrientation.VERTICAL) {
259                            line = new Line2D.Double(x0, y0, x1, y1);
260                        }
261                        g2.setPaint(getItemPaint(row, column));
262                        g2.setStroke(getItemStroke(row, column));
263                        g2.draw(line);
264                    }
265                }
266            }
267    
268            RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
269            g2.setPaint(getItemPaint(row, column));
270    
271            //standard deviation lines
272            double valueDelta = statData.getStdDevValue(row, column).doubleValue();
273    
274            double highVal, lowVal;
275            if ((meanValue.doubleValue() + valueDelta)
276                    > rangeAxis.getRange().getUpperBound()) {
277                highVal = rangeAxis.valueToJava2D(
278                        rangeAxis.getRange().getUpperBound(), dataArea,
279                        yAxisLocation);
280            }
281            else {
282                highVal = rangeAxis.valueToJava2D(meanValue.doubleValue()
283                        + valueDelta, dataArea, yAxisLocation);
284            }
285    
286            if ((meanValue.doubleValue() + valueDelta)
287                    < rangeAxis.getRange().getLowerBound()) {
288                lowVal = rangeAxis.valueToJava2D(
289                        rangeAxis.getRange().getLowerBound(), dataArea,
290                        yAxisLocation);
291            }
292            else {
293                lowVal = rangeAxis.valueToJava2D(meanValue.doubleValue()
294                        - valueDelta, dataArea, yAxisLocation);
295            }
296    
297            if (this.errorIndicatorPaint != null) {
298                g2.setPaint(this.errorIndicatorPaint);
299            }
300            else {
301                g2.setPaint(getItemPaint(row, column));
302            }
303            Line2D line = new Line2D.Double();
304            if (orientation == PlotOrientation.HORIZONTAL) {
305                line.setLine(lowVal, x1, highVal, x1);
306                g2.draw(line);
307                line.setLine(lowVal, x1 - 5.0d, lowVal, x1 + 5.0d);
308                g2.draw(line);
309                line.setLine(highVal, x1 - 5.0d, highVal, x1 + 5.0d);
310                g2.draw(line);
311            }
312            else {  // PlotOrientation.VERTICAL
313                line.setLine(x1, lowVal, x1, highVal);
314                g2.draw(line);
315                line.setLine(x1 - 5.0d, highVal, x1 + 5.0d, highVal);
316                g2.draw(line);
317                line.setLine(x1 - 5.0d, lowVal, x1 + 5.0d, lowVal);
318                g2.draw(line);
319            }
320    
321            // draw the item label if there is one...
322            if (isItemLabelVisible(row, column)) {
323                if (orientation == PlotOrientation.HORIZONTAL) {
324                    drawItemLabel(g2, orientation, dataset, row, column,
325                            y1, x1, (meanValue.doubleValue() < 0.0));
326                }
327                else if (orientation == PlotOrientation.VERTICAL) {
328                    drawItemLabel(g2, orientation, dataset, row, column,
329                            x1, y1, (meanValue.doubleValue() < 0.0));
330                }
331            }
332    
333            // add an item entity, if this information is being collected
334            EntityCollection entities = state.getEntityCollection();
335            if (entities != null && shape != null) {
336                addItemEntity(entities, dataset, row, column, shape);
337            }
338    
339        }
340    
341        /**
342         * Tests this renderer for equality with an arbitrary object.
343         *
344         * @param obj  the object (<code>null</code> permitted).
345         *
346         * @return A boolean.
347         */
348        public boolean equals(Object obj) {
349            if (obj == this) {
350                return true;
351            }
352            if (!(obj instanceof StatisticalLineAndShapeRenderer)) {
353                return false;
354            }
355            StatisticalLineAndShapeRenderer that
356                    = (StatisticalLineAndShapeRenderer) obj;
357            if (!PaintUtilities.equal(this.errorIndicatorPaint,
358                    that.errorIndicatorPaint)) {
359                return false;
360            }
361            return super.equals(obj);
362        }
363    
364        /**
365         * Provides serialization support.
366         *
367         * @param stream  the output stream.
368         *
369         * @throws IOException  if there is an I/O error.
370         */
371        private void writeObject(ObjectOutputStream stream) throws IOException {
372            stream.defaultWriteObject();
373            SerialUtilities.writePaint(this.errorIndicatorPaint, stream);
374        }
375    
376        /**
377         * Provides serialization support.
378         *
379         * @param stream  the input stream.
380         *
381         * @throws IOException  if there is an I/O error.
382         * @throws ClassNotFoundException  if there is a classpath problem.
383         */
384        private void readObject(ObjectInputStream stream)
385                throws IOException, ClassNotFoundException {
386            stream.defaultReadObject();
387            this.errorIndicatorPaint = SerialUtilities.readPaint(stream);
388        }
389    
390    }