    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 * XYErrorRenderer.java
    029 * --------------------
    030 * (C) Copyright 2006-2011, by Object Refinery Limited.
    031 *
    032 * Original Author:  David Gilbert (for Object Refinery Limited);
    033 * Contributor(s):   -;
    034 *
    035 * Changes
    036 * -------
    037 * 25-Oct-2006 : Version 1 (DG);
    038 * 23-Mar-2007 : Check item visibility before drawing error bars - see bug
    039 *               1686178 (DG);
    040 * 28-Jan-2009 : Added stroke options for error indicators (DG);
    041 *
    042 */
    043
    044package org.jfree.chart.renderer.xy;
    045
    046import java.awt.Graphics2D;
    047import java.awt.Paint;
    048import java.awt.Stroke;
    049import java.awt.geom.Line2D;
    050import java.awt.geom.Rectangle2D;
    051import java.io.IOException;
    052import java.io.ObjectInputStream;
    053import java.io.ObjectOutputStream;
    054
    055import org.jfree.chart.axis.ValueAxis;
    056import org.jfree.chart.event.RendererChangeEvent;
    057import org.jfree.chart.plot.CrosshairState;
    058import org.jfree.chart.plot.PlotOrientation;
    059import org.jfree.chart.plot.PlotRenderingInfo;
    060import org.jfree.chart.plot.XYPlot;
    061import org.jfree.data.Range;
    062import org.jfree.data.general.DatasetUtilities;
    063import org.jfree.data.xy.IntervalXYDataset;
    064import org.jfree.data.xy.XYDataset;
    065import org.jfree.io.SerialUtilities;
    066import org.jfree.ui.RectangleEdge;
    067import org.jfree.util.ObjectUtilities;
    068import 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.  The example shown here is generated by
    074 * the <code>XYErrorRendererDemo1.java</code> program included in the
    075 * JFreeChart demo collection:
    076 * <br><br>
    077 * <img src="../../../../../images/XYErrorRendererSample.png"
    078 * alt="XYErrorRendererSample.png" />
    079 *
    080 * @since 1.0.3
    081 */
    082public class XYErrorRenderer extends XYLineAndShapeRenderer {
    083
    084    /** For serialization. */
    085    static final long serialVersionUID = 5162283570955172424L;
    086
    087    /** A flag that controls whether or not the x-error bars are drawn. */
    088    private boolean drawXError;
    089
    090    /** A flag that controls whether or not the y-error bars are drawn. */
    091    private boolean drawYError;
    092
    093    /** The length of the cap at the end of the error bars. */
    094    private double capLength;
    095
    096    /**
    097     * The paint used to draw the error bars (if <code>null</code> we use the
    098     * series paint).
    099     */
    100    private transient Paint errorPaint;
    101
    102    /**
    103     * The stroke used to draw the error bars (if <code>null</code> we use the
    104     * series outline stroke).
    105     *
    106     * @since 1.0.13
    107     */
    108    private transient Stroke errorStroke;
    109
    110    /**
    111     * Creates a new <code>XYErrorRenderer</code> instance.
    112     */
    113    public XYErrorRenderer() {
    114        super(false, true);
    115        this.drawXError = true;
    116        this.drawYError = true;
    117        this.errorPaint = null;
    118        this.errorStroke = null;
    119        this.capLength = 4.0;
    120    }
    121
    122    /**
    123     * Returns the flag that controls whether or not the renderer draws error
    124     * bars for the x-values.
    125     *
    126     * @return A boolean.
    127     *
    128     * @see #setDrawXError(boolean)
    129     */
    130    public boolean getDrawXError() {
    131        return this.drawXError;
    132    }
    133
    134    /**
    135     * Sets the flag that controls whether or not the renderer draws error
    136     * bars for the x-values and, if the flag changes, sends a
    137     * {@link RendererChangeEvent} to all registered listeners.
    138     *
    139     * @param draw  the flag value.
    140     *
    141     * @see #getDrawXError()
    142     */
    143    public void setDrawXError(boolean draw) {
    144        if (this.drawXError != draw) {
    145            this.drawXError = draw;
    146            fireChangeEvent();
    147        }
    148    }
    149
    150    /**
    151     * Returns the flag that controls whether or not the renderer draws error
    152     * bars for the y-values.
    153     *
    154     * @return A boolean.
    155     *
    156     * @see #setDrawYError(boolean)
    157     */
    158    public boolean getDrawYError() {
    159        return this.drawYError;
    160    }
    161
    162    /**
    163     * Sets the flag that controls whether or not the renderer draws error
    164     * bars for the y-values and, if the flag changes, sends a
    165     * {@link RendererChangeEvent} to all registered listeners.
    166     *
    167     * @param draw  the flag value.
    168     *
    169     * @see #getDrawYError()
    170     */
    171    public void setDrawYError(boolean draw) {
    172        if (this.drawYError != draw) {
    173            this.drawYError = draw;
    174            fireChangeEvent();
    175        }
    176    }
    177
    178    /**
    179     * Returns the length (in Java2D units) of the cap at the end of the error
    180     * bars.
    181     *
    182     * @return The cap length.
    183     *
    184     * @see #setCapLength(double)
    185     */
    186    public double getCapLength() {
    187        return this.capLength;
    188    }
    189
    190    /**
    191     * Sets the length of the cap at the end of the error bars, and sends a
    192     * {@link RendererChangeEvent} to all registered listeners.
    193     *
    194     * @param length  the length (in Java2D units).
    195     *
    196     * @see #getCapLength()
    197     */
    198    public void setCapLength(double length) {
    199        this.capLength = length;
    200        fireChangeEvent();
    201    }
    202
    203    /**
    204     * Returns the paint used to draw the error bars.  If this is
    205     * <code>null</code> (the default), the item paint is used instead.
    206     *
    207     * @return The paint (possibly <code>null</code>).
    208     *
    209     * @see #setErrorPaint(Paint)
    210     */
    211    public Paint getErrorPaint() {
    212        return this.errorPaint;
    213    }
    214
    215    /**
    216     * Sets the paint used to draw the error bars and sends a
    217     * {@link RendererChangeEvent} to all registered listeners.
    218     *
    219     * @param paint  the paint (<code>null</code> permitted).
    220     *
    221     * @see #getErrorPaint()
    222     */
    223    public void setErrorPaint(Paint paint) {
    224        this.errorPaint = paint;
    225        fireChangeEvent();
    226    }
    227
    228    /**
    229     * Returns the stroke used to draw the error bars.  If this is
    230     * <code>null</code> (the default), the item outline stroke is used
    231     * instead.
    232     *
    233     * @return The stroke (possibly <code>null</code>).
    234     *
    235     * @see #setErrorStroke(Stroke)
    236     *
    237     * @since 1.0.13
    238     */
    239    public Stroke getErrorStroke() {
    240        return this.errorStroke;
    241    }
    242
    243    /**
    244     * Sets the stroke used to draw the error bars and sends a
    245     * {@link RendererChangeEvent} to all registered listeners.
    246     *
    247     * @param stroke   the stroke (<code>null</code> permitted).
    248     *
    249     * @see #getErrorStroke()
    250     *
    251     * @since 1.0.13
    252     */
    253    public void setErrorStroke(Stroke stroke) {
    254        this.errorStroke = stroke;
    255        fireChangeEvent();
    256    }
    257
    258    /**
    259     * Returns the range required by this renderer to display all the domain
    260     * values in the specified dataset.
    261     *
    262     * @param dataset  the dataset (<code>null</code> permitted).
    263     *
    264     * @return The range, or <code>null</code> if the dataset is
    265     *     <code>null</code>.
    266     */
    267    public Range findDomainBounds(XYDataset dataset) {
    268        // include the interval if there is one
    269        return findDomainBounds(dataset, true);
    270    }
    271
    272    /**
    273     * Returns the range required by this renderer to display all the range
    274     * values in the specified dataset.
    275     *
    276     * @param dataset  the dataset (<code>null</code> permitted).
    277     *
    278     * @return The range, or <code>null</code> if the dataset is
    279     *     <code>null</code>.
    280     */
    281    public Range findRangeBounds(XYDataset dataset) {
    282        // include the interval if there is one
    283        return findRangeBounds(dataset, true);
    284    }
    285
    286    /**
    287     * Draws the visual representation for one data item.
    288     *
    289     * @param g2  the graphics output target.
    290     * @param state  the renderer state.
    291     * @param dataArea  the data area.
    292     * @param info  the plot rendering info.
    293     * @param plot  the plot.
    294     * @param domainAxis  the domain axis.
    295     * @param rangeAxis  the range axis.
    296     * @param dataset  the dataset.
    297     * @param series  the series index.
    298     * @param item  the item index.
    299     * @param crosshairState  the crosshair state.
    300     * @param pass  the pass index.
    301     */
    302    public void drawItem(Graphics2D g2, XYItemRendererState state,
    303            Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
    304            ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset,
    305            int series, int item, CrosshairState crosshairState, int pass) {
    306
    307        if (pass == 0 && dataset instanceof IntervalXYDataset
    308                && getItemVisible(series, item)) {
    309            IntervalXYDataset ixyd = (IntervalXYDataset) dataset;
    310            PlotOrientation orientation = plot.getOrientation();
    311            if (this.drawXError) {
    312                // draw the error bar for the x-interval
    313                double x0 = ixyd.getStartXValue(series, item);
    314                double x1 = ixyd.getEndXValue(series, item);
    315                double y = ixyd.getYValue(series, item);
    316                RectangleEdge edge = plot.getDomainAxisEdge();
    317                double xx0 = domainAxis.valueToJava2D(x0, dataArea, edge);
    318                double xx1 = domainAxis.valueToJava2D(x1, dataArea, edge);
    319                double yy = rangeAxis.valueToJava2D(y, dataArea,
    320                        plot.getRangeAxisEdge());
    321                Line2D line;
    322                Line2D cap1 = null;
    323                Line2D cap2 = null;
    324                double adj = this.capLength / 2.0;
    325                if (orientation == PlotOrientation.VERTICAL) {
    326                    line = new Line2D.Double(xx0, yy, xx1, yy);
    327                    cap1 = new Line2D.Double(xx0, yy - adj, xx0, yy + adj);
    328                    cap2 = new Line2D.Double(xx1, yy - adj, xx1, yy + adj);
    329                }
    330                else {  // PlotOrientation.HORIZONTAL
    331                    line = new Line2D.Double(yy, xx0, yy, xx1);
    332                    cap1 = new Line2D.Double(yy - adj, xx0, yy + adj, xx0);
    333                    cap2 = new Line2D.Double(yy - adj, xx1, yy + adj, xx1);
    334                }
    335                if (this.errorPaint != null) {
    336                    g2.setPaint(this.errorPaint);
    337                }
    338                else {
    339                    g2.setPaint(getItemPaint(series, item));
    340                }
    341                if (this.errorStroke != null) {
    342                    g2.setStroke(this.errorStroke);
    343                }
    344                else {
    345                    g2.setStroke(getItemStroke(series, item));
    346                }
    347                g2.draw(line);
    348                g2.draw(cap1);
    349                g2.draw(cap2);
    350            }
    351            if (this.drawYError) {
    352                // draw the error bar for the y-interval
    353                double y0 = ixyd.getStartYValue(series, item);
    354                double y1 = ixyd.getEndYValue(series, item);
    355                double x = ixyd.getXValue(series, item);
    356                RectangleEdge edge = plot.getRangeAxisEdge();
    357                double yy0 = rangeAxis.valueToJava2D(y0, dataArea, edge);
    358                double yy1 = rangeAxis.valueToJava2D(y1, dataArea, edge);
    359                double xx = domainAxis.valueToJava2D(x, dataArea,
    360                        plot.getDomainAxisEdge());
    361                Line2D line;
    362                Line2D cap1 = null;
    363                Line2D cap2 = null;
    364                double adj = this.capLength / 2.0;
    365                if (orientation == PlotOrientation.VERTICAL) {
    366                    line = new Line2D.Double(xx, yy0, xx, yy1);
    367                    cap1 = new Line2D.Double(xx - adj, yy0, xx + adj, yy0);
    368                    cap2 = new Line2D.Double(xx - adj, yy1, xx + adj, yy1);
    369                }
    370                else {  // PlotOrientation.HORIZONTAL
    371                    line = new Line2D.Double(yy0, xx, yy1, xx);
    372                    cap1 = new Line2D.Double(yy0, xx - adj, yy0, xx + adj);
    373                    cap2 = new Line2D.Double(yy1, xx - adj, yy1, xx + adj);
    374                }
    375                if (this.errorPaint != null) {
    376                    g2.setPaint(this.errorPaint);
    377                }
    378                else {
    379                    g2.setPaint(getItemPaint(series, item));
    380                }
    381                if (this.errorStroke != null) {
    382                    g2.setStroke(this.errorStroke);
    383                }
    384                else {
    385                    g2.setStroke(getItemStroke(series, item));
    386                }
    387                g2.draw(line);
    388                g2.draw(cap1);
    389                g2.draw(cap2);
    390            }
    391        }
    392        super.drawItem(g2, state, dataArea, info, plot, domainAxis, rangeAxis,
    393                dataset, series, item, crosshairState, pass);
    394    }
    395
    396    /**
    397     * Tests this instance for equality with an arbitrary object.
    398     *
    399     * @param obj  the object (<code>null</code> permitted).
    400     *
    401     * @return A boolean.
    402     */
    403    public boolean equals(Object obj) {
    404        if (obj == this) {
    405            return true;
    406        }
    407        if (!(obj instanceof XYErrorRenderer)) {
    408            return false;
    409        }
    410        XYErrorRenderer that = (XYErrorRenderer) obj;
    411        if (this.drawXError != that.drawXError) {
    412            return false;
    413        }
    414        if (this.drawYError != that.drawYError) {
    415            return false;
    416        }
    417        if (this.capLength != that.capLength) {
    418            return false;
    419        }
    420        if (!PaintUtilities.equal(this.errorPaint, that.errorPaint)) {
    421            return false;
    422        }
    423        if (!ObjectUtilities.equal(this.errorStroke, that.errorStroke)) {
    424            return false;
    425        }
    426        return super.equals(obj);
    427    }
    428
    429    /**
    430     * Provides serialization support.
    431     *
    432     * @param stream  the input stream.
    433     *
    434     * @throws IOException  if there is an I/O error.
    435     * @throws ClassNotFoundException  if there is a classpath problem.
    436     */
    437    private void readObject(ObjectInputStream stream)
    438            throws IOException, ClassNotFoundException {
    439        stream.defaultReadObject();
    440        this.errorPaint = SerialUtilities.readPaint(stream);
    441        this.errorStroke = SerialUtilities.readStroke(stream);
    442    }
    443
    444    /**
    445     * Provides serialization support.
    446     *
    447     * @param stream  the output stream.
    448     *
    449     * @throws IOException  if there is an I/O error.
    450     */
    451    private void writeObject(ObjectOutputStream stream) throws IOException {
    452        stream.defaultWriteObject();
    453        SerialUtilities.writePaint(this.errorPaint, stream);
    454        SerialUtilities.writeStroke(this.errorStroke, stream);
    455    }
    456
    457}