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    }