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