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     * $Id: StatisticalBarRenderer.java,v 1.4.2.6 2007/02/02 15:52:07 mungady Exp $
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     *
055     */
056    
057    package org.jfree.chart.renderer.category;
058    
059    import java.awt.Color;
060    import java.awt.Graphics2D;
061    import java.awt.Paint;
062    import java.awt.geom.Line2D;
063    import java.awt.geom.Rectangle2D;
064    import java.io.IOException;
065    import java.io.ObjectInputStream;
066    import java.io.ObjectOutputStream;
067    import java.io.Serializable;
068    
069    import org.jfree.chart.axis.CategoryAxis;
070    import org.jfree.chart.axis.ValueAxis;
071    import org.jfree.chart.entity.EntityCollection;
072    import org.jfree.chart.event.RendererChangeEvent;
073    import org.jfree.chart.labels.CategoryItemLabelGenerator;
074    import org.jfree.chart.plot.CategoryPlot;
075    import org.jfree.chart.plot.PlotOrientation;
076    import org.jfree.data.category.CategoryDataset;
077    import org.jfree.data.statistics.StatisticalCategoryDataset;
078    import org.jfree.io.SerialUtilities;
079    import org.jfree.ui.RectangleEdge;
080    import org.jfree.util.PaintUtilities;
081    import org.jfree.util.PublicCloneable;
082    
083    /**
084     * A renderer that handles the drawing a bar plot where
085     * each bar has a mean value and a standard deviation line.
086     */
087    public class StatisticalBarRenderer extends BarRenderer
088                                        implements CategoryItemRenderer, 
089                                                   Cloneable, PublicCloneable, 
090                                                   Serializable {
091    
092        /** For serialization. */
093        private static final long serialVersionUID = -4986038395414039117L;
094        
095        /** The paint used to show the error indicator. */
096        private transient Paint errorIndicatorPaint;
097        
098        /**
099         * Default constructor.
100         */
101        public StatisticalBarRenderer() {
102            super();
103            this.errorIndicatorPaint = Color.gray;
104        }
105    
106        /**
107         * Returns the paint used for the error indicators.
108         * 
109         * @return The paint used for the error indicators (possibly 
110         *         <code>null</code>).
111         */
112        public Paint getErrorIndicatorPaint() {
113            return this.errorIndicatorPaint;   
114        }
115    
116        /**
117         * Sets the paint used for the error indicators (if <code>null</code>, 
118         * the item outline paint is used instead)
119         * 
120         * @param paint  the paint (<code>null</code> permitted).
121         */
122        public void setErrorIndicatorPaint(Paint paint) {
123            this.errorIndicatorPaint = paint;
124            notifyListeners(new RendererChangeEvent(this));
125        }
126        
127        /**
128         * Draws the bar with its standard deviation line range for a single 
129         * (series, category) data item.
130         *
131         * @param g2  the graphics device.
132         * @param state  the renderer state.
133         * @param dataArea  the data area.
134         * @param plot  the plot.
135         * @param domainAxis  the domain axis.
136         * @param rangeAxis  the range axis.
137         * @param data  the data.
138         * @param row  the row index (zero-based).
139         * @param column  the column index (zero-based).
140         * @param pass  the pass index.
141         */
142        public void drawItem(Graphics2D g2,
143                             CategoryItemRendererState state,
144                             Rectangle2D dataArea,
145                             CategoryPlot plot,
146                             CategoryAxis domainAxis,
147                             ValueAxis rangeAxis,
148                             CategoryDataset data,
149                             int row,
150                             int column,
151                             int pass) {
152    
153            // defensive check
154            if (!(data instanceof StatisticalCategoryDataset)) {
155                throw new IllegalArgumentException(
156                    "Requires StatisticalCategoryDataset.");
157            }
158            StatisticalCategoryDataset statData = (StatisticalCategoryDataset) data;
159    
160            PlotOrientation orientation = plot.getOrientation();
161            if (orientation == PlotOrientation.HORIZONTAL) {
162                drawHorizontalItem(g2, state, dataArea, plot, domainAxis, 
163                        rangeAxis, statData, row, column);
164            }
165            else if (orientation == PlotOrientation.VERTICAL) {
166                drawVerticalItem(g2, state, dataArea, plot, domainAxis, rangeAxis, 
167                        statData, row, column);
168            }
169        }
170                    
171        /**
172         * Draws an item for a plot with a horizontal orientation.
173         * 
174         * @param g2  the graphics device.
175         * @param state  the renderer state.
176         * @param dataArea  the data area.
177         * @param plot  the plot.
178         * @param domainAxis  the domain axis.
179         * @param rangeAxis  the range axis.
180         * @param dataset  the data.
181         * @param row  the row index (zero-based).
182         * @param column  the column index (zero-based).
183         */
184        protected void drawHorizontalItem(Graphics2D g2,
185                                          CategoryItemRendererState state,
186                                          Rectangle2D dataArea,
187                                          CategoryPlot plot,
188                                          CategoryAxis domainAxis,
189                                          ValueAxis rangeAxis,
190                                          StatisticalCategoryDataset dataset,
191                                          int row,
192                                          int column) {
193                                         
194            RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
195            
196            // BAR Y
197            double rectY = domainAxis.getCategoryStart(column, getColumnCount(), 
198                    dataArea, xAxisLocation);
199    
200            int seriesCount = getRowCount();
201            int categoryCount = getColumnCount();
202            if (seriesCount > 1) {
203                double seriesGap = dataArea.getHeight() * getItemMargin()
204                                   / (categoryCount * (seriesCount - 1));
205                rectY = rectY + row * (state.getBarWidth() + seriesGap);
206            }
207            else {
208                rectY = rectY + row * state.getBarWidth();
209            }
210    
211            // BAR X
212            Number meanValue = dataset.getMeanValue(row, column);
213    
214            double value = meanValue.doubleValue();
215            double base = 0.0;
216            double lclip = getLowerClip();
217            double uclip = getUpperClip();
218    
219            if (uclip <= 0.0) {  // cases 1, 2, 3 and 4
220                if (value >= uclip) {
221                    return; // bar is not visible
222                }
223                base = uclip;
224                if (value <= lclip) {
225                    value = lclip;
226                }
227            }
228            else if (lclip <= 0.0) { // cases 5, 6, 7 and 8
229                if (value >= uclip) {
230                    value = uclip;
231                }
232                else {
233                    if (value <= lclip) {
234                        value = lclip;
235                    }
236                }
237            }
238            else { // cases 9, 10, 11 and 12
239                if (value <= lclip) {
240                    return; // bar is not visible
241                }
242                base = getLowerClip();
243                if (value >= uclip) {
244                   value = uclip;
245                }
246            }
247    
248            RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
249            double transY1 = rangeAxis.valueToJava2D(base, dataArea, yAxisLocation);
250            double transY2 = rangeAxis.valueToJava2D(value, dataArea, 
251                    yAxisLocation);
252            double rectX = Math.min(transY2, transY1);
253    
254            double rectHeight = state.getBarWidth();
255            double rectWidth = Math.abs(transY2 - transY1);
256    
257            Rectangle2D bar = new Rectangle2D.Double(rectX, rectY, rectWidth, 
258                    rectHeight);
259            Paint seriesPaint = getItemPaint(row, column);
260            g2.setPaint(seriesPaint);
261            g2.fill(bar);
262            if (state.getBarWidth() > 3) {
263                g2.setStroke(getItemStroke(row, column));
264                g2.setPaint(getItemOutlinePaint(row, column));
265                g2.draw(bar);
266            }
267    
268            // standard deviation lines
269            double valueDelta = dataset.getStdDevValue(row, column).doubleValue();
270            double highVal = rangeAxis.valueToJava2D(meanValue.doubleValue() 
271                    + valueDelta, dataArea, yAxisLocation);
272            double lowVal = rangeAxis.valueToJava2D(meanValue.doubleValue() 
273                    - valueDelta, dataArea, yAxisLocation);
274    
275            if (this.errorIndicatorPaint != null) {
276                g2.setPaint(this.errorIndicatorPaint);  
277            }
278            else {
279                g2.setPaint(getItemOutlinePaint(row, column));   
280            }
281            Line2D line = null;
282            line = new Line2D.Double(lowVal, rectY + rectHeight / 2.0d, 
283                                     highVal, rectY + rectHeight / 2.0d);
284            g2.draw(line);
285            line = new Line2D.Double(highVal, rectY + rectHeight * 0.25, 
286                                     highVal, rectY + rectHeight * 0.75);
287            g2.draw(line);
288            line = new Line2D.Double(lowVal, rectY + rectHeight * 0.25, 
289                                     lowVal, rectY + rectHeight * 0.75);
290            g2.draw(line);
291            
292            CategoryItemLabelGenerator generator = getItemLabelGenerator(row, 
293                    column);
294            if (generator != null && isItemLabelVisible(row, column)) {
295                drawItemLabel(g2, dataset, row, column, plot, generator, bar, 
296                    (value < 0.0));
297            }        
298    
299            // add an item entity, if this information is being collected
300            EntityCollection entities = state.getEntityCollection();
301            if (entities != null) {
302                addItemEntity(entities, dataset, row, column, bar);
303            }
304    
305        }
306    
307        /**
308         * Draws an item for a plot with a vertical orientation.
309         * 
310         * @param g2  the graphics device.
311         * @param state  the renderer state.
312         * @param dataArea  the data area.
313         * @param plot  the plot.
314         * @param domainAxis  the domain axis.
315         * @param rangeAxis  the range axis.
316         * @param dataset  the data.
317         * @param row  the row index (zero-based).
318         * @param column  the column index (zero-based).
319         */
320        protected void drawVerticalItem(Graphics2D g2,
321                                        CategoryItemRendererState state,
322                                        Rectangle2D dataArea,
323                                        CategoryPlot plot,
324                                        CategoryAxis domainAxis,
325                                        ValueAxis rangeAxis,
326                                        StatisticalCategoryDataset dataset,
327                                        int row,
328                                        int column) {
329                                         
330            RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
331            
332            // BAR X
333            double rectX = domainAxis.getCategoryStart(
334                column, getColumnCount(), dataArea, xAxisLocation
335            );
336    
337            int seriesCount = getRowCount();
338            int categoryCount = getColumnCount();
339            if (seriesCount > 1) {
340                double seriesGap = dataArea.getWidth() * getItemMargin()
341                                   / (categoryCount * (seriesCount - 1));
342                rectX = rectX + row * (state.getBarWidth() + seriesGap);
343            }
344            else {
345                rectX = rectX + row * state.getBarWidth();
346            }
347    
348            // BAR Y
349            Number meanValue = dataset.getMeanValue(row, column);
350    
351            double value = meanValue.doubleValue();
352            double base = 0.0;
353            double lclip = getLowerClip();
354            double uclip = getUpperClip();
355    
356            if (uclip <= 0.0) {  // cases 1, 2, 3 and 4
357                if (value >= uclip) {
358                    return; // bar is not visible
359                }
360                base = uclip;
361                if (value <= lclip) {
362                    value = lclip;
363                }
364            }
365            else if (lclip <= 0.0) { // cases 5, 6, 7 and 8
366                if (value >= uclip) {
367                    value = uclip;
368                }
369                else {
370                    if (value <= lclip) {
371                        value = lclip;
372                    }
373                }
374            }
375            else { // cases 9, 10, 11 and 12
376                if (value <= lclip) {
377                    return; // bar is not visible
378                }
379                base = getLowerClip();
380                if (value >= uclip) {
381                   value = uclip;
382                }
383            }
384    
385            RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
386            double transY1 = rangeAxis.valueToJava2D(base, dataArea, yAxisLocation);
387            double transY2 = rangeAxis.valueToJava2D(value, dataArea, 
388                    yAxisLocation);
389            double rectY = Math.min(transY2, transY1);
390    
391            double rectWidth = state.getBarWidth();
392            double rectHeight = Math.abs(transY2 - transY1);
393    
394            Rectangle2D bar = new Rectangle2D.Double(rectX, rectY, rectWidth, 
395                    rectHeight);
396            Paint seriesPaint = getItemPaint(row, column);
397            g2.setPaint(seriesPaint);
398            g2.fill(bar);
399            if (state.getBarWidth() > 3) {
400                g2.setStroke(getItemStroke(row, column));
401                g2.setPaint(getItemOutlinePaint(row, column));
402                g2.draw(bar);
403            }
404    
405            // standard deviation lines
406            double valueDelta = dataset.getStdDevValue(row, column).doubleValue();
407            double highVal = rangeAxis.valueToJava2D(meanValue.doubleValue() 
408                    + valueDelta, dataArea, yAxisLocation);
409            double lowVal = rangeAxis.valueToJava2D(meanValue.doubleValue() 
410                    - valueDelta, dataArea, yAxisLocation);
411    
412            if (this.errorIndicatorPaint != null) {
413                g2.setPaint(this.errorIndicatorPaint);  
414            }
415            else {
416                g2.setPaint(getItemOutlinePaint(row, column));   
417            }
418            Line2D line = null;
419            line = new Line2D.Double(rectX + rectWidth / 2.0d, lowVal,
420                                     rectX + rectWidth / 2.0d, highVal);
421            g2.draw(line);
422            line = new Line2D.Double(rectX + rectWidth / 2.0d - 5.0d, highVal,
423                                     rectX + rectWidth / 2.0d + 5.0d, highVal);
424            g2.draw(line);
425            line = new Line2D.Double(rectX + rectWidth / 2.0d - 5.0d, lowVal,
426                                     rectX + rectWidth / 2.0d + 5.0d, lowVal);
427            g2.draw(line);
428            
429            CategoryItemLabelGenerator generator = getItemLabelGenerator(row, 
430                    column);
431            if (generator != null && isItemLabelVisible(row, column)) {
432                drawItemLabel(g2, dataset, row, column, plot, generator, bar, 
433                    (value < 0.0));
434            }        
435    
436            // add an item entity, if this information is being collected
437            EntityCollection entities = state.getEntityCollection();
438            if (entities != null) {
439                addItemEntity(entities, dataset, row, column, bar);
440            }
441        }
442        
443        /**
444         * Tests this renderer for equality with an arbitrary object.
445         * 
446         * @param obj  the object (<code>null</code> permitted).
447         * 
448         * @return A boolean.
449         */
450        public boolean equals(Object obj) {
451            if (obj == this) {
452                return true;   
453            }
454            if (!(obj instanceof StatisticalBarRenderer)) {
455                return false;   
456            }
457            if (!super.equals(obj)) {
458                return false;   
459            }
460            StatisticalBarRenderer that = (StatisticalBarRenderer) obj;
461            if (!PaintUtilities.equal(this.errorIndicatorPaint, 
462                    that.errorIndicatorPaint)) {
463                return false;
464            }
465            return true;
466        }
467        
468        /**
469         * Provides serialization support.
470         *
471         * @param stream  the output stream.
472         *
473         * @throws IOException  if there is an I/O error.
474         */
475        private void writeObject(ObjectOutputStream stream) throws IOException {
476            stream.defaultWriteObject();
477            SerialUtilities.writePaint(this.errorIndicatorPaint, stream);
478        }
479    
480        /**
481         * Provides serialization support.
482         *
483         * @param stream  the input stream.
484         *
485         * @throws IOException  if there is an I/O error.
486         * @throws ClassNotFoundException  if there is a classpath problem.
487         */
488        private void readObject(ObjectInputStream stream) 
489            throws IOException, ClassNotFoundException {
490            stream.defaultReadObject();
491            this.errorIndicatorPaint = SerialUtilities.readPaint(stream);
492        }
493    
494    }