001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2011, 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 * StatisticalBarRenderer.java
029 * ---------------------------
030 * (C) Copyright 2002-2011, by Pascal Collet and Contributors.
031 *
032 * Original Author:  Pascal Collet;
033 * Contributor(s):   David Gilbert (for Object Refinery Limited);
034 *                   Christian W. Zuckschwerdt;
035 *                   Peter Kolb (patches 2497611, 2791407);
036 *                   Martin Hoeller;
037 *
038 * Changes
039 * -------
040 * 21-Aug-2002 : Version 1, contributed by Pascal Collet (DG);
041 * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
042 * 24-Oct-2002 : Changes to dataset interface (DG);
043 * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG);
044 * 05-Feb-2003 : Updates for new DefaultStatisticalCategoryDataset (DG);
045 * 25-Mar-2003 : Implemented Serializable (DG);
046 * 30-Jul-2003 : Modified entity constructor (CZ);
047 * 06-Oct-2003 : Corrected typo in exception message (DG);
048 * 05-Nov-2004 : Modified drawItem() signature (DG);
049 * 15-Jun-2005 : Added errorIndicatorPaint attribute (DG);
050 * ------------- JFREECHART 1.0.x ---------------------------------------------
051 * 19-May-2006 : Added support for tooltips and URLs (DG);
052 * 12-Jul-2006 : Added support for item labels (DG);
053 * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG);
054 * 28-Aug-2007 : Fixed NullPointerException - see bug 1779941 (DG);
055 * 14-Nov-2007 : Added errorIndicatorStroke, and fixed bugs with drawBarOutline
056 *               and gradientPaintTransformer attributes being ignored (DG);
057 * 14-Jan-2009 : Added support for seriesVisible flags (PK);
058 * 16-May-2009 : Added findRangeBounds() override to take into account the
059 *               dataset interval (PK);
060 * 28-Oct-2011 : Fixed problem with maximalBarWidth, bug #2810220 (MH);
061 * 30-Oct-2011 : Additional change for bug #2810220 (DG);
062 *
063 */
064
065package org.jfree.chart.renderer.category;
066
067import java.awt.BasicStroke;
068import java.awt.Color;
069import java.awt.GradientPaint;
070import java.awt.Graphics2D;
071import java.awt.Paint;
072import java.awt.Stroke;
073import java.awt.geom.Line2D;
074import java.awt.geom.Rectangle2D;
075import java.io.IOException;
076import java.io.ObjectInputStream;
077import java.io.ObjectOutputStream;
078import java.io.Serializable;
079
080import org.jfree.chart.axis.CategoryAxis;
081import org.jfree.chart.axis.ValueAxis;
082import org.jfree.chart.entity.EntityCollection;
083import org.jfree.chart.event.RendererChangeEvent;
084import org.jfree.chart.labels.CategoryItemLabelGenerator;
085import org.jfree.chart.plot.CategoryPlot;
086import org.jfree.chart.plot.PlotOrientation;
087import org.jfree.data.Range;
088import org.jfree.data.category.CategoryDataset;
089import org.jfree.data.statistics.StatisticalCategoryDataset;
090import org.jfree.io.SerialUtilities;
091import org.jfree.ui.GradientPaintTransformer;
092import org.jfree.ui.RectangleEdge;
093import org.jfree.util.ObjectUtilities;
094import org.jfree.util.PaintUtilities;
095import org.jfree.util.PublicCloneable;
096
097/**
098 * A renderer that handles the drawing a bar plot where
099 * each bar has a mean value and a standard deviation line.  The example shown
100 * here is generated by the <code>StatisticalBarChartDemo1.java</code> program
101 * included in the JFreeChart Demo Collection:
102 * <br><br>
103 * <img src="../../../../../images/StatisticalBarRendererSample.png"
104 * alt="StatisticalBarRendererSample.png" />
105 */
106public class StatisticalBarRenderer extends BarRenderer
107        implements CategoryItemRenderer, Cloneable, PublicCloneable,
108                   Serializable {
109
110    /** For serialization. */
111    private static final long serialVersionUID = -4986038395414039117L;
112
113    /** The paint used to show the error indicator. */
114    private transient Paint errorIndicatorPaint;
115
116    /**
117     * The stroke used to draw the error indicators.
118     *
119     * @since 1.0.8
120     */
121    private transient Stroke errorIndicatorStroke;
122
123    /**
124     * Default constructor.
125     */
126    public StatisticalBarRenderer() {
127        super();
128        this.errorIndicatorPaint = Color.gray;
129        this.errorIndicatorStroke = new BasicStroke(1.0f);
130    }
131
132    /**
133     * Returns the paint used for the error indicators.
134     *
135     * @return The paint used for the error indicators (possibly
136     *         <code>null</code>).
137     *
138     * @see #setErrorIndicatorPaint(Paint)
139     */
140    public Paint getErrorIndicatorPaint() {
141        return this.errorIndicatorPaint;
142    }
143
144    /**
145     * Sets the paint used for the error indicators (if <code>null</code>,
146     * the item outline paint is used instead) and sends a
147     * {@link RendererChangeEvent} to all registered listeners.
148     *
149     * @param paint  the paint (<code>null</code> permitted).
150     *
151     * @see #getErrorIndicatorPaint()
152     */
153    public void setErrorIndicatorPaint(Paint paint) {
154        this.errorIndicatorPaint = paint;
155        fireChangeEvent();
156    }
157
158    /**
159     * Returns the stroke used to draw the error indicators.  If this is
160     * <code>null</code>, the renderer will use the item outline stroke).
161     *
162     * @return The stroke (possibly <code>null</code>).
163     *
164     * @see #setErrorIndicatorStroke(Stroke)
165     *
166     * @since 1.0.8
167     */
168    public Stroke getErrorIndicatorStroke() {
169        return this.errorIndicatorStroke;
170    }
171
172    /**
173     * Sets the stroke used to draw the error indicators, and sends a
174     * {@link RendererChangeEvent} to all registered listeners.  If you set
175     * this to <code>null</code>, the renderer will use the item outline
176     * stroke.
177     *
178     * @param stroke  the stroke (<code>null</code> permitted).
179     *
180     * @see #getErrorIndicatorStroke()
181     *
182     * @since 1.0.8
183     */
184    public void setErrorIndicatorStroke(Stroke stroke) {
185        this.errorIndicatorStroke = stroke;
186        fireChangeEvent();
187    }
188
189    /**
190     * Returns the range of values the renderer requires to display all the
191     * items from the specified dataset. This takes into account the range
192     * between the min/max values, possibly ignoring invisible series.
193     *
194     * @param dataset  the dataset (<code>null</code> permitted).
195     *
196     * @return The range (or <code>null</code> if the dataset is
197     *         <code>null</code> or empty).
198     */
199    public Range findRangeBounds(CategoryDataset dataset) {
200         return findRangeBounds(dataset, true);
201    }
202
203    /**
204     * Draws the bar with its standard deviation line range for a single
205     * (series, category) data item.
206     *
207     * @param g2  the graphics device.
208     * @param state  the renderer state.
209     * @param dataArea  the data area.
210     * @param plot  the plot.
211     * @param domainAxis  the domain axis.
212     * @param rangeAxis  the range axis.
213     * @param data  the data.
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 data,
225                         int row,
226                         int column,
227                         int pass) {
228
229        int visibleRow = state.getVisibleSeriesIndex(row);
230        if (visibleRow < 0) {
231            return;
232        }
233        // defensive check
234        if (!(data instanceof StatisticalCategoryDataset)) {
235            throw new IllegalArgumentException(
236                "Requires StatisticalCategoryDataset.");
237        }
238        StatisticalCategoryDataset statData = (StatisticalCategoryDataset) data;
239
240        PlotOrientation orientation = plot.getOrientation();
241        if (orientation == PlotOrientation.HORIZONTAL) {
242            drawHorizontalItem(g2, state, dataArea, plot, domainAxis,
243                    rangeAxis, statData, visibleRow, row, column);
244        }
245        else if (orientation == PlotOrientation.VERTICAL) {
246            drawVerticalItem(g2, state, dataArea, plot, domainAxis, rangeAxis,
247                    statData, visibleRow, row, column);
248        }
249    }
250
251    /**
252     * Draws an item for a plot with a horizontal orientation.
253     *
254     * @param g2  the graphics device.
255     * @param state  the renderer state.
256     * @param dataArea  the data area.
257     * @param plot  the plot.
258     * @param domainAxis  the domain axis.
259     * @param rangeAxis  the range axis.
260     * @param dataset  the data.
261     * @param visibleRow  the visible row index.
262     * @param row  the row index (zero-based).
263     * @param column  the column index (zero-based).
264     */
265    protected void drawHorizontalItem(Graphics2D g2,
266                                      CategoryItemRendererState state,
267                                      Rectangle2D dataArea,
268                                      CategoryPlot plot,
269                                      CategoryAxis domainAxis,
270                                      ValueAxis rangeAxis,
271                                      StatisticalCategoryDataset dataset,
272                                      int visibleRow,
273                                      int row,
274                                      int column) {
275
276        // BAR Y
277        double rectY = calculateBarW0(plot, PlotOrientation.HORIZONTAL, 
278                dataArea, domainAxis, state, visibleRow, column);
279
280        // BAR X
281        Number meanValue = dataset.getMeanValue(row, column);
282        if (meanValue == null) {
283            return;
284        }
285        double value = meanValue.doubleValue();
286        double base = 0.0;
287        double lclip = getLowerClip();
288        double uclip = getUpperClip();
289
290        if (uclip <= 0.0) {  // cases 1, 2, 3 and 4
291            if (value >= uclip) {
292                return; // bar is not visible
293            }
294            base = uclip;
295            if (value <= lclip) {
296                value = lclip;
297            }
298        }
299        else if (lclip <= 0.0) { // cases 5, 6, 7 and 8
300            if (value >= uclip) {
301                value = uclip;
302            }
303            else {
304                if (value <= lclip) {
305                    value = lclip;
306                }
307            }
308        }
309        else { // cases 9, 10, 11 and 12
310            if (value <= lclip) {
311                return; // bar is not visible
312            }
313            base = getLowerClip();
314            if (value >= uclip) {
315               value = uclip;
316            }
317        }
318
319        RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
320        double transY1 = rangeAxis.valueToJava2D(base, dataArea, yAxisLocation);
321        double transY2 = rangeAxis.valueToJava2D(value, dataArea,
322                yAxisLocation);
323        double rectX = Math.min(transY2, transY1);
324
325        double rectHeight = state.getBarWidth();
326        double rectWidth = Math.abs(transY2 - transY1);
327
328        Rectangle2D bar = new Rectangle2D.Double(rectX, rectY, rectWidth,
329                rectHeight);
330        Paint itemPaint = getItemPaint(row, column);
331        GradientPaintTransformer t = getGradientPaintTransformer();
332        if (t != null && itemPaint instanceof GradientPaint) {
333            itemPaint = t.transform((GradientPaint) itemPaint, bar);
334        }
335        g2.setPaint(itemPaint);
336        g2.fill(bar);
337
338        // draw the outline...
339        if (isDrawBarOutline()
340                && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
341            Stroke stroke = getItemOutlineStroke(row, column);
342            Paint paint = getItemOutlinePaint(row, column);
343            if (stroke != null && paint != null) {
344                g2.setStroke(stroke);
345                g2.setPaint(paint);
346                g2.draw(bar);
347            }
348        }
349
350        // standard deviation lines
351        Number n = dataset.getStdDevValue(row, column);
352        if (n != null) {
353            double valueDelta = n.doubleValue();
354            double highVal = rangeAxis.valueToJava2D(meanValue.doubleValue()
355                    + valueDelta, dataArea, yAxisLocation);
356            double lowVal = rangeAxis.valueToJava2D(meanValue.doubleValue()
357                    - valueDelta, dataArea, yAxisLocation);
358
359            if (this.errorIndicatorPaint != null) {
360                g2.setPaint(this.errorIndicatorPaint);
361            }
362            else {
363                g2.setPaint(getItemOutlinePaint(row, column));
364            }
365            if (this.errorIndicatorStroke != null) {
366                g2.setStroke(this.errorIndicatorStroke);
367            }
368            else {
369                g2.setStroke(getItemOutlineStroke(row, column));
370            }
371            Line2D line = null;
372            line = new Line2D.Double(lowVal, rectY + rectHeight / 2.0d,
373                                     highVal, rectY + rectHeight / 2.0d);
374            g2.draw(line);
375            line = new Line2D.Double(highVal, rectY + rectHeight * 0.25,
376                                     highVal, rectY + rectHeight * 0.75);
377            g2.draw(line);
378            line = new Line2D.Double(lowVal, rectY + rectHeight * 0.25,
379                                     lowVal, rectY + rectHeight * 0.75);
380            g2.draw(line);
381        }
382
383        CategoryItemLabelGenerator generator = getItemLabelGenerator(row,
384                column);
385        if (generator != null && isItemLabelVisible(row, column)) {
386            drawItemLabel(g2, dataset, row, column, plot, generator, bar,
387                    (value < 0.0));
388        }
389
390        // add an item entity, if this information is being collected
391        EntityCollection entities = state.getEntityCollection();
392        if (entities != null) {
393            addItemEntity(entities, dataset, row, column, bar);
394        }
395
396    }
397
398    /**
399     * Draws an item for a plot with a vertical orientation.
400     *
401     * @param g2  the graphics device.
402     * @param state  the renderer state.
403     * @param dataArea  the data area.
404     * @param plot  the plot.
405     * @param domainAxis  the domain axis.
406     * @param rangeAxis  the range axis.
407     * @param dataset  the data.
408     * @param visibleRow  the visible row index.
409     * @param row  the row index (zero-based).
410     * @param column  the column index (zero-based).
411     */
412    protected void drawVerticalItem(Graphics2D g2,
413                                    CategoryItemRendererState state,
414                                    Rectangle2D dataArea,
415                                    CategoryPlot plot,
416                                    CategoryAxis domainAxis,
417                                    ValueAxis rangeAxis,
418                                    StatisticalCategoryDataset dataset,
419                                    int visibleRow,
420                                    int row,
421                                    int column) {
422
423        // BAR X
424        double rectX = calculateBarW0(plot, PlotOrientation.VERTICAL, dataArea,
425                domainAxis, state, visibleRow, column);
426
427        // BAR Y
428        Number meanValue = dataset.getMeanValue(row, column);
429        if (meanValue == null) {
430            return;
431        }
432
433        double value = meanValue.doubleValue();
434        double base = 0.0;
435        double lclip = getLowerClip();
436        double uclip = getUpperClip();
437
438        if (uclip <= 0.0) {  // cases 1, 2, 3 and 4
439            if (value >= uclip) {
440                return; // bar is not visible
441            }
442            base = uclip;
443            if (value <= lclip) {
444                value = lclip;
445            }
446        }
447        else if (lclip <= 0.0) { // cases 5, 6, 7 and 8
448            if (value >= uclip) {
449                value = uclip;
450            }
451            else {
452                if (value <= lclip) {
453                    value = lclip;
454                }
455            }
456        }
457        else { // cases 9, 10, 11 and 12
458            if (value <= lclip) {
459                return; // bar is not visible
460            }
461            base = getLowerClip();
462            if (value >= uclip) {
463               value = uclip;
464            }
465        }
466
467        RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
468        double transY1 = rangeAxis.valueToJava2D(base, dataArea, yAxisLocation);
469        double transY2 = rangeAxis.valueToJava2D(value, dataArea,
470                yAxisLocation);
471        double rectY = Math.min(transY2, transY1);
472
473        double rectWidth = state.getBarWidth();
474        double rectHeight = Math.abs(transY2 - transY1);
475
476        Rectangle2D bar = new Rectangle2D.Double(rectX, rectY, rectWidth,
477                rectHeight);
478        Paint itemPaint = getItemPaint(row, column);
479        GradientPaintTransformer t = getGradientPaintTransformer();
480        if (t != null && itemPaint instanceof GradientPaint) {
481            itemPaint = t.transform((GradientPaint) itemPaint, bar);
482        }
483        g2.setPaint(itemPaint);
484        g2.fill(bar);
485        // draw the outline...
486        if (isDrawBarOutline()
487                && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
488            Stroke stroke = getItemOutlineStroke(row, column);
489            Paint paint = getItemOutlinePaint(row, column);
490            if (stroke != null && paint != null) {
491                g2.setStroke(stroke);
492                g2.setPaint(paint);
493                g2.draw(bar);
494            }
495        }
496
497        // standard deviation lines
498        Number n = dataset.getStdDevValue(row, column);
499        if (n != null) {
500            double valueDelta = n.doubleValue();
501            double highVal = rangeAxis.valueToJava2D(meanValue.doubleValue()
502                    + valueDelta, dataArea, yAxisLocation);
503            double lowVal = rangeAxis.valueToJava2D(meanValue.doubleValue()
504                    - valueDelta, dataArea, yAxisLocation);
505
506            if (this.errorIndicatorPaint != null) {
507                g2.setPaint(this.errorIndicatorPaint);
508            }
509            else {
510                g2.setPaint(getItemOutlinePaint(row, column));
511            }
512            if (this.errorIndicatorStroke != null) {
513                g2.setStroke(this.errorIndicatorStroke);
514            }
515            else {
516                g2.setStroke(getItemOutlineStroke(row, column));
517            }
518
519            Line2D line = null;
520            line = new Line2D.Double(rectX + rectWidth / 2.0d, lowVal,
521                                     rectX + rectWidth / 2.0d, highVal);
522            g2.draw(line);
523            line = new Line2D.Double(rectX + rectWidth / 2.0d - 5.0d, highVal,
524                                     rectX + rectWidth / 2.0d + 5.0d, highVal);
525            g2.draw(line);
526            line = new Line2D.Double(rectX + rectWidth / 2.0d - 5.0d, lowVal,
527                                     rectX + rectWidth / 2.0d + 5.0d, lowVal);
528            g2.draw(line);
529        }
530
531        CategoryItemLabelGenerator generator = getItemLabelGenerator(row,
532                column);
533        if (generator != null && isItemLabelVisible(row, column)) {
534            drawItemLabel(g2, dataset, row, column, plot, generator, bar,
535                    (value < 0.0));
536        }
537
538        // add an item entity, if this information is being collected
539        EntityCollection entities = state.getEntityCollection();
540        if (entities != null) {
541            addItemEntity(entities, dataset, row, column, bar);
542        }
543    }
544
545    /**
546     * Tests this renderer for equality with an arbitrary object.
547     *
548     * @param obj  the object (<code>null</code> permitted).
549     *
550     * @return A boolean.
551     */
552    public boolean equals(Object obj) {
553        if (obj == this) {
554            return true;
555        }
556        if (!(obj instanceof StatisticalBarRenderer)) {
557            return false;
558        }
559        StatisticalBarRenderer that = (StatisticalBarRenderer) obj;
560        if (!PaintUtilities.equal(this.errorIndicatorPaint,
561                that.errorIndicatorPaint)) {
562            return false;
563        }
564        if (!ObjectUtilities.equal(this.errorIndicatorStroke,
565                that.errorIndicatorStroke)) {
566            return false;
567        }
568        return super.equals(obj);
569    }
570
571    /**
572     * Provides serialization support.
573     *
574     * @param stream  the output stream.
575     *
576     * @throws IOException  if there is an I/O error.
577     */
578    private void writeObject(ObjectOutputStream stream) throws IOException {
579        stream.defaultWriteObject();
580        SerialUtilities.writePaint(this.errorIndicatorPaint, stream);
581        SerialUtilities.writeStroke(this.errorIndicatorStroke, stream);
582    }
583
584    /**
585     * Provides serialization support.
586     *
587     * @param stream  the input stream.
588     *
589     * @throws IOException  if there is an I/O error.
590     * @throws ClassNotFoundException  if there is a classpath problem.
591     */
592    private void readObject(ObjectInputStream stream)
593        throws IOException, ClassNotFoundException {
594        stream.defaultReadObject();
595        this.errorIndicatorPaint = SerialUtilities.readPaint(stream);
596        this.errorIndicatorStroke = SerialUtilities.readStroke(stream);
597    }
598
599}