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    
065    package org.jfree.chart.renderer.category;
066    
067    import java.awt.BasicStroke;
068    import java.awt.Color;
069    import java.awt.GradientPaint;
070    import java.awt.Graphics2D;
071    import java.awt.Paint;
072    import java.awt.Stroke;
073    import java.awt.geom.Line2D;
074    import java.awt.geom.Rectangle2D;
075    import java.io.IOException;
076    import java.io.ObjectInputStream;
077    import java.io.ObjectOutputStream;
078    import java.io.Serializable;
079    
080    import org.jfree.chart.axis.CategoryAxis;
081    import org.jfree.chart.axis.ValueAxis;
082    import org.jfree.chart.entity.EntityCollection;
083    import org.jfree.chart.event.RendererChangeEvent;
084    import org.jfree.chart.labels.CategoryItemLabelGenerator;
085    import org.jfree.chart.plot.CategoryPlot;
086    import org.jfree.chart.plot.PlotOrientation;
087    import org.jfree.data.Range;
088    import org.jfree.data.category.CategoryDataset;
089    import org.jfree.data.statistics.StatisticalCategoryDataset;
090    import org.jfree.io.SerialUtilities;
091    import org.jfree.ui.GradientPaintTransformer;
092    import org.jfree.ui.RectangleEdge;
093    import org.jfree.util.ObjectUtilities;
094    import org.jfree.util.PaintUtilities;
095    import 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     */
106    public 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    }