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 * StatisticalBarRenderer.java
029 * ---------------------------
030 * (C) Copyright 2002-2014, 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    @Override
200    public Range findRangeBounds(CategoryDataset dataset) {
201         return findRangeBounds(dataset, true);
202    }
203
204    /**
205     * Draws the bar with its standard deviation line range for a single
206     * (series, category) data item.
207     *
208     * @param g2  the graphics device.
209     * @param state  the renderer state.
210     * @param dataArea  the data area.
211     * @param plot  the plot.
212     * @param domainAxis  the domain axis.
213     * @param rangeAxis  the range axis.
214     * @param data  the data.
215     * @param row  the row index (zero-based).
216     * @param column  the column index (zero-based).
217     * @param pass  the pass index.
218     */
219    @Override
220    public void drawItem(Graphics2D g2, CategoryItemRendererState state,
221            Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis,
222            ValueAxis rangeAxis, CategoryDataset data, int row, int column,
223            int pass) {
224
225        int visibleRow = state.getVisibleSeriesIndex(row);
226        if (visibleRow < 0) {
227            return;
228        }
229        // defensive check
230        if (!(data instanceof StatisticalCategoryDataset)) {
231            throw new IllegalArgumentException(
232                "Requires StatisticalCategoryDataset.");
233        }
234        StatisticalCategoryDataset statData = (StatisticalCategoryDataset) data;
235
236        PlotOrientation orientation = plot.getOrientation();
237        if (orientation == PlotOrientation.HORIZONTAL) {
238            drawHorizontalItem(g2, state, dataArea, plot, domainAxis,
239                    rangeAxis, statData, visibleRow, row, column);
240        }
241        else if (orientation == PlotOrientation.VERTICAL) {
242            drawVerticalItem(g2, state, dataArea, plot, domainAxis, rangeAxis,
243                    statData, visibleRow, row, column);
244        }
245    }
246
247    /**
248     * Draws an item for a plot with a horizontal orientation.
249     *
250     * @param g2  the graphics device.
251     * @param state  the renderer state.
252     * @param dataArea  the data area.
253     * @param plot  the plot.
254     * @param domainAxis  the domain axis.
255     * @param rangeAxis  the range axis.
256     * @param dataset  the data.
257     * @param visibleRow  the visible row index.
258     * @param row  the row index (zero-based).
259     * @param column  the column index (zero-based).
260     */
261    protected void drawHorizontalItem(Graphics2D g2,
262                                      CategoryItemRendererState state,
263                                      Rectangle2D dataArea,
264                                      CategoryPlot plot,
265                                      CategoryAxis domainAxis,
266                                      ValueAxis rangeAxis,
267                                      StatisticalCategoryDataset dataset,
268                                      int visibleRow,
269                                      int row,
270                                      int column) {
271
272        // BAR Y
273        double rectY = calculateBarW0(plot, PlotOrientation.HORIZONTAL, 
274                dataArea, domainAxis, state, visibleRow, column);
275
276        // BAR X
277        Number meanValue = dataset.getMeanValue(row, column);
278        if (meanValue == null) {
279            return;
280        }
281        double value = meanValue.doubleValue();
282        double base = 0.0;
283        double lclip = getLowerClip();
284        double uclip = getUpperClip();
285
286        if (uclip <= 0.0) {  // cases 1, 2, 3 and 4
287            if (value >= uclip) {
288                return; // bar is not visible
289            }
290            base = uclip;
291            if (value <= lclip) {
292                value = lclip;
293            }
294        }
295        else if (lclip <= 0.0) { // cases 5, 6, 7 and 8
296            if (value >= uclip) {
297                value = uclip;
298            }
299            else {
300                if (value <= lclip) {
301                    value = lclip;
302                }
303            }
304        }
305        else { // cases 9, 10, 11 and 12
306            if (value <= lclip) {
307                return; // bar is not visible
308            }
309            base = getLowerClip();
310            if (value >= uclip) {
311               value = uclip;
312            }
313        }
314
315        RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
316        double transY1 = rangeAxis.valueToJava2D(base, dataArea, yAxisLocation);
317        double transY2 = rangeAxis.valueToJava2D(value, dataArea,
318                yAxisLocation);
319        double rectX = Math.min(transY2, transY1);
320
321        double rectHeight = state.getBarWidth();
322        double rectWidth = Math.abs(transY2 - transY1);
323
324        Rectangle2D bar = new Rectangle2D.Double(rectX, rectY, rectWidth,
325                rectHeight);
326        Paint itemPaint = getItemPaint(row, column);
327        GradientPaintTransformer t = getGradientPaintTransformer();
328        if (t != null && itemPaint instanceof GradientPaint) {
329            itemPaint = t.transform((GradientPaint) itemPaint, bar);
330        }
331        g2.setPaint(itemPaint);
332        g2.fill(bar);
333
334        // draw the outline...
335        if (isDrawBarOutline()
336                && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
337            Stroke stroke = getItemOutlineStroke(row, column);
338            Paint paint = getItemOutlinePaint(row, column);
339            if (stroke != null && paint != null) {
340                g2.setStroke(stroke);
341                g2.setPaint(paint);
342                g2.draw(bar);
343            }
344        }
345
346        // standard deviation lines
347        Number n = dataset.getStdDevValue(row, column);
348        if (n != null) {
349            double valueDelta = n.doubleValue();
350            double highVal = rangeAxis.valueToJava2D(meanValue.doubleValue()
351                    + valueDelta, dataArea, yAxisLocation);
352            double lowVal = rangeAxis.valueToJava2D(meanValue.doubleValue()
353                    - valueDelta, dataArea, yAxisLocation);
354
355            if (this.errorIndicatorPaint != null) {
356                g2.setPaint(this.errorIndicatorPaint);
357            }
358            else {
359                g2.setPaint(getItemOutlinePaint(row, column));
360            }
361            if (this.errorIndicatorStroke != null) {
362                g2.setStroke(this.errorIndicatorStroke);
363            }
364            else {
365                g2.setStroke(getItemOutlineStroke(row, column));
366            }
367            Line2D line;
368            line = new Line2D.Double(lowVal, rectY + rectHeight / 2.0d,
369                                     highVal, rectY + rectHeight / 2.0d);
370            g2.draw(line);
371            line = new Line2D.Double(highVal, rectY + rectHeight * 0.25,
372                                     highVal, rectY + rectHeight * 0.75);
373            g2.draw(line);
374            line = new Line2D.Double(lowVal, rectY + rectHeight * 0.25,
375                                     lowVal, rectY + rectHeight * 0.75);
376            g2.draw(line);
377        }
378
379        CategoryItemLabelGenerator generator = getItemLabelGenerator(row,
380                column);
381        if (generator != null && isItemLabelVisible(row, column)) {
382            drawItemLabel(g2, dataset, row, column, plot, generator, bar,
383                    (value < 0.0));
384        }
385
386        // add an item entity, if this information is being collected
387        EntityCollection entities = state.getEntityCollection();
388        if (entities != null) {
389            addItemEntity(entities, dataset, row, column, bar);
390        }
391
392    }
393
394    /**
395     * Draws an item for a plot with a vertical orientation.
396     *
397     * @param g2  the graphics device.
398     * @param state  the renderer state.
399     * @param dataArea  the data area.
400     * @param plot  the plot.
401     * @param domainAxis  the domain axis.
402     * @param rangeAxis  the range axis.
403     * @param dataset  the data.
404     * @param visibleRow  the visible row index.
405     * @param row  the row index (zero-based).
406     * @param column  the column index (zero-based).
407     */
408    protected void drawVerticalItem(Graphics2D g2,
409                                    CategoryItemRendererState state,
410                                    Rectangle2D dataArea,
411                                    CategoryPlot plot,
412                                    CategoryAxis domainAxis,
413                                    ValueAxis rangeAxis,
414                                    StatisticalCategoryDataset dataset,
415                                    int visibleRow,
416                                    int row,
417                                    int column) {
418
419        // BAR X
420        double rectX = calculateBarW0(plot, PlotOrientation.VERTICAL, dataArea,
421                domainAxis, state, visibleRow, column);
422
423        // BAR Y
424        Number meanValue = dataset.getMeanValue(row, column);
425        if (meanValue == null) {
426            return;
427        }
428
429        double value = meanValue.doubleValue();
430        double base = 0.0;
431        double lclip = getLowerClip();
432        double uclip = getUpperClip();
433
434        if (uclip <= 0.0) {  // cases 1, 2, 3 and 4
435            if (value >= uclip) {
436                return; // bar is not visible
437            }
438            base = uclip;
439            if (value <= lclip) {
440                value = lclip;
441            }
442        }
443        else if (lclip <= 0.0) { // cases 5, 6, 7 and 8
444            if (value >= uclip) {
445                value = uclip;
446            }
447            else {
448                if (value <= lclip) {
449                    value = lclip;
450                }
451            }
452        }
453        else { // cases 9, 10, 11 and 12
454            if (value <= lclip) {
455                return; // bar is not visible
456            }
457            base = getLowerClip();
458            if (value >= uclip) {
459               value = uclip;
460            }
461        }
462
463        RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
464        double transY1 = rangeAxis.valueToJava2D(base, dataArea, yAxisLocation);
465        double transY2 = rangeAxis.valueToJava2D(value, dataArea,
466                yAxisLocation);
467        double rectY = Math.min(transY2, transY1);
468
469        double rectWidth = state.getBarWidth();
470        double rectHeight = Math.abs(transY2 - transY1);
471
472        Rectangle2D bar = new Rectangle2D.Double(rectX, rectY, rectWidth,
473                rectHeight);
474        Paint itemPaint = getItemPaint(row, column);
475        GradientPaintTransformer t = getGradientPaintTransformer();
476        if (t != null && itemPaint instanceof GradientPaint) {
477            itemPaint = t.transform((GradientPaint) itemPaint, bar);
478        }
479        g2.setPaint(itemPaint);
480        g2.fill(bar);
481        // draw the outline...
482        if (isDrawBarOutline()
483                && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
484            Stroke stroke = getItemOutlineStroke(row, column);
485            Paint paint = getItemOutlinePaint(row, column);
486            if (stroke != null && paint != null) {
487                g2.setStroke(stroke);
488                g2.setPaint(paint);
489                g2.draw(bar);
490            }
491        }
492
493        // standard deviation lines
494        Number n = dataset.getStdDevValue(row, column);
495        if (n != null) {
496            double valueDelta = n.doubleValue();
497            double highVal = rangeAxis.valueToJava2D(meanValue.doubleValue()
498                    + valueDelta, dataArea, yAxisLocation);
499            double lowVal = rangeAxis.valueToJava2D(meanValue.doubleValue()
500                    - valueDelta, dataArea, yAxisLocation);
501
502            if (this.errorIndicatorPaint != null) {
503                g2.setPaint(this.errorIndicatorPaint);
504            }
505            else {
506                g2.setPaint(getItemOutlinePaint(row, column));
507            }
508            if (this.errorIndicatorStroke != null) {
509                g2.setStroke(this.errorIndicatorStroke);
510            }
511            else {
512                g2.setStroke(getItemOutlineStroke(row, column));
513            }
514
515            Line2D line;
516            line = new Line2D.Double(rectX + rectWidth / 2.0d, lowVal,
517                                     rectX + rectWidth / 2.0d, highVal);
518            g2.draw(line);
519            line = new Line2D.Double(rectX + rectWidth / 2.0d - 5.0d, highVal,
520                                     rectX + rectWidth / 2.0d + 5.0d, highVal);
521            g2.draw(line);
522            line = new Line2D.Double(rectX + rectWidth / 2.0d - 5.0d, lowVal,
523                                     rectX + rectWidth / 2.0d + 5.0d, lowVal);
524            g2.draw(line);
525        }
526
527        CategoryItemLabelGenerator generator = getItemLabelGenerator(row,
528                column);
529        if (generator != null && isItemLabelVisible(row, column)) {
530            drawItemLabel(g2, dataset, row, column, plot, generator, bar,
531                    (value < 0.0));
532        }
533
534        // add an item entity, if this information is being collected
535        EntityCollection entities = state.getEntityCollection();
536        if (entities != null) {
537            addItemEntity(entities, dataset, row, column, bar);
538        }
539    }
540
541    /**
542     * Tests this renderer for equality with an arbitrary object.
543     *
544     * @param obj  the object (<code>null</code> permitted).
545     *
546     * @return A boolean.
547     */
548    @Override
549    public boolean equals(Object obj) {
550        if (obj == this) {
551            return true;
552        }
553        if (!(obj instanceof StatisticalBarRenderer)) {
554            return false;
555        }
556        StatisticalBarRenderer that = (StatisticalBarRenderer) obj;
557        if (!PaintUtilities.equal(this.errorIndicatorPaint,
558                that.errorIndicatorPaint)) {
559            return false;
560        }
561        if (!ObjectUtilities.equal(this.errorIndicatorStroke,
562                that.errorIndicatorStroke)) {
563            return false;
564        }
565        return super.equals(obj);
566    }
567
568    /**
569     * Provides serialization support.
570     *
571     * @param stream  the output stream.
572     *
573     * @throws IOException  if there is an I/O error.
574     */
575    private void writeObject(ObjectOutputStream stream) throws IOException {
576        stream.defaultWriteObject();
577        SerialUtilities.writePaint(this.errorIndicatorPaint, stream);
578        SerialUtilities.writeStroke(this.errorIndicatorStroke, stream);
579    }
580
581    /**
582     * Provides serialization support.
583     *
584     * @param stream  the input stream.
585     *
586     * @throws IOException  if there is an I/O error.
587     * @throws ClassNotFoundException  if there is a classpath problem.
588     */
589    private void readObject(ObjectInputStream stream)
590        throws IOException, ClassNotFoundException {
591        stream.defaultReadObject();
592        this.errorIndicatorPaint = SerialUtilities.readPaint(stream);
593        this.errorIndicatorStroke = SerialUtilities.readStroke(stream);
594    }
595
596}