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