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