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