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