    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     * XYErrorRenderer.java
    029     * --------------------
    030     * (C) Copyright 2006, 2007, by Object Refinery Limited.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   -;
    034     *
    035     * $Id: XYErrorRenderer.java,v 1.1.2.4 2007/03/23 14:01:12 mungady Exp $
    036     *
    037     * Changes
    038     * -------
    039     * 25-Oct-2006 : Version 1 (DG);
    040     * 23-Mar-2007 : Check item visibility before drawing error bars - see bug
    041     *               1686178 (DG);
    042     *
    043     */
    044
    045    package org.jfree.chart.renderer.xy;
    046
    047    import java.awt.BasicStroke;
    048    import java.awt.Graphics2D;
    049    import java.awt.Paint;
    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
    056    import org.jfree.chart.axis.ValueAxis;
    057    import org.jfree.chart.event.RendererChangeEvent;
    058    import org.jfree.chart.plot.CrosshairState;
    059    import org.jfree.chart.plot.PlotOrientation;
    060    import org.jfree.chart.plot.PlotRenderingInfo;
    061    import org.jfree.chart.plot.XYPlot;
    062    import org.jfree.data.Range;
    063    import org.jfree.data.general.DatasetUtilities;
    064    import org.jfree.data.xy.IntervalXYDataset;
    065    import org.jfree.data.xy.XYDataset;
    066    import org.jfree.io.SerialUtilities;
    067    import org.jfree.ui.RectangleEdge;
    068    import org.jfree.util.PaintUtilities;
    069
    070    /**
    071     * A line and shape renderer that can also display x and/or y-error values.
    072     * This renderer expects an {@link IntervalXYDataset}, otherwise it reverts
    073     * to the behaviour of the super class.
    074     *
    075     * @since 1.0.3
    076     */
    077    public class XYErrorRenderer extends XYLineAndShapeRenderer {
    078
    079        /** A flag that controls whether or not the x-error bars are drawn. */
    080        private boolean drawXError;
    081
    082        /** A flag that controls whether or not the y-error bars are drawn. */
    083        private boolean drawYError;
    084
    085        /** The length of the cap at the end of the error bars. */
    086        private double capLength;
    087
    088        /**
    089         * The paint used to draw the error bars (if <code>null</code> we use the
    090         * series paint).
    091         */
    092        private transient Paint errorPaint;
    093
    094        /**
    095         * Creates a new <code>XYErrorRenderer</code> instance.
    096         */
    097        public XYErrorRenderer() {
    098            super(false, true);
    099            this.drawXError = true;
    100            this.drawYError = true;
    101            this.errorPaint = null;
    102            this.capLength = 4.0;
    103        }
    104
    105        /**
    106         * Returns the flag that controls whether or not the renderer draws error
    107         * bars for the x-values.
    108         *
    109         * @return A boolean.
    110         *
    111         * @see #setDrawXError(boolean)
    112         */
    113        public boolean getDrawXError() {
    114            return this.drawXError;
    115        }
    116
    117        /**
    118         * Sets the flag that controls whether or not the renderer draws error
    119         * bars for the x-values and, if the flag changes, sends a
    120         * {@link RendererChangeEvent} to all registered listeners.
    121         *
    122         * @param draw  the flag value.
    123         *
    124         * @see #getDrawXError()
    125         */
    126        public void setDrawXError(boolean draw) {
    127            if (this.drawXError != draw) {
    128                this.drawXError = draw;
    129                this.notifyListeners(new RendererChangeEvent(this));
    130            }
    131        }
    132
    133        /**
    134         * Returns the flag that controls whether or not the renderer draws error
    135         * bars for the y-values.
    136         *
    137         * @return A boolean.
    138         *
    139         * @see #setDrawYError(boolean)
    140         */
    141        public boolean getDrawYError() {
    142            return this.drawYError;
    143        }
    144
    145        /**
    146         * Sets the flag that controls whether or not the renderer draws error
    147         * bars for the y-values and, if the flag changes, sends a
    148         * {@link RendererChangeEvent} to all registered listeners.
    149         *
    150         * @param draw  the flag value.
    151         *
    152         * @see #getDrawYError()
    153         */
    154        public void setDrawYError(boolean draw) {
    155            if (this.drawYError != draw) {
    156                this.drawYError = draw;
    157                notifyListeners(new RendererChangeEvent(this));
    158            }
    159        }
    160
    161        /**
    162         * Returns the length (in Java2D units) of the cap at the end of the error
    163         * bars.
    164         *
    165         * @return The cap length.
    166         *
    167         * @see #setCapLength(double)
    168         */
    169        public double getCapLength() {
    170            return this.capLength;
    171        }
    172
    173        /**
    174         * Sets the length of the cap at the end of the error bars, and sends a
    175         * {@link RendererChangeEvent} to all registered listeners.
    176         *
    177         * @param length  the length (in Java2D units).
    178         *
    179         * @see #getCapLength()
    180         */
    181        public void setCapLength(double length) {
    182            this.capLength = length;
    183            notifyListeners(new RendererChangeEvent(this));
    184        }
    185
    186        /**
    187         * Returns the paint used to draw the error bars.  If this is
    188         * <code>null</code> (the default), the item paint is used instead.
    189         *
    190         * @return The paint (possibly <code>null</code>).
    191         *
    192         * @see #setErrorPaint(Paint)
    193         */
    194        public Paint getErrorPaint() {
    195            return this.errorPaint;
    196        }
    197
    198        /**
    199         * Sets the paint used to draw the error bars.
    200         *
    201         * @param paint  the paint (<code>null</code> permitted).
    202         *
    203         * @see #getErrorPaint()
    204         */
    205        public void setErrorPaint(Paint paint) {
    206            this.errorPaint = paint;
    207            notifyListeners(new RendererChangeEvent(this));
    208        }
    209
    210        /**
    211         * Returns the range required by this renderer to display all the domain
    212         * values in the specified dataset.
    213         *
    214         * @param dataset  the dataset (<code>null</code> permitted).
    215         *
    216         * @return The range, or <code>null</code> if the dataset is
    217         *     <code>null</code>.
    218         */
    219        public Range findDomainBounds(XYDataset dataset) {
    220            if (dataset != null) {
    221                return DatasetUtilities.findDomainBounds(dataset, true);
    222            }
    223            else {
    224                return null;
    225            }
    226        }
    227
    228        /**
    229         * Returns the range required by this renderer to display all the range
    230         * values in the specified dataset.
    231         *
    232         * @param dataset  the dataset (<code>null</code> permitted).
    233         *
    234         * @return The range, or <code>null</code> if the dataset is
    235         *     <code>null</code>.
    236         */
    237        public Range findRangeBounds(XYDataset dataset) {
    238            if (dataset != null) {
    239                return DatasetUtilities.findRangeBounds(dataset, true);
    240            }
    241            else {
    242                return null;
    243            }
    244        }
    245
    246        /**
    247         * Draws the visual representation for one data item.
    248         *
    249         * @param g2  the graphics output target.
    250         * @param state  the renderer state.
    251         * @param dataArea  the data area.
    252         * @param info  the plot rendering info.
    253         * @param plot  the plot.
    254         * @param domainAxis  the domain axis.
    255         * @param rangeAxis  the range axis.
    256         * @param dataset  the dataset.
    257         * @param series  the series index.
    258         * @param item  the item index.
    259         * @param crosshairState  the crosshair state.
    260         * @param pass  the pass index.
    261         */
    262        public void drawItem(Graphics2D g2, XYItemRendererState state,
    263                Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
    264                ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset,
    265                int series, int item, CrosshairState crosshairState, int pass) {
    266
    267            if (pass == 0 && dataset instanceof IntervalXYDataset
    268                    && getItemVisible(series, item)) {
    269                IntervalXYDataset ixyd = (IntervalXYDataset) dataset;
    270                PlotOrientation orientation = plot.getOrientation();
    271                if (this.drawXError) {
    272                    // draw the error bar for the x-interval
    273                    double x0 = ixyd.getStartXValue(series, item);
    274                    double x1 = ixyd.getEndXValue(series, item);
    275                    double y = ixyd.getYValue(series, item);
    276                    RectangleEdge edge = plot.getDomainAxisEdge();
    277                    double xx0 = domainAxis.valueToJava2D(x0, dataArea, edge);
    278                    double xx1 = domainAxis.valueToJava2D(x1, dataArea, edge);
    279                    double yy = rangeAxis.valueToJava2D(y, dataArea,
    280                            plot.getRangeAxisEdge());
    281                    Line2D line;
    282                    Line2D cap1 = null;
    283                    Line2D cap2 = null;
    284                    double adj = this.capLength / 2.0;
    285                    if (orientation == PlotOrientation.VERTICAL) {
    286                        line = new Line2D.Double(xx0, yy, xx1, yy);
    287                        cap1 = new Line2D.Double(xx0, yy - adj, xx0, yy + adj);
    288                        cap2 = new Line2D.Double(xx1, yy - adj, xx1, yy + adj);
    289                    }
    290                    else {  // PlotOrientation.HORIZONTAL
    291                        line = new Line2D.Double(yy, xx0, yy, xx1);
    292                        cap1 = new Line2D.Double(yy - adj, xx0, yy + adj, xx0);
    293                        cap2 = new Line2D.Double(yy - adj, xx1, yy + adj, xx1);
    294                    }
    295                    g2.setStroke(new BasicStroke(1.0f));
    296                    if (this.errorPaint != null) {
    297                        g2.setPaint(this.errorPaint);
    298                    }
    299                    else {
    300                        g2.setPaint(getItemPaint(series, item));
    301                    }
    302                    g2.draw(line);
    303                    g2.draw(cap1);
    304                    g2.draw(cap2);
    305                }
    306                if (this.drawYError) {
    307                    // draw the error bar for the y-interval
    308                    double y0 = ixyd.getStartYValue(series, item);
    309                    double y1 = ixyd.getEndYValue(series, item);
    310                    double x = ixyd.getXValue(series, item);
    311                    RectangleEdge edge = plot.getRangeAxisEdge();
    312                    double yy0 = rangeAxis.valueToJava2D(y0, dataArea, edge);
    313                    double yy1 = rangeAxis.valueToJava2D(y1, dataArea, edge);
    314                    double xx = domainAxis.valueToJava2D(x, dataArea,
    315                            plot.getDomainAxisEdge());
    316                    Line2D line;
    317                    Line2D cap1 = null;
    318                    Line2D cap2 = null;
    319                    double adj = this.capLength / 2.0;
    320                    if (orientation == PlotOrientation.VERTICAL) {
    321                        line = new Line2D.Double(xx, yy0, xx, yy1);
    322                        cap1 = new Line2D.Double(xx - adj, yy0, xx + adj, yy0);
    323                        cap2 = new Line2D.Double(xx - adj, yy1, xx + adj, yy1);
    324                    }
    325                    else {  // PlotOrientation.HORIZONTAL
    326                        line = new Line2D.Double(yy0, xx, yy1, xx);
    327                        cap1 = new Line2D.Double(yy0, xx - adj, yy0, xx + adj);
    328                        cap2 = new Line2D.Double(yy1, xx - adj, yy1, xx + adj);
    329                    }
    330                    g2.setStroke(new BasicStroke(1.0f));
    331                    if (this.errorPaint != null) {
    332                        g2.setPaint(this.errorPaint);
    333                    }
    334                    else {
    335                        g2.setPaint(getItemPaint(series, item));
    336                    }
    337                    g2.draw(line);
    338                    g2.draw(cap1);
    339                    g2.draw(cap2);
    340                }
    341            }
    342            super.drawItem(g2, state, dataArea, info, plot, domainAxis, rangeAxis,
    343                    dataset, series, item, crosshairState, pass);
    344        }
    345
    346        /**
    347         * Tests this instance for equality with an arbitrary object.
    348         *
    349         * @param obj  the object (<code>null</code> permitted).
    350         *
    351         * @return A boolean.
    352         */
    353        public boolean equals(Object obj) {
    354            if (obj == this) {
    355                return true;
    356            }
    357            if (!(obj instanceof XYErrorRenderer)) {
    358                return false;
    359            }
    360            XYErrorRenderer that = (XYErrorRenderer) obj;
    361            if (this.drawXError != that.drawXError) {
    362                return false;
    363            }
    364            if (this.drawYError != that.drawYError) {
    365                return false;
    366            }
    367            if (this.capLength != that.capLength) {
    368                return false;
    369            }
    370            if (!PaintUtilities.equal(this.errorPaint, that.errorPaint)) {
    371                return false;
    372            }
    373            return super.equals(obj);
    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.errorPaint = SerialUtilities.readPaint(stream);
    388        }
    389
    390        /**
    391         * Provides serialization support.
    392         *
    393         * @param stream  the output stream.
    394         *
    395         * @throws IOException  if there is an I/O error.
    396         */
    397        private void writeObject(ObjectOutputStream stream) throws IOException {
    398            stream.defaultWriteObject();
    399            SerialUtilities.writePaint(this.errorPaint, stream);
    400        }
    401
    402    }