    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    }