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