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