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