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