001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2014, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * ----------------------
028 * DeviationRenderer.java
029 * ----------------------
030 * (C) Copyright 2007-2014, 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 * 01-Jul-2012 : Provide initial size for GeneralPath in drawItem(), as 
042 *               suggested by Milan Ramaiya in bug 3521736 (DG);
043 * 
044 */
045
046package org.jfree.chart.renderer.xy;
047
048import java.awt.AlphaComposite;
049import java.awt.Composite;
050import java.awt.Graphics2D;
051import java.awt.geom.GeneralPath;
052import java.awt.geom.Rectangle2D;
053import java.util.List;
054
055import org.jfree.chart.axis.ValueAxis;
056import org.jfree.chart.entity.EntityCollection;
057import org.jfree.chart.event.RendererChangeEvent;
058import org.jfree.chart.plot.CrosshairState;
059import org.jfree.chart.plot.PlotOrientation;
060import org.jfree.chart.plot.PlotRenderingInfo;
061import org.jfree.chart.plot.XYPlot;
062import org.jfree.data.Range;
063import org.jfree.data.xy.IntervalXYDataset;
064import org.jfree.data.xy.XYDataset;
065import org.jfree.ui.RectangleEdge;
066
067/**
068 * A specialised subclass of the {@link XYLineAndShapeRenderer} that requires
069 * an {@link IntervalXYDataset} and represents the y-interval by shading an
070 * area behind the y-values on the chart.
071 * The example shown here is generated by the
072 * <code>DeviationRendererDemo1.java</code> program included in the
073 * JFreeChart demo collection:
074 * <br><br>
075 * <img src="../../../../../images/DeviationRendererSample.png"
076 * alt="DeviationRendererSample.png">
077 *
078 * @since 1.0.5
079 */
080public class DeviationRenderer extends XYLineAndShapeRenderer {
081
082    /**
083     * A state object that is passed to each call to <code>drawItem</code>.
084     */
085    public static class State extends XYLineAndShapeRenderer.State {
086
087        /**
088         * A list of coordinates for the upper y-values in the current series
089         * (after translation into Java2D space).
090         */
091        public List upperCoordinates;
092
093        /**
094         * A list of coordinates for the lower y-values in the current series
095         * (after translation into Java2D space).
096         */
097        public List lowerCoordinates;
098
099        /**
100         * Creates a new state instance.
101         *
102         * @param info  the plot rendering info.
103         */
104        public State(PlotRenderingInfo info) {
105            super(info);
106            this.lowerCoordinates = new java.util.ArrayList();
107            this.upperCoordinates = new java.util.ArrayList();
108        }
109
110    }
111
112    /** The alpha transparency for the interval shading. */
113    private float alpha;
114
115    /**
116     * Creates a new renderer that displays lines and shapes for the data
117     * items, as well as the shaded area for the y-interval.
118     */
119    public DeviationRenderer() {
120        this(true, true);
121    }
122
123    /**
124     * Creates a new renderer.
125     *
126     * @param lines  show lines between data items?
127     * @param shapes  show a shape for each data item?
128     */
129    public DeviationRenderer(boolean lines, boolean shapes) {
130        super(lines, shapes);
131        super.setDrawSeriesLineAsPath(true);
132        this.alpha = 0.5f;
133    }
134
135    /**
136     * Returns the alpha transparency for the background shading.
137     *
138     * @return The alpha transparency.
139     *
140     * @see #setAlpha(float)
141     */
142    public float getAlpha() {
143        return this.alpha;
144    }
145
146    /**
147     * Sets the alpha transparency for the background shading, and sends a
148     * {@link RendererChangeEvent} to all registered listeners.
149     *
150     * @param alpha   the alpha (in the range 0.0f to 1.0f).
151     *
152     * @see #getAlpha()
153     */
154    public void setAlpha(float alpha) {
155        if (alpha < 0.0f || alpha > 1.0f) {
156            throw new IllegalArgumentException(
157                    "Requires 'alpha' in the range 0.0 to 1.0.");
158        }
159        this.alpha = alpha;
160        fireChangeEvent();
161    }
162
163    /**
164     * This method is overridden so that this flag cannot be changed---it is
165     * set to <code>true</code> for this renderer.
166     *
167     * @param flag  ignored.
168     */
169    @Override
170    public void setDrawSeriesLineAsPath(boolean flag) {
171        // ignore
172    }
173
174    /**
175     * Returns the range of values the renderer requires to display all the
176     * items from the specified dataset.
177     *
178     * @param dataset  the dataset (<code>null</code> permitted).
179     *
180     * @return The range (<code>null</code> if the dataset is <code>null</code>
181     *         or empty).
182     */
183    @Override
184    public Range findRangeBounds(XYDataset dataset) {
185        return findRangeBounds(dataset, true);
186    }
187
188    /**
189     * Initialises and returns a state object that can be passed to each
190     * invocation of the {@link #drawItem} method.
191     *
192     * @param g2  the graphics target.
193     * @param dataArea  the data area.
194     * @param plot  the plot.
195     * @param dataset  the dataset.
196     * @param info  the plot rendering info.
197     *
198     * @return A newly initialised state object.
199     */
200    @Override
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    @Override
216    public int getPassCount() {
217        return 3;
218    }
219
220    /**
221     * Returns <code>true</code> if this is the pass where the shapes are
222     * drawn.
223     *
224     * @param pass  the pass index.
225     *
226     * @return A boolean.
227     *
228     * @see #isLinePass(int)
229     */
230    @Override
231    protected boolean isItemPass(int pass) {
232        return (pass == 2);
233    }
234
235    /**
236     * Returns <code>true</code> if this is the pass where the lines are
237     * drawn.
238     *
239     * @param pass  the pass index.
240     *
241     * @return A boolean.
242     *
243     * @see #isItemPass(int)
244     */
245    @Override
246    protected boolean isLinePass(int pass) {
247        return (pass == 1);
248    }
249
250    /**
251     * Draws the visual representation of a single data item.
252     *
253     * @param g2  the graphics device.
254     * @param state  the renderer state.
255     * @param dataArea  the area within which the data is being drawn.
256     * @param info  collects information about the drawing.
257     * @param plot  the plot (can be used to obtain standard color
258     *              information etc).
259     * @param domainAxis  the domain axis.
260     * @param rangeAxis  the range axis.
261     * @param dataset  the dataset.
262     * @param series  the series index (zero-based).
263     * @param item  the item index (zero-based).
264     * @param crosshairState  crosshair information for the plot
265     *                        (<code>null</code> permitted).
266     * @param pass  the pass index.
267     */
268    @Override
269    public void drawItem(Graphics2D g2, XYItemRendererState state,
270            Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
271            ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset,
272            int series, int item, CrosshairState crosshairState, 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(GeneralPath.WIND_NON_ZERO,
315                        drState.lowerCoordinates.size() 
316                        + drState.upperCoordinates.size());
317                double[] coords = (double[]) drState.lowerCoordinates.get(0);
318                area.moveTo((float) coords[0], (float) coords[1]);
319                for (int i = 1; i < drState.lowerCoordinates.size(); i++) {
320                    coords = (double[]) drState.lowerCoordinates.get(i);
321                    area.lineTo((float) coords[0], (float) coords[1]);
322                }
323                int count = drState.upperCoordinates.size();
324                coords = (double[]) drState.upperCoordinates.get(count - 1);
325                area.lineTo((float) coords[0], (float) coords[1]);
326                for (int i = count - 2; i >= 0; i--) {
327                    coords = (double[]) drState.upperCoordinates.get(i);
328                    area.lineTo((float) coords[0], (float) coords[1]);
329                }
330                area.closePath();
331                g2.fill(area);
332                g2.setComposite(originalComposite);
333
334                drState.lowerCoordinates.clear();
335                drState.upperCoordinates.clear();
336            }
337        }
338        if (isLinePass(pass)) {
339
340            // the following code handles the line for the y-values...it's
341            // all done by code in the super class
342            if (item == 0) {
343                State s = (State) state;
344                s.seriesPath.reset();
345                s.setLastPointGood(false);
346            }
347
348            if (getItemLineVisible(series, item)) {
349                drawPrimaryLineAsPath(state, g2, plot, dataset, pass,
350                        series, item, domainAxis, rangeAxis, dataArea);
351            }
352        }
353
354        // second pass adds shapes where the items are ..
355        else if (isItemPass(pass)) {
356
357            // setup for collecting optional entity info...
358            EntityCollection entities = null;
359            if (info != null) {
360                entities = info.getOwner().getEntityCollection();
361            }
362
363            drawSecondaryPass(g2, plot, dataset, pass, series, item,
364                    domainAxis, dataArea, rangeAxis, crosshairState, entities);
365        }
366    }
367
368    /**
369     * Tests this renderer for equality with an arbitrary object.
370     *
371     * @param obj  the object (<code>null</code> permitted).
372     *
373     * @return A boolean.
374     */
375    @Override
376    public boolean equals(Object obj) {
377        if (obj == this) {
378            return true;
379        }
380        if (!(obj instanceof DeviationRenderer)) {
381            return false;
382        }
383        DeviationRenderer that = (DeviationRenderer) obj;
384        if (this.alpha != that.alpha) {
385            return false;
386        }
387        return super.equals(obj);
388    }
389
390}