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