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