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