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