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