    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 * StatisticalLineAndShapeRenderer.java
    029 * ------------------------------------
    030 * (C) Copyright 2005-2009, by Object Refinery Limited and Contributors.
    031 *
    032 * Original Author:  Mofeed Shahin;
    033 * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034 *                   Peter Kolb (patch 2497611);
    035 *
    036 * Changes
    037 * -------
    038 * 01-Feb-2005 : Version 1, contributed by Mofeed Shahin (DG);
    039 * 16-Jun-2005 : Added errorIndicatorPaint to be consistent with
    040 *               StatisticalBarRenderer (DG);
    041 * ------------- JFREECHART 1.0.x ---------------------------------------------
    042 * 11-Apr-2006 : Fixed bug 1468794, error bars drawn incorrectly when rendering
    043 *               plots with horizontal orientation (DG);
    044 * 25-Sep-2006 : Fixed bug 1562759, constructor ignoring arguments (DG);
    045 * 01-Jun-2007 : Return early from drawItem() method if item is not
    046 *               visible (DG);
    047 * 14-Jun-2007 : If the dataset is not a StatisticalCategoryDataset, revert
    048 *               to the drawing behaviour of LineAndShapeRenderer (DG);
    049 * 27-Sep-2007 : Added offset option to match new option in
    050 *               LineAndShapeRenderer (DG);
    051 * 14-Jan-2009 : Added support for seriesVisible flags (PK);
    052 * 23-Jan-2009 : Observe useFillPaint and drawOutlines flags (PK);
    053 * 23-Jan-2009 : In drawItem, divide code into passes (DG);
    054 * 05-Feb-2009 : Added errorIndicatorStroke field (DG);
    055 * 01-Apr-2009 : Added override for findRangeBounds(), and fixed NPE in
    056 *               creating item entities (DG);
    057 */
    058
    059package org.jfree.chart.renderer.category;
    060
    061import java.awt.Graphics2D;
    062import java.awt.Paint;
    063import java.awt.Shape;
    064import java.awt.Stroke;
    065import java.awt.geom.Line2D;
    066import java.awt.geom.Rectangle2D;
    067import java.io.IOException;
    068import java.io.ObjectInputStream;
    069import java.io.ObjectOutputStream;
    070import java.io.Serializable;
    071
    072import org.jfree.chart.HashUtilities;
    073import org.jfree.chart.axis.CategoryAxis;
    074import org.jfree.chart.axis.ValueAxis;
    075import org.jfree.chart.entity.EntityCollection;
    076import org.jfree.chart.event.RendererChangeEvent;
    077import org.jfree.chart.plot.CategoryPlot;
    078import org.jfree.chart.plot.PlotOrientation;
    079import org.jfree.data.Range;
    080import org.jfree.data.category.CategoryDataset;
    081import org.jfree.data.statistics.StatisticalCategoryDataset;
    082import org.jfree.io.SerialUtilities;
    083import org.jfree.ui.RectangleEdge;
    084import org.jfree.util.ObjectUtilities;
    085import org.jfree.util.PaintUtilities;
    086import org.jfree.util.PublicCloneable;
    087import org.jfree.util.ShapeUtilities;
    088
    089/**
    090 * A renderer that draws shapes for each data item, and lines between data
    091 * items.  Each point has a mean value and a standard deviation line. For use
    092 * with the {@link CategoryPlot} class.  The example shown
    093 * here is generated by the <code>StatisticalLineChartDemo1.java</code> program
    094 * included in the JFreeChart Demo Collection:
    095 * <br><br>
    096 * <img src="../../../../../images/StatisticalLineRendererSample.png"
    097 * alt="StatisticalLineRendererSample.png" />
    098 */
    099public class StatisticalLineAndShapeRenderer extends LineAndShapeRenderer
    100        implements Cloneable, PublicCloneable, Serializable {
    101
    102    /** For serialization. */
    103    private static final long serialVersionUID = -3557517173697777579L;
    104
    105    /** The paint used to show the error indicator. */
    106    private transient Paint errorIndicatorPaint;
    107
    108    /**
    109     * The stroke used to draw the error indicators.  If null, the renderer
    110     * will use the itemOutlineStroke.
    111     *
    112     * @since 1.0.13
    113     */
    114    private transient Stroke errorIndicatorStroke;
    115
    116    /**
    117     * Constructs a default renderer (draws shapes and lines).
    118     */
    119    public StatisticalLineAndShapeRenderer() {
    120        this(true, true);
    121    }
    122
    123    /**
    124     * Constructs a new renderer.
    125     *
    126     * @param linesVisible  draw lines?
    127     * @param shapesVisible  draw shapes?
    128     */
    129    public StatisticalLineAndShapeRenderer(boolean linesVisible,
    130                                           boolean shapesVisible) {
    131        super(linesVisible, shapesVisible);
    132        this.errorIndicatorPaint = null;
    133        this.errorIndicatorStroke = null;
    134    }
    135
    136    /**
    137     * Returns the paint used for the error indicators.
    138     *
    139     * @return The paint used for the error indicators (possibly
    140     *         <code>null</code>).
    141     *
    142     * @see #setErrorIndicatorPaint(Paint)
    143     */
    144    public Paint getErrorIndicatorPaint() {
    145        return this.errorIndicatorPaint;
    146    }
    147
    148    /**
    149     * Sets the paint used for the error indicators (if <code>null</code>,
    150     * the item paint is used instead) and sends a
    151     * {@link RendererChangeEvent} to all registered listeners.
    152     *
    153     * @param paint  the paint (<code>null</code> permitted).
    154     *
    155     * @see #getErrorIndicatorPaint()
    156     */
    157    public void setErrorIndicatorPaint(Paint paint) {
    158        this.errorIndicatorPaint = paint;
    159        fireChangeEvent();
    160    }
    161
    162    /**
    163     * Returns the stroke used for the error indicators.
    164     *
    165     * @return The stroke used for the error indicators (possibly
    166     *         <code>null</code>).
    167     *
    168     * @see #setErrorIndicatorStroke(Stroke)
    169     *
    170     * @since 1.0.13
    171     */
    172    public Stroke getErrorIndicatorStroke() {
    173        return this.errorIndicatorStroke;
    174    }
    175
    176    /**
    177     * Sets the stroke used for the error indicators (if <code>null</code>,
    178     * the item outline stroke is used instead) and sends a
    179     * {@link RendererChangeEvent} to all registered listeners.
    180     *
    181     * @param stroke  the stroke (<code>null</code> permitted).
    182     *
    183     * @see #getErrorIndicatorStroke()
    184     *
    185     * @since 1.0.13
    186     */
    187    public void setErrorIndicatorStroke(Stroke stroke) {
    188        this.errorIndicatorStroke = stroke;
    189        fireChangeEvent();
    190    }
    191
    192    /**
    193     * Returns the range of values the renderer requires to display all the
    194     * items from the specified dataset.
    195     *
    196     * @param dataset  the dataset (<code>null</code> permitted).
    197     *
    198     * @return The range (or <code>null</code> if the dataset is
    199     *         <code>null</code> or empty).
    200     */
    201    public Range findRangeBounds(CategoryDataset dataset) {
    202        return findRangeBounds(dataset, true);
    203    }
    204
    205    /**
    206     * Draw a single data item.
    207     *
    208     * @param g2  the graphics device.
    209     * @param state  the renderer state.
    210     * @param dataArea  the area in which the data is drawn.
    211     * @param plot  the plot.
    212     * @param domainAxis  the domain axis.
    213     * @param rangeAxis  the range axis.
    214     * @param dataset  the dataset (a {@link StatisticalCategoryDataset} is
    215     *                 required).
    216     * @param row  the row index (zero-based).
    217     * @param column  the column index (zero-based).
    218     * @param pass  the pass.
    219     */
    220    public void drawItem(Graphics2D g2,
    221                         CategoryItemRendererState state,
    222                         Rectangle2D dataArea,
    223                         CategoryPlot plot,
    224                         CategoryAxis domainAxis,
    225                         ValueAxis rangeAxis,
    226                         CategoryDataset dataset,
    227                         int row,
    228                         int column,
    229                         int pass) {
    230
    231        // do nothing if item is not visible
    232        if (!getItemVisible(row, column)) {
    233            return;
    234        }
    235
    236        // if the dataset is not a StatisticalCategoryDataset then just revert
    237        // to the superclass (LineAndShapeRenderer) behaviour...
    238        if (!(dataset instanceof StatisticalCategoryDataset)) {
    239            super.drawItem(g2, state, dataArea, plot, domainAxis, rangeAxis,
    240                    dataset, row, column, pass);
    241            return;
    242        }
    243
    244        int visibleRow = state.getVisibleSeriesIndex(row);
    245        if (visibleRow < 0) {
    246            return;
    247        }
    248        int visibleRowCount = state.getVisibleSeriesCount();
    249
    250        StatisticalCategoryDataset statDataset
    251                = (StatisticalCategoryDataset) dataset;
    252        Number meanValue = statDataset.getMeanValue(row, column);
    253        if (meanValue == null) {
    254            return;
    255        }
    256        PlotOrientation orientation = plot.getOrientation();
    257
    258        // current data point...
    259        double x1;
    260        if (getUseSeriesOffset()) {
    261            x1 = domainAxis.getCategorySeriesMiddle(column,
    262                    dataset.getColumnCount(),
    263                    visibleRow, visibleRowCount,
    264                    getItemMargin(), dataArea, plot.getDomainAxisEdge());
    265        }
    266        else {
    267            x1 = domainAxis.getCategoryMiddle(column, getColumnCount(),
    268                    dataArea, plot.getDomainAxisEdge());
    269        }
    270        double y1 = rangeAxis.valueToJava2D(meanValue.doubleValue(), dataArea,
    271                plot.getRangeAxisEdge());
    272
    273        // draw the standard deviation lines *before* the shapes (if they're
    274        // visible) - it looks better if the shape fill colour is different to
    275        // the line colour
    276        Number sdv = statDataset.getStdDevValue(row, column);
    277        if (pass == 1 && sdv != null) {
    278            //standard deviation lines
    279            RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
    280            double valueDelta = sdv.doubleValue();
    281            double highVal, lowVal;
    282            if ((meanValue.doubleValue() + valueDelta)
    283                    > rangeAxis.getRange().getUpperBound()) {
    284                highVal = rangeAxis.valueToJava2D(
    285                        rangeAxis.getRange().getUpperBound(), dataArea,
    286                        yAxisLocation);
    287            }
    288            else {
    289                highVal = rangeAxis.valueToJava2D(meanValue.doubleValue()
    290                        + valueDelta, dataArea, yAxisLocation);
    291            }
    292
    293            if ((meanValue.doubleValue() + valueDelta)
    294                    < rangeAxis.getRange().getLowerBound()) {
    295                lowVal = rangeAxis.valueToJava2D(
    296                        rangeAxis.getRange().getLowerBound(), dataArea,
    297                        yAxisLocation);
    298            }
    299            else {
    300                lowVal = rangeAxis.valueToJava2D(meanValue.doubleValue()
    301                        - valueDelta, dataArea, yAxisLocation);
    302            }
    303
    304            if (this.errorIndicatorPaint != null) {
    305                g2.setPaint(this.errorIndicatorPaint);
    306            }
    307            else {
    308                g2.setPaint(getItemPaint(row, column));
    309            }
    310            if (this.errorIndicatorStroke != null) {
    311                g2.setStroke(this.errorIndicatorStroke);
    312            }
    313            else {
    314                g2.setStroke(getItemOutlineStroke(row, column));
    315            }
    316            Line2D line = new Line2D.Double();
    317            if (orientation == PlotOrientation.HORIZONTAL) {
    318                line.setLine(lowVal, x1, highVal, x1);
    319                g2.draw(line);
    320                line.setLine(lowVal, x1 - 5.0d, lowVal, x1 + 5.0d);
    321                g2.draw(line);
    322                line.setLine(highVal, x1 - 5.0d, highVal, x1 + 5.0d);
    323                g2.draw(line);
    324            }
    325            else {  // PlotOrientation.VERTICAL
    326                line.setLine(x1, lowVal, x1, highVal);
    327                g2.draw(line);
    328                line.setLine(x1 - 5.0d, highVal, x1 + 5.0d, highVal);
    329                g2.draw(line);
    330                line.setLine(x1 - 5.0d, lowVal, x1 + 5.0d, lowVal);
    331                g2.draw(line);
    332            }
    333
    334        }
    335
    336        Shape hotspot = null;
    337        if (pass == 1 && getItemShapeVisible(row, column)) {
    338            Shape shape = getItemShape(row, column);
    339            if (orientation == PlotOrientation.HORIZONTAL) {
    340                shape = ShapeUtilities.createTranslatedShape(shape, y1, x1);
    341            }
    342            else if (orientation == PlotOrientation.VERTICAL) {
    343                shape = ShapeUtilities.createTranslatedShape(shape, x1, y1);
    344            }
    345            hotspot = shape;
    346
    347            if (getItemShapeFilled(row, column)) {
    348                if (getUseFillPaint()) {
    349                    g2.setPaint(getItemFillPaint(row, column));
    350                }
    351                else {
    352                    g2.setPaint(getItemPaint(row, column));
    353                }
    354                g2.fill(shape);
    355            }
    356            if (getDrawOutlines()) {
    357                if (getUseOutlinePaint()) {
    358                    g2.setPaint(getItemOutlinePaint(row, column));
    359                }
    360                else {
    361                    g2.setPaint(getItemPaint(row, column));
    362                }
    363                g2.setStroke(getItemOutlineStroke(row, column));
    364                g2.draw(shape);
    365            }
    366            // draw the item label if there is one...
    367            if (isItemLabelVisible(row, column)) {
    368                if (orientation == PlotOrientation.HORIZONTAL) {
    369                    drawItemLabel(g2, orientation, dataset, row, column,
    370                            y1, x1, (meanValue.doubleValue() < 0.0));
    371                }
    372                else if (orientation == PlotOrientation.VERTICAL) {
    373                    drawItemLabel(g2, orientation, dataset, row, column,
    374                            x1, y1, (meanValue.doubleValue() < 0.0));
    375                }
    376            }
    377        }
    378
    379        if (pass == 0 && getItemLineVisible(row, column)) {
    380            if (column != 0) {
    381
    382                Number previousValue = statDataset.getValue(row, column - 1);
    383                if (previousValue != null) {
    384
    385                    // previous data point...
    386                    double previous = previousValue.doubleValue();
    387                    double x0;
    388                    if (getUseSeriesOffset()) {
    389                        x0 = domainAxis.getCategorySeriesMiddle(
    390                                column - 1, dataset.getColumnCount(),
    391                                visibleRow, visibleRowCount,
    392                                getItemMargin(), dataArea,
    393                                plot.getDomainAxisEdge());
    394                    }
    395                    else {
    396                        x0 = domainAxis.getCategoryMiddle(column - 1,
    397                                getColumnCount(), dataArea,
    398                                plot.getDomainAxisEdge());
    399                    }
    400                    double y0 = rangeAxis.valueToJava2D(previous, dataArea,
    401                            plot.getRangeAxisEdge());
    402
    403                    Line2D line = null;
    404                    if (orientation == PlotOrientation.HORIZONTAL) {
    405                        line = new Line2D.Double(y0, x0, y1, x1);
    406                    }
    407                    else if (orientation == PlotOrientation.VERTICAL) {
    408                        line = new Line2D.Double(x0, y0, x1, y1);
    409                    }
    410                    g2.setPaint(getItemPaint(row, column));
    411                    g2.setStroke(getItemStroke(row, column));
    412                    g2.draw(line);
    413                }
    414            }
    415        }
    416
    417        if (pass == 1) {
    418            // add an item entity, if this information is being collected
    419            EntityCollection entities = state.getEntityCollection();
    420            if (entities != null) {
    421                addEntity(entities, hotspot, dataset, row, column, x1, y1);
    422            }
    423        }
    424
    425    }
    426
    427    /**
    428     * Tests this renderer for equality with an arbitrary object.
    429     *
    430     * @param obj  the object (<code>null</code> permitted).
    431     *
    432     * @return A boolean.
    433     */
    434    public boolean equals(Object obj) {
    435        if (obj == this) {
    436            return true;
    437        }
    438        if (!(obj instanceof StatisticalLineAndShapeRenderer)) {
    439            return false;
    440        }
    441        StatisticalLineAndShapeRenderer that
    442                = (StatisticalLineAndShapeRenderer) obj;
    443        if (!PaintUtilities.equal(this.errorIndicatorPaint,
    444                that.errorIndicatorPaint)) {
    445            return false;
    446        }
    447        if (!ObjectUtilities.equal(this.errorIndicatorStroke,
    448                that.errorIndicatorStroke)) {
    449            return false;
    450        }
    451        return super.equals(obj);
    452    }
    453
    454    /**
    455     * Returns a hash code for this instance.
    456     *
    457     * @return A hash code.
    458     */
    459    public int hashCode() {
    460        int hash = super.hashCode();
    461        hash = HashUtilities.hashCode(hash, this.errorIndicatorPaint);
    462        return hash;
    463    }
    464
    465    /**
    466     * Provides serialization support.
    467     *
    468     * @param stream  the output stream.
    469     *
    470     * @throws IOException  if there is an I/O error.
    471     */
    472    private void writeObject(ObjectOutputStream stream) throws IOException {
    473        stream.defaultWriteObject();
    474        SerialUtilities.writePaint(this.errorIndicatorPaint, stream);
    475        SerialUtilities.writeStroke(this.errorIndicatorStroke, stream);
    476    }
    477
    478    /**
    479     * Provides serialization support.
    480     *
    481     * @param stream  the input stream.
    482     *
    483     * @throws IOException  if there is an I/O error.
    484     * @throws ClassNotFoundException  if there is a classpath problem.
    485     */
    486    private void readObject(ObjectInputStream stream)
    487            throws IOException, ClassNotFoundException {
    488        stream.defaultReadObject();
    489        this.errorIndicatorPaint = SerialUtilities.readPaint(stream);
    490        this.errorIndicatorStroke = SerialUtilities.readStroke(stream);
    491    }
    492
    493}