    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
    006     *
    007     * Project Info:  http://www.jfree.org/jfreechart/index.html
    008     *
    009     * This library is free software; you can redistribute it and/or modify it
    010     * under the terms of the GNU Lesser General Public License as published by
    011     * the Free Software Foundation; either version 2.1 of the License, or
    012     * (at your option) any later version.
    013     *
    014     * This library is distributed in the hope that it will be useful, but
    015     * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
    016     * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
    017     * License for more details.
    018     *
    019     * You should have received a copy of the GNU Lesser General Public
    020     * License along with this library; if not, write to the Free Software
    021     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
    022     * USA.
    023     *
    024     * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
    025     * in the United States and other countries.]
    026     *
    027     * ---------------------------
    028     * StatisticalBarRenderer.java
    029     * ---------------------------
    030     * (C) Copyright 2002-2007, by Pascal Collet and Contributors.
    031     *
    032     * Original Author:  Pascal Collet;
    033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034     *                   Christian W. Zuckschwerdt;
    035     *
    036     * 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,
    095                                                   Cloneable, PublicCloneable,
    096                                                   Serializable {
    097
    098        /** For serialization. */
    099        private static final long serialVersionUID = -4986038395414039117L;
    100
    101        /** The paint used to show the error indicator. */
    102        private transient Paint errorIndicatorPaint;
    103
    104        /**
    105         * The stroke used to draw the error indicators.
    106         *
    107         * @since 1.0.8
    108         */
    109        private transient Stroke errorIndicatorStroke;
    110
    111        /**
    112         * Default constructor.
    113         */
    114        public StatisticalBarRenderer() {
    115            super();
    116            this.errorIndicatorPaint = Color.gray;
    117            this.errorIndicatorStroke = new BasicStroke(1.0f);
    118        }
    119
    120        /**
    121         * Returns the paint used for the error indicators.
    122         *
    123         * @return The paint used for the error indicators (possibly
    124         *         <code>null</code>).
    125         *
    126         * @see #setErrorIndicatorPaint(Paint)
    127         */
    128        public Paint getErrorIndicatorPaint() {
    129            return this.errorIndicatorPaint;
    130        }
    131
    132        /**
    133         * Sets the paint used for the error indicators (if <code>null</code>,
    134         * the item outline paint is used instead) and sends a
    135         * {@link RendererChangeEvent} to all registered listeners.
    136         *
    137         * @param paint  the paint (<code>null</code> permitted).
    138         *
    139         * @see #getErrorIndicatorPaint()
    140         */
    141        public void setErrorIndicatorPaint(Paint paint) {
    142            this.errorIndicatorPaint = paint;
    143            fireChangeEvent();
    144        }
    145
    146        /**
    147         * Returns the stroke used to draw the error indicators.  If this is
    148         * <code>null</code>, the renderer will use the item outline stroke).
    149         *
    150         * @return The stroke (possibly <code>null</code>).
    151         *
    152         * @see #setErrorIndicatorStroke(Stroke)
    153         *
    154         * @since 1.0.8
    155         */
    156        public Stroke getErrorIndicatorStroke() {
    157            return this.errorIndicatorStroke;
    158        }
    159
    160        /**
    161         * Sets the stroke used to draw the error indicators, and sends a
    162         * {@link RendererChangeEvent} to all registered listeners.  If you set
    163         * this to <code>null</code>, the renderer will use the item outline
    164         * stroke.
    165         *
    166         * @param stroke  the stroke (<code>null</code> permitted).
    167         *
    168         * @see #getErrorIndicatorStroke()
    169         *
    170         * @since 1.0.8
    171         */
    172        public void setErrorIndicatorStroke(Stroke stroke) {
    173            this.errorIndicatorStroke = stroke;
    174            fireChangeEvent();
    175        }
    176
    177        /**
    178         * Draws the bar with its standard deviation line range for a single
    179         * (series, category) data item.
    180         *
    181         * @param g2  the graphics device.
    182         * @param state  the renderer state.
    183         * @param dataArea  the data area.
    184         * @param plot  the plot.
    185         * @param domainAxis  the domain axis.
    186         * @param rangeAxis  the range axis.
    187         * @param data  the data.
    188         * @param row  the row index (zero-based).
    189         * @param column  the column index (zero-based).
    190         * @param pass  the pass index.
    191         */
    192        public void drawItem(Graphics2D g2,
    193                             CategoryItemRendererState state,
    194                             Rectangle2D dataArea,
    195                             CategoryPlot plot,
    196                             CategoryAxis domainAxis,
    197                             ValueAxis rangeAxis,
    198                             CategoryDataset data,
    199                             int row,
    200                             int column,
    201                             int pass) {
    202
    203            // defensive check
    204            if (!(data instanceof StatisticalCategoryDataset)) {
    205                throw new IllegalArgumentException(
    206                    "Requires StatisticalCategoryDataset.");
    207            }
    208            StatisticalCategoryDataset statData = (StatisticalCategoryDataset) data;
    209
    210            PlotOrientation orientation = plot.getOrientation();
    211            if (orientation == PlotOrientation.HORIZONTAL) {
    212                drawHorizontalItem(g2, state, dataArea, plot, domainAxis,
    213                        rangeAxis, statData, row, column);
    214            }
    215            else if (orientation == PlotOrientation.VERTICAL) {
    216                drawVerticalItem(g2, state, dataArea, plot, domainAxis, rangeAxis,
    217                        statData, row, column);
    218            }
    219        }
    220
    221        /**
    222         * Draws an item for a plot with a horizontal orientation.
    223         *
    224         * @param g2  the graphics device.
    225         * @param state  the renderer state.
    226         * @param dataArea  the data area.
    227         * @param plot  the plot.
    228         * @param domainAxis  the domain axis.
    229         * @param rangeAxis  the range axis.
    230         * @param dataset  the data.
    231         * @param row  the row index (zero-based).
    232         * @param column  the column index (zero-based).
    233         */
    234        protected void drawHorizontalItem(Graphics2D g2,
    235                                          CategoryItemRendererState state,
    236                                          Rectangle2D dataArea,
    237                                          CategoryPlot plot,
    238                                          CategoryAxis domainAxis,
    239                                          ValueAxis rangeAxis,
    240                                          StatisticalCategoryDataset dataset,
    241                                          int row,
    242                                          int column) {
    243
    244            RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
    245
    246            // BAR Y
    247            double rectY = domainAxis.getCategoryStart(column, getColumnCount(),
    248                    dataArea, xAxisLocation);
    249
    250            int seriesCount = getRowCount();
    251            int categoryCount = getColumnCount();
    252            if (seriesCount > 1) {
    253                double seriesGap = dataArea.getHeight() * getItemMargin()
    254                                   / (categoryCount * (seriesCount - 1));
    255                rectY = rectY + row * (state.getBarWidth() + seriesGap);
    256            }
    257            else {
    258                rectY = rectY + row * state.getBarWidth();
    259            }
    260
    261            // BAR X
    262            Number meanValue = dataset.getMeanValue(row, column);
    263            if (meanValue == null) {
    264                return;
    265            }
    266            double value = meanValue.doubleValue();
    267            double base = 0.0;
    268            double lclip = getLowerClip();
    269            double uclip = getUpperClip();
    270
    271            if (uclip <= 0.0) {  // cases 1, 2, 3 and 4
    272                if (value >= uclip) {
    273                    return; // bar is not visible
    274                }
    275                base = uclip;
    276                if (value <= lclip) {
    277                    value = lclip;
    278                }
    279            }
    280            else if (lclip <= 0.0) { // cases 5, 6, 7 and 8
    281                if (value >= uclip) {
    282                    value = uclip;
    283                }
    284                else {
    285                    if (value <= lclip) {
    286                        value = lclip;
    287                    }
    288                }
    289            }
    290            else { // cases 9, 10, 11 and 12
    291                if (value <= lclip) {
    292                    return; // bar is not visible
    293                }
    294                base = getLowerClip();
    295                if (value >= uclip) {
    296                   value = uclip;
    297                }
    298            }
    299
    300            RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
    301            double transY1 = rangeAxis.valueToJava2D(base, dataArea, yAxisLocation);
    302            double transY2 = rangeAxis.valueToJava2D(value, dataArea,
    303                    yAxisLocation);
    304            double rectX = Math.min(transY2, transY1);
    305
    306            double rectHeight = state.getBarWidth();
    307            double rectWidth = Math.abs(transY2 - transY1);
    308
    309            Rectangle2D bar = new Rectangle2D.Double(rectX, rectY, rectWidth,
    310                    rectHeight);
    311            Paint itemPaint = getItemPaint(row, column);
    312            GradientPaintTransformer t = getGradientPaintTransformer();
    313            if (t != null && itemPaint instanceof GradientPaint) {
    314                itemPaint = t.transform((GradientPaint) itemPaint, bar);
    315            }
    316            g2.setPaint(itemPaint);
    317            g2.fill(bar);
    318
    319            // draw the outline...
    320            if (isDrawBarOutline()
    321                    && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
    322                Stroke stroke = getItemOutlineStroke(row, column);
    323                Paint paint = getItemOutlinePaint(row, column);
    324                if (stroke != null && paint != null) {
    325                    g2.setStroke(stroke);
    326                    g2.setPaint(paint);
    327                    g2.draw(bar);
    328                }
    329            }
    330
    331            // standard deviation lines
    332            Number n = dataset.getStdDevValue(row, column);
    333            if (n != null) {
    334                double valueDelta = n.doubleValue();
    335                double highVal = rangeAxis.valueToJava2D(meanValue.doubleValue()
    336                        + valueDelta, dataArea, yAxisLocation);
    337                double lowVal = rangeAxis.valueToJava2D(meanValue.doubleValue()
    338                        - valueDelta, dataArea, yAxisLocation);
    339
    340                if (this.errorIndicatorPaint != null) {
    341                    g2.setPaint(this.errorIndicatorPaint);
    342                }
    343                else {
    344                    g2.setPaint(getItemOutlinePaint(row, column));
    345                }
    346                if (this.errorIndicatorStroke != null) {
    347                    g2.setStroke(this.errorIndicatorStroke);
    348                }
    349                else {
    350                    g2.setStroke(getItemOutlineStroke(row, column));
    351                }
    352                Line2D line = null;
    353                line = new Line2D.Double(lowVal, rectY + rectHeight / 2.0d,
    354                                         highVal, rectY + rectHeight / 2.0d);
    355                g2.draw(line);
    356                line = new Line2D.Double(highVal, rectY + rectHeight * 0.25,
    357                                         highVal, rectY + rectHeight * 0.75);
    358                g2.draw(line);
    359                line = new Line2D.Double(lowVal, rectY + rectHeight * 0.25,
    360                                         lowVal, rectY + rectHeight * 0.75);
    361                g2.draw(line);
    362            }
    363
    364            CategoryItemLabelGenerator generator = getItemLabelGenerator(row,
    365                    column);
    366            if (generator != null && isItemLabelVisible(row, column)) {
    367                drawItemLabel(g2, dataset, row, column, plot, generator, bar,
    368                        (value < 0.0));
    369            }
    370
    371            // add an item entity, if this information is being collected
    372            EntityCollection entities = state.getEntityCollection();
    373            if (entities != null) {
    374                addItemEntity(entities, dataset, row, column, bar);
    375            }
    376
    377        }
    378
    379        /**
    380         * Draws an item for a plot with a vertical orientation.
    381         *
    382         * @param g2  the graphics device.
    383         * @param state  the renderer state.
    384         * @param dataArea  the data area.
    385         * @param plot  the plot.
    386         * @param domainAxis  the domain axis.
    387         * @param rangeAxis  the range axis.
    388         * @param dataset  the data.
    389         * @param row  the row index (zero-based).
    390         * @param column  the column index (zero-based).
    391         */
    392        protected void drawVerticalItem(Graphics2D g2,
    393                                        CategoryItemRendererState state,
    394                                        Rectangle2D dataArea,
    395                                        CategoryPlot plot,
    396                                        CategoryAxis domainAxis,
    397                                        ValueAxis rangeAxis,
    398                                        StatisticalCategoryDataset dataset,
    399                                        int row,
    400                                        int column) {
    401
    402            RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
    403
    404            // BAR X
    405            double rectX = domainAxis.getCategoryStart(column, getColumnCount(),
    406                    dataArea, xAxisLocation);
    407
    408            int seriesCount = getRowCount();
    409            int categoryCount = getColumnCount();
    410            if (seriesCount > 1) {
    411                double seriesGap = dataArea.getWidth() * getItemMargin()
    412                                   / (categoryCount * (seriesCount - 1));
    413                rectX = rectX + row * (state.getBarWidth() + seriesGap);
    414            }
    415            else {
    416                rectX = rectX + row * state.getBarWidth();
    417            }
    418
    419            // BAR Y
    420            Number meanValue = dataset.getMeanValue(row, column);
    421            if (meanValue == null) {
    422                return;
    423            }
    424
    425            double value = meanValue.doubleValue();
    426            double base = 0.0;
    427            double lclip = getLowerClip();
    428            double uclip = getUpperClip();
    429
    430            if (uclip <= 0.0) {  // cases 1, 2, 3 and 4
    431                if (value >= uclip) {
    432                    return; // bar is not visible
    433                }
    434                base = uclip;
    435                if (value <= lclip) {
    436                    value = lclip;
    437                }
    438            }
    439            else if (lclip <= 0.0) { // cases 5, 6, 7 and 8
    440                if (value >= uclip) {
    441                    value = uclip;
    442                }
    443                else {
    444                    if (value <= lclip) {
    445                        value = lclip;
    446                    }
    447                }
    448            }
    449            else { // cases 9, 10, 11 and 12
    450                if (value <= lclip) {
    451                    return; // bar is not visible
    452                }
    453                base = getLowerClip();
    454                if (value >= uclip) {
    455                   value = uclip;
    456                }
    457            }
    458
    459            RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
    460            double transY1 = rangeAxis.valueToJava2D(base, dataArea, yAxisLocation);
    461            double transY2 = rangeAxis.valueToJava2D(value, dataArea,
    462                    yAxisLocation);
    463            double rectY = Math.min(transY2, transY1);
    464
    465            double rectWidth = state.getBarWidth();
    466            double rectHeight = Math.abs(transY2 - transY1);
    467
    468            Rectangle2D bar = new Rectangle2D.Double(rectX, rectY, rectWidth,
    469                    rectHeight);
    470            Paint itemPaint = getItemPaint(row, column);
    471            GradientPaintTransformer t = getGradientPaintTransformer();
    472            if (t != null && itemPaint instanceof GradientPaint) {
    473                itemPaint = t.transform((GradientPaint) itemPaint, bar);
    474            }
    475            g2.setPaint(itemPaint);
    476            g2.fill(bar);
    477            // draw the outline...
    478            if (isDrawBarOutline()
    479                    && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
    480                Stroke stroke = getItemOutlineStroke(row, column);
    481                Paint paint = getItemOutlinePaint(row, column);
    482                if (stroke != null && paint != null) {
    483                    g2.setStroke(stroke);
    484                    g2.setPaint(paint);
    485                    g2.draw(bar);
    486                }
    487            }
    488
    489            // standard deviation lines
    490            Number n = dataset.getStdDevValue(row, column);
    491            if (n != null) {
    492                double valueDelta = n.doubleValue();
    493                double highVal = rangeAxis.valueToJava2D(meanValue.doubleValue()
    494                        + valueDelta, dataArea, yAxisLocation);
    495                double lowVal = rangeAxis.valueToJava2D(meanValue.doubleValue()
    496                        - valueDelta, dataArea, yAxisLocation);
    497
    498                if (this.errorIndicatorPaint != null) {
    499                    g2.setPaint(this.errorIndicatorPaint);
    500                }
    501                else {
    502                    g2.setPaint(getItemOutlinePaint(row, column));
    503                }
    504                if (this.errorIndicatorStroke != null) {
    505                    g2.setStroke(this.errorIndicatorStroke);
    506                }
    507                else {
    508                    g2.setStroke(getItemOutlineStroke(row, column));
    509                }
    510
    511                Line2D line = null;
    512                line = new Line2D.Double(rectX + rectWidth / 2.0d, lowVal,
    513                                         rectX + rectWidth / 2.0d, highVal);
    514                g2.draw(line);
    515                line = new Line2D.Double(rectX + rectWidth / 2.0d - 5.0d, highVal,
    516                                         rectX + rectWidth / 2.0d + 5.0d, highVal);
    517                g2.draw(line);
    518                line = new Line2D.Double(rectX + rectWidth / 2.0d - 5.0d, lowVal,
    519                                         rectX + rectWidth / 2.0d + 5.0d, lowVal);
    520                g2.draw(line);
    521            }
    522
    523            CategoryItemLabelGenerator generator = getItemLabelGenerator(row,
    524                    column);
    525            if (generator != null && isItemLabelVisible(row, column)) {
    526                drawItemLabel(g2, dataset, row, column, plot, generator, bar,
    527                        (value < 0.0));
    528            }
    529
    530            // add an item entity, if this information is being collected
    531            EntityCollection entities = state.getEntityCollection();
    532            if (entities != null) {
    533                addItemEntity(entities, dataset, row, column, bar);
    534            }
    535        }
    536
    537        /**
    538         * Tests this renderer for equality with an arbitrary object.
    539         *
    540         * @param obj  the object (<code>null</code> permitted).
    541         *
    542         * @return A boolean.
    543         */
    544        public boolean equals(Object obj) {
    545            if (obj == this) {
    546                return true;
    547            }
    548            if (!(obj instanceof StatisticalBarRenderer)) {
    549                return false;
    550            }
    551            StatisticalBarRenderer that = (StatisticalBarRenderer) obj;
    552            if (!PaintUtilities.equal(this.errorIndicatorPaint,
    553                    that.errorIndicatorPaint)) {
    554                return false;
    555            }
    556            if (!ObjectUtilities.equal(this.errorIndicatorStroke,
    557                    that.errorIndicatorStroke)) {
    558                return false;
    559            }
    560            return super.equals(obj);
    561        }
    562
    563        /**
    564         * Provides serialization support.
    565         *
    566         * @param stream  the output stream.
    567         *
    568         * @throws IOException  if there is an I/O error.
    569         */
    570        private void writeObject(ObjectOutputStream stream) throws IOException {
    571            stream.defaultWriteObject();
    572            SerialUtilities.writePaint(this.errorIndicatorPaint, stream);
    573            SerialUtilities.writeStroke(this.errorIndicatorStroke, stream);
    574        }
    575
    576        /**
    577         * Provides serialization support.
    578         *
    579         * @param stream  the input stream.
    580         *
    581         * @throws IOException  if there is an I/O error.
    582         * @throws ClassNotFoundException  if there is a classpath problem.
    583         */
    584        private void readObject(ObjectInputStream stream)
    585            throws IOException, ClassNotFoundException {
    586            stream.defaultReadObject();
    587            this.errorIndicatorPaint = SerialUtilities.readPaint(stream);
    588            this.errorIndicatorStroke = SerialUtilities.readStroke(stream);
    589        }
    590
    591    }