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