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