    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    }