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
042package org.jfree.chart.renderer.xy;
043
044import java.awt.AlphaComposite;
045import java.awt.Composite;
046import java.awt.Graphics2D;
047import java.awt.geom.GeneralPath;
048import java.awt.geom.Rectangle2D;
049import java.util.List;
050
051import org.jfree.chart.axis.ValueAxis;
052import org.jfree.chart.entity.EntityCollection;
053import org.jfree.chart.event.RendererChangeEvent;
054import org.jfree.chart.plot.CrosshairState;
055import org.jfree.chart.plot.PlotOrientation;
056import org.jfree.chart.plot.PlotRenderingInfo;
057import org.jfree.chart.plot.XYPlot;
058import org.jfree.data.xy.IntervalXYDataset;
059import org.jfree.data.xy.XYDataset;
060import 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 */
069public 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        notifyListeners(new RendererChangeEvent(this));
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}