    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
    060    package org.jfree.chart.renderer.category;
    061
    062    import java.awt.BasicStroke;
    063    import java.awt.Color;
    064    import java.awt.GradientPaint;
    065    import java.awt.Graphics2D;
    066    import java.awt.Paint;
    067    import java.awt.Stroke;
    068    import java.awt.geom.Line2D;
    069    import java.awt.geom.Rectangle2D;
    070    import java.io.IOException;
    071    import java.io.ObjectInputStream;
    072    import java.io.ObjectOutputStream;
    073    import java.io.Serializable;
    074
    075    import org.jfree.chart.axis.CategoryAxis;
    076    import org.jfree.chart.axis.ValueAxis;
    077    import org.jfree.chart.entity.EntityCollection;
    078    import org.jfree.chart.event.RendererChangeEvent;
    079    import org.jfree.chart.labels.CategoryItemLabelGenerator;
    080    import org.jfree.chart.plot.CategoryPlot;
    081    import org.jfree.chart.plot.PlotOrientation;
    082    import org.jfree.data.category.CategoryDataset;
    083    import org.jfree.data.statistics.StatisticalCategoryDataset;
    084    import org.jfree.io.SerialUtilities;
    085    import org.jfree.ui.GradientPaintTransformer;
    086    import org.jfree.ui.RectangleEdge;
    087    import org.jfree.util.ObjectUtilities;
    088    import org.jfree.util.PaintUtilities;
    089    import 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     */
    100    public 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    }