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