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     * DeviationRenderer.java
029     * ----------------------
030     * (C) Copyright 2007, by Object Refinery Limited and Contributors.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   -;
034     *
035     * $Id: DeviationRenderer.java,v 1.1.2.3 2007/05/04 11:12:16 mungady Exp $
036     *
037     * Changes
038     * -------
039     * 21-Feb-2007 : Version 1 (DG);
040     * 04-May-2007 : Set processVisibleItemsOnly flag to false (DG);
041     * 
042     */
043    
044    package org.jfree.chart.renderer.xy;
045    
046    import java.awt.AlphaComposite;
047    import java.awt.Composite;
048    import java.awt.Graphics2D;
049    import java.awt.geom.GeneralPath;
050    import java.awt.geom.Rectangle2D;
051    import java.util.List;
052    
053    import org.jfree.chart.axis.ValueAxis;
054    import org.jfree.chart.entity.EntityCollection;
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.xy.IntervalXYDataset;
061    import org.jfree.data.xy.XYDataset;
062    import org.jfree.ui.RectangleEdge;
063    
064    /**
065     * A specialised subclass of the {@link XYLineAndShapeRenderer} that requires
066     * an {@link IntervalXYDataset} and represents the y-interval by shading an 
067     * area behind the y-values on the chart.
068     * 
069     * @since 1.0.5
070     */
071    public class DeviationRenderer extends XYLineAndShapeRenderer {
072    
073        /**
074         * A state object that is passed to each call to <code>drawItem</code>.
075         */
076        public static class State extends XYLineAndShapeRenderer.State {
077            
078            /** 
079             * A list of coordinates for the upper y-values in the current series 
080             * (after translation into Java2D space).
081             */
082            public List upperCoordinates;
083            
084            /** 
085             * A list of coordinates for the lower y-values in the current series 
086             * (after translation into Java2D space).
087             */
088            public List lowerCoordinates;
089            
090            /**
091             * Creates a new state instance.
092             * 
093             * @param info  the plot rendering info.
094             */
095            public State(PlotRenderingInfo info) {
096                super(info);
097                this.lowerCoordinates = new java.util.ArrayList();
098                this.upperCoordinates = new java.util.ArrayList();
099            }
100            
101        }
102        
103        /** The alpha transparency for the interval shading. */
104        private float alpha;
105    
106        /**
107         * Creates a new renderer that displays lines and shapes for the data 
108         * items, as well as the shaded area for the y-interval.
109         */
110        public DeviationRenderer() {
111            this(true, true);
112        }
113        
114        /**
115         * Creates a new renderer.
116         * 
117         * @param lines  show lines between data items?
118         * @param shapes  show a shape for each data item?
119         */
120        public DeviationRenderer(boolean lines, boolean shapes) {
121            super(lines, shapes);
122            super.setDrawSeriesLineAsPath(true);
123            this.alpha = 0.5f;
124        }
125        
126        /**
127         * Returns the alpha transparency for the background shading.
128         * 
129         * @return The alpha transparency.
130         * 
131         * @see #setAlpha(float)
132         */
133        public float getAlpha() {
134            return this.alpha;
135        }
136    
137        /**
138         * Sets the alpha transparency for the background shading, and sends a 
139         * {@link RendererChangeEvent} to all registered listeners.
140         * 
141         * @param alpha   the alpha (in the range 0.0f to 1.0f).
142         * 
143         * @see #getAlpha()
144         */
145        public void setAlpha(float alpha) {
146            if (alpha < 0.0f || alpha > 1.0f) {
147                throw new IllegalArgumentException(
148                        "Requires 'alpha' in the range 0.0 to 1.0.");
149            }
150            this.alpha = alpha;
151            notifyListeners(new RendererChangeEvent(this));
152        }
153    
154        /**
155         * This method is overridden so that this flag cannot be changed---it is
156         * set to <code>true</code> for this renderer.
157         * 
158         * @param flag  ignored.
159         */
160        public void setDrawSeriesLineAsPath(boolean flag) {
161            // ignore
162        }
163    
164        /**
165         * Initialises and returns a state object that can be passed to each
166         * invocation of the {@link #drawItem} method.
167         * 
168         * @param g2  the graphics target.
169         * @param dataArea  the data area.
170         * @param plot  the plot.
171         * @param dataset  the dataset.
172         * @param info  the plot rendering info.
173         * 
174         * @return A newly initialised state object.
175         */
176        public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea, 
177                XYPlot plot, XYDataset dataset, PlotRenderingInfo info) {
178            State state = new State(info);
179            state.seriesPath = new GeneralPath();
180            state.setProcessVisibleItemsOnly(false);
181            return state;
182        }
183    
184        /**
185         * Returns the number of passes (through the dataset) used by this 
186         * renderer.
187         * 
188         * @return <code>3</code>.
189         */
190        public int getPassCount() {
191            return 3;
192        }
193    
194        /**
195         * Returns <code>true</code> if this is the pass where the shapes are
196         * drawn.
197         * 
198         * @param pass  the pass index.
199         * 
200         * @return A boolean.
201         * 
202         * @see #isLinePass(int)
203         */
204        protected boolean isItemPass(int pass) {
205            return (pass == 2);
206        }
207    
208        /**
209         * Returns <code>true</code> if this is the pass where the lines are
210         * drawn.
211         * 
212         * @param pass  the pass index.
213         * 
214         * @return A boolean.
215         * 
216         * @see #isItemPass(int)
217         */
218        protected boolean isLinePass(int pass) {
219            return (pass == 1);
220        }
221    
222        /**
223         * Draws the visual representation of a single data item.
224         *
225         * @param g2  the graphics device.
226         * @param state  the renderer state.
227         * @param dataArea  the area within which the data is being drawn.
228         * @param info  collects information about the drawing.
229         * @param plot  the plot (can be used to obtain standard color 
230         *              information etc).
231         * @param domainAxis  the domain axis.
232         * @param rangeAxis  the range axis.
233         * @param dataset  the dataset.
234         * @param series  the series index (zero-based).
235         * @param item  the item index (zero-based).
236         * @param crosshairState  crosshair information for the plot 
237         *                        (<code>null</code> permitted).
238         * @param pass  the pass index.
239         */
240        public void drawItem(Graphics2D g2,
241                             XYItemRendererState state,
242                             Rectangle2D dataArea,
243                             PlotRenderingInfo info,
244                             XYPlot plot,
245                             ValueAxis domainAxis,
246                             ValueAxis rangeAxis,
247                             XYDataset dataset,
248                             int series,
249                             int item,
250                             CrosshairState crosshairState,
251                             int pass) {
252    
253            // do nothing if item is not visible
254            if (!getItemVisible(series, item)) {
255                return;   
256            }
257    
258            // first pass draws the shading
259            if (pass == 0) {
260                IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset;
261                State drState = (State) state;
262    
263                double x = intervalDataset.getXValue(series, item);
264                double yLow = intervalDataset.getStartYValue(series, item);
265                double yHigh  = intervalDataset.getEndYValue(series, item);
266    
267                RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
268                RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
269                
270                double xx = domainAxis.valueToJava2D(x, dataArea, xAxisLocation);
271                double yyLow = rangeAxis.valueToJava2D(yLow, dataArea, 
272                        yAxisLocation);
273                double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea, 
274                        yAxisLocation);
275    
276                PlotOrientation orientation = plot.getOrientation();
277                if (orientation == PlotOrientation.HORIZONTAL) {
278                    drState.lowerCoordinates.add(new double[] {yyLow, xx});
279                    drState.upperCoordinates.add(new double[] {yyHigh, xx});
280                }
281                else if (orientation == PlotOrientation.VERTICAL) {
282                    drState.lowerCoordinates.add(new double[] {xx, yyLow});
283                    drState.upperCoordinates.add(new double[] {xx, yyHigh});
284                }
285    
286                if (item == (dataset.getItemCount(series) - 1)) {
287                    // last item in series, draw the lot...
288                    // set up the alpha-transparency...
289                    Composite originalComposite = g2.getComposite();
290                    g2.setComposite(AlphaComposite.getInstance(
291                            AlphaComposite.SRC_OVER, this.alpha));
292                    g2.setPaint(getItemFillPaint(series, item));
293                    GeneralPath area = new GeneralPath();
294                    double[] coords = (double[]) drState.lowerCoordinates.get(0);
295                    area.moveTo((float) coords[0], (float) coords[1]);
296                    for (int i = 1; i < drState.lowerCoordinates.size(); i++) {
297                        coords = (double[]) drState.lowerCoordinates.get(i);
298                        area.lineTo((float) coords[0], (float) coords[1]);
299                    }
300                    int count = drState.upperCoordinates.size();
301                    coords = (double[]) drState.upperCoordinates.get(count - 1);
302                    area.lineTo((float) coords[0], (float) coords[1]);
303                    for (int i = count - 2; i >= 0; i--) {
304                        coords = (double[]) drState.upperCoordinates.get(i);
305                        area.lineTo((float) coords[0], (float) coords[1]);
306                    }
307                    area.closePath();
308                    g2.fill(area);
309                    g2.setComposite(originalComposite);
310                    
311                    drState.lowerCoordinates.clear();
312                    drState.upperCoordinates.clear();
313                }            
314            }
315            if (isLinePass(pass)) {
316                
317                // the following code handles the line for the y-values...it's
318                // all done by code in the super class
319                if (item == 0) {
320                    State s = (State) state;
321                    s.seriesPath.reset();
322                    s.setLastPointGood(false);     
323                }
324    
325                if (getItemLineVisible(series, item)) {
326                    drawPrimaryLineAsPath(state, g2, plot, dataset, pass, 
327                            series, item, domainAxis, rangeAxis, dataArea);
328                }
329            }
330            
331            // second pass adds shapes where the items are ..
332            else if (isItemPass(pass)) {
333    
334                // setup for collecting optional entity info...
335                EntityCollection entities = null;
336                if (info != null) {
337                    entities = info.getOwner().getEntityCollection();
338                }
339    
340                drawSecondaryPass(g2, plot, dataset, pass, series, item, 
341                        domainAxis, dataArea, rangeAxis, crosshairState, entities);
342            }
343        }
344        
345        /**
346         * Tests this renderer for equality with an arbitrary object.
347         * 
348         * @param obj  the object (<code>null</code> permitted).
349         * 
350         * @return A boolean.
351         */
352        public boolean equals(Object obj) {
353            if (obj == this) {
354                return true;
355            }
356            if (!(obj instanceof DeviationRenderer)) {
357                return false;
358            }
359            DeviationRenderer that = (DeviationRenderer) obj;
360            if (this.alpha != that.alpha) {
361                return false;
362            }
363            return super.equals(obj);
364        }
365    
366    }