001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2007, 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-2007, 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
053package org.jfree.chart.renderer.category;
054
055import java.awt.Graphics2D;
056import java.awt.Paint;
057import java.awt.Shape;
058import java.awt.geom.Line2D;
059import java.awt.geom.Rectangle2D;
060import java.io.IOException;
061import java.io.ObjectInputStream;
062import java.io.ObjectOutputStream;
063import java.io.Serializable;
064
065import org.jfree.chart.axis.CategoryAxis;
066import org.jfree.chart.axis.ValueAxis;
067import org.jfree.chart.entity.EntityCollection;
068import org.jfree.chart.event.RendererChangeEvent;
069import org.jfree.chart.plot.CategoryPlot;
070import org.jfree.chart.plot.PlotOrientation;
071import org.jfree.data.category.CategoryDataset;
072import org.jfree.data.statistics.StatisticalCategoryDataset;
073import org.jfree.io.SerialUtilities;
074import org.jfree.ui.RectangleEdge;
075import org.jfree.util.PaintUtilities;
076import org.jfree.util.PublicCloneable;
077import 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 */
084public 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        notifyListeners(new RendererChangeEvent(this));
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}