    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     * StatisticalLineAndShapeRenderer.java
    029     * ------------------------------------
    030     * (C) Copyright 2005-2007, by Object Refinery Limited and Contributors.
    031     *
    032     * Original Author:  Mofeed Shahin;
    033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034     *
    035     * Changes
    036     * -------
    037     * 01-Feb-2005 : Version 1, contributed by Mofeed Shahin (DG);
    038     * 16-Jun-2005 : Added errorIndicatorPaint to be consistent with
    039     *               StatisticalBarRenderer (DG);
    040     * ------------- JFREECHART 1.0.x ---------------------------------------------
    041     * 11-Apr-2006 : Fixed bug 1468794, error bars drawn incorrectly when rendering
    042     *               plots with horizontal orientation (DG);
    043     * 25-Sep-2006 : Fixed bug 1562759, constructor ignoring arguments (DG);
    044     * 01-Jun-2007 : Return early from drawItem() method if item is not
    045     *               visible (DG);
    046     * 14-Jun-2007 : If the dataset is not a StatisticalCategoryDataset, revert
    047     *               to the drawing behaviour of LineAndShapeRenderer (DG);
    048     * 27-Sep-2007 : Added offset option to match new option in
    049     *               LineAndShapeRenderer (DG);
    050     *
    051     */
    052
    053    package org.jfree.chart.renderer.category;
    054
    055    import java.awt.Graphics2D;
    056    import java.awt.Paint;
    057    import java.awt.Shape;
    058    import java.awt.geom.Line2D;
    059    import java.awt.geom.Rectangle2D;
    060    import java.io.IOException;
    061    import java.io.ObjectInputStream;
    062    import java.io.ObjectOutputStream;
    063    import java.io.Serializable;
    064
    065    import org.jfree.chart.axis.CategoryAxis;
    066    import org.jfree.chart.axis.ValueAxis;
    067    import org.jfree.chart.entity.EntityCollection;
    068    import org.jfree.chart.event.RendererChangeEvent;
    069    import org.jfree.chart.plot.CategoryPlot;
    070    import org.jfree.chart.plot.PlotOrientation;
    071    import org.jfree.data.category.CategoryDataset;
    072    import org.jfree.data.statistics.StatisticalCategoryDataset;
    073    import org.jfree.io.SerialUtilities;
    074    import org.jfree.ui.RectangleEdge;
    075    import org.jfree.util.PaintUtilities;
    076    import org.jfree.util.PublicCloneable;
    077    import org.jfree.util.ShapeUtilities;
    078
    079    /**
    080     * A renderer that draws shapes for each data item, and lines between data
    081     * items.  Each point has a mean value and a standard deviation line. For use
    082     * with the {@link CategoryPlot} class.
    083     */
    084    public class StatisticalLineAndShapeRenderer extends LineAndShapeRenderer
    085            implements Cloneable, PublicCloneable, Serializable {
    086
    087        /** For serialization. */
    088        private static final long serialVersionUID = -3557517173697777579L;
    089
    090        /** The paint used to show the error indicator. */
    091        private transient Paint errorIndicatorPaint;
    092
    093        /**
    094         * Constructs a default renderer (draws shapes and lines).
    095         */
    096        public StatisticalLineAndShapeRenderer() {
    097            this(true, true);
    098        }
    099
    100        /**
    101         * Constructs a new renderer.
    102         *
    103         * @param linesVisible  draw lines?
    104         * @param shapesVisible  draw shapes?
    105         */
    106        public StatisticalLineAndShapeRenderer(boolean linesVisible,
    107                                               boolean shapesVisible) {
    108            super(linesVisible, shapesVisible);
    109            this.errorIndicatorPaint = null;
    110        }
    111
    112        /**
    113         * Returns the paint used for the error indicators.
    114         *
    115         * @return The paint used for the error indicators (possibly
    116         *         <code>null</code>).
    117         *
    118         * @see #setErrorIndicatorPaint(Paint)
    119         */
    120        public Paint getErrorIndicatorPaint() {
    121            return this.errorIndicatorPaint;
    122        }
    123
    124        /**
    125         * Sets the paint used for the error indicators (if <code>null</code>,
    126         * the item outline paint is used instead) and sends a
    127         * {@link RendererChangeEvent} to all registered listeners.
    128         *
    129         * @param paint  the paint (<code>null</code> permitted).
    130         *
    131         * @see #getErrorIndicatorPaint()
    132         */
    133        public void setErrorIndicatorPaint(Paint paint) {
    134            this.errorIndicatorPaint = paint;
    135            fireChangeEvent();
    136        }
    137
    138        /**
    139         * Draw a single data item.
    140         *
    141         * @param g2  the graphics device.
    142         * @param state  the renderer state.
    143         * @param dataArea  the area in which the data is drawn.
    144         * @param plot  the plot.
    145         * @param domainAxis  the domain axis.
    146         * @param rangeAxis  the range axis.
    147         * @param dataset  the dataset (a {@link StatisticalCategoryDataset} is
    148         *                 required).
    149         * @param row  the row index (zero-based).
    150         * @param column  the column index (zero-based).
    151         * @param pass  the pass.
    152         */
    153        public void drawItem(Graphics2D g2,
    154                             CategoryItemRendererState state,
    155                             Rectangle2D dataArea,
    156                             CategoryPlot plot,
    157                             CategoryAxis domainAxis,
    158                             ValueAxis rangeAxis,
    159                             CategoryDataset dataset,
    160                             int row,
    161                             int column,
    162                             int pass) {
    163
    164            // do nothing if item is not visible
    165            if (!getItemVisible(row, column)) {
    166                return;
    167            }
    168
    169            // nothing is drawn for null...
    170            Number v = dataset.getValue(row, column);
    171            if (v == null) {
    172                return;
    173            }
    174
    175            // if the dataset is not a StatisticalCategoryDataset then just revert
    176            // to the superclass (LineAndShapeRenderer) behaviour...
    177            if (!(dataset instanceof StatisticalCategoryDataset)) {
    178                super.drawItem(g2, state, dataArea, plot, domainAxis, rangeAxis,
    179                        dataset, row, column, pass);
    180                return;
    181            }
    182
    183            StatisticalCategoryDataset statData
    184                    = (StatisticalCategoryDataset) dataset;
    185
    186            Number meanValue = statData.getMeanValue(row, column);
    187
    188            PlotOrientation orientation = plot.getOrientation();
    189
    190            // current data point...
    191            double x1;
    192            if (getUseSeriesOffset()) {
    193                x1 = domainAxis.getCategorySeriesMiddle(dataset.getColumnKey(
    194                        column), dataset.getRowKey(row), dataset, getItemMargin(),
    195                        dataArea, plot.getDomainAxisEdge());
    196            }
    197            else {
    198                x1 = domainAxis.getCategoryMiddle(column, getColumnCount(),
    199                        dataArea, plot.getDomainAxisEdge());
    200            }
    201
    202            double y1 = rangeAxis.valueToJava2D(meanValue.doubleValue(), dataArea,
    203                    plot.getRangeAxisEdge());
    204
    205            Shape shape = getItemShape(row, column);
    206            if (orientation == PlotOrientation.HORIZONTAL) {
    207                shape = ShapeUtilities.createTranslatedShape(shape, y1, x1);
    208            }
    209            else if (orientation == PlotOrientation.VERTICAL) {
    210                shape = ShapeUtilities.createTranslatedShape(shape, x1, y1);
    211            }
    212            if (getItemShapeVisible(row, column)) {
    213
    214                if (getItemShapeFilled(row, column)) {
    215                    g2.setPaint(getItemPaint(row, column));
    216                    g2.fill(shape);
    217                }
    218                else {
    219                    if (getUseOutlinePaint()) {
    220                        g2.setPaint(getItemOutlinePaint(row, column));
    221                    }
    222                    else {
    223                        g2.setPaint(getItemPaint(row, column));
    224                    }
    225                    g2.setStroke(getItemOutlineStroke(row, column));
    226                    g2.draw(shape);
    227                }
    228            }
    229
    230            if (getItemLineVisible(row, column)) {
    231                if (column != 0) {
    232
    233                    Number previousValue = statData.getValue(row, column - 1);
    234                    if (previousValue != null) {
    235
    236                        // previous data point...
    237                        double previous = previousValue.doubleValue();
    238                        double x0;
    239                        if (getUseSeriesOffset()) {
    240                            x0 = domainAxis.getCategorySeriesMiddle(
    241                                    dataset.getColumnKey(column - 1),
    242                                    dataset.getRowKey(row), dataset,
    243                                    getItemMargin(), dataArea,
    244                                    plot.getDomainAxisEdge());
    245                        }
    246                        else {
    247                            x0 = domainAxis.getCategoryMiddle(column - 1,
    248                                    getColumnCount(), dataArea,
    249                                    plot.getDomainAxisEdge());
    250                        }
    251                        double y0 = rangeAxis.valueToJava2D(previous, dataArea,
    252                                plot.getRangeAxisEdge());
    253
    254                        Line2D line = null;
    255                        if (orientation == PlotOrientation.HORIZONTAL) {
    256                            line = new Line2D.Double(y0, x0, y1, x1);
    257                        }
    258                        else if (orientation == PlotOrientation.VERTICAL) {
    259                            line = new Line2D.Double(x0, y0, x1, y1);
    260                        }
    261                        g2.setPaint(getItemPaint(row, column));
    262                        g2.setStroke(getItemStroke(row, column));
    263                        g2.draw(line);
    264                    }
    265                }
    266            }
    267
    268            RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
    269            g2.setPaint(getItemPaint(row, column));
    270
    271            //standard deviation lines
    272            double valueDelta = statData.getStdDevValue(row, column).doubleValue();
    273
    274            double highVal, lowVal;
    275            if ((meanValue.doubleValue() + valueDelta)
    276                    > rangeAxis.getRange().getUpperBound()) {
    277                highVal = rangeAxis.valueToJava2D(
    278                        rangeAxis.getRange().getUpperBound(), dataArea,
    279                        yAxisLocation);
    280            }
    281            else {
    282                highVal = rangeAxis.valueToJava2D(meanValue.doubleValue()
    283                        + valueDelta, dataArea, yAxisLocation);
    284            }
    285
    286            if ((meanValue.doubleValue() + valueDelta)
    287                    < rangeAxis.getRange().getLowerBound()) {
    288                lowVal = rangeAxis.valueToJava2D(
    289                        rangeAxis.getRange().getLowerBound(), dataArea,
    290                        yAxisLocation);
    291            }
    292            else {
    293                lowVal = rangeAxis.valueToJava2D(meanValue.doubleValue()
    294                        - valueDelta, dataArea, yAxisLocation);
    295            }
    296
    297            if (this.errorIndicatorPaint != null) {
    298                g2.setPaint(this.errorIndicatorPaint);
    299            }
    300            else {
    301                g2.setPaint(getItemPaint(row, column));
    302            }
    303            Line2D line = new Line2D.Double();
    304            if (orientation == PlotOrientation.HORIZONTAL) {
    305                line.setLine(lowVal, x1, highVal, x1);
    306                g2.draw(line);
    307                line.setLine(lowVal, x1 - 5.0d, lowVal, x1 + 5.0d);
    308                g2.draw(line);
    309                line.setLine(highVal, x1 - 5.0d, highVal, x1 + 5.0d);
    310                g2.draw(line);
    311            }
    312            else {  // PlotOrientation.VERTICAL
    313                line.setLine(x1, lowVal, x1, highVal);
    314                g2.draw(line);
    315                line.setLine(x1 - 5.0d, highVal, x1 + 5.0d, highVal);
    316                g2.draw(line);
    317                line.setLine(x1 - 5.0d, lowVal, x1 + 5.0d, lowVal);
    318                g2.draw(line);
    319            }
    320
    321            // draw the item label if there is one...
    322            if (isItemLabelVisible(row, column)) {
    323                if (orientation == PlotOrientation.HORIZONTAL) {
    324                    drawItemLabel(g2, orientation, dataset, row, column,
    325                            y1, x1, (meanValue.doubleValue() < 0.0));
    326                }
    327                else if (orientation == PlotOrientation.VERTICAL) {
    328                    drawItemLabel(g2, orientation, dataset, row, column,
    329                            x1, y1, (meanValue.doubleValue() < 0.0));
    330                }
    331            }
    332
    333            // add an item entity, if this information is being collected
    334            EntityCollection entities = state.getEntityCollection();
    335            if (entities != null && shape != null) {
    336                addItemEntity(entities, dataset, row, column, shape);
    337            }
    338
    339        }
    340
    341        /**
    342         * Tests this renderer for equality with an arbitrary object.
    343         *
    344         * @param obj  the object (<code>null</code> permitted).
    345         *
    346         * @return A boolean.
    347         */
    348        public boolean equals(Object obj) {
    349            if (obj == this) {
    350                return true;
    351            }
    352            if (!(obj instanceof StatisticalLineAndShapeRenderer)) {
    353                return false;
    354            }
    355            StatisticalLineAndShapeRenderer that
    356                    = (StatisticalLineAndShapeRenderer) obj;
    357            if (!PaintUtilities.equal(this.errorIndicatorPaint,
    358                    that.errorIndicatorPaint)) {
    359                return false;
    360            }
    361            return super.equals(obj);
    362        }
    363
    364        /**
    365         * Provides serialization support.
    366         *
    367         * @param stream  the output stream.
    368         *
    369         * @throws IOException  if there is an I/O error.
    370         */
    371        private void writeObject(ObjectOutputStream stream) throws IOException {
    372            stream.defaultWriteObject();
    373            SerialUtilities.writePaint(this.errorIndicatorPaint, stream);
    374        }
    375
    376        /**
    377         * Provides serialization support.
    378         *
    379         * @param stream  the input stream.
    380         *
    381         * @throws IOException  if there is an I/O error.
    382         * @throws ClassNotFoundException  if there is a classpath problem.
    383         */
    384        private void readObject(ObjectInputStream stream)
    385                throws IOException, ClassNotFoundException {
    386            stream.defaultReadObject();
    387            this.errorIndicatorPaint = SerialUtilities.readPaint(stream);
    388        }
    389
    390    }