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