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