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 * DeviationStepRenderer.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 org.jfree.chart.axis.ValueAxis;
040import org.jfree.chart.entity.EntityCollection;
041import org.jfree.chart.plot.CrosshairState;
042import org.jfree.chart.plot.PlotOrientation;
043import org.jfree.chart.plot.PlotRenderingInfo;
044import org.jfree.chart.plot.XYPlot;
045import org.jfree.chart.ui.RectangleEdge;
046import org.jfree.data.xy.IntervalXYDataset;
047import org.jfree.data.xy.XYDataset;
048
049import java.awt.*;
050import java.awt.geom.GeneralPath;
051import java.awt.geom.Rectangle2D;
052
053/**
054 * A specialised subclass of the {@link DeviationRenderer} that requires
055 * an {@link IntervalXYDataset} and represents the y-interval by shading an
056 * area behind the y-values on the chart, drawing only horizontal or
057 * vertical lines (steps);
058 *
059 * @since 1.5.1
060 */
061public class DeviationStepRenderer extends DeviationRenderer {
062
063    /**
064     * Creates a new renderer that displays lines and shapes for the data
065     * items, as well as the shaded area for the y-interval.
066     */
067    public DeviationStepRenderer() {
068        super();
069    }
070
071    /**
072     * Creates a new renderer.
073     *
074     * @param lines  show lines between data items?
075     * @param shapes  show a shape for each data item?
076     */
077    public DeviationStepRenderer(boolean lines, boolean shapes) {
078        super(lines, shapes);
079    }
080
081    /**
082     * Draws the visual representation of a single data item.
083     *
084     * @param g2  the graphics device.
085     * @param state  the renderer state.
086     * @param dataArea  the area within which the data is being drawn.
087     * @param info  collects information about the drawing.
088     * @param plot  the plot (can be used to obtain standard color
089     *              information etc).
090     * @param domainAxis  the domain axis.
091     * @param rangeAxis  the range axis.
092     * @param dataset  the dataset.
093     * @param series  the series index (zero-based).
094     * @param item  the item index (zero-based).
095     * @param crosshairState  crosshair information for the plot
096     *                        ({@code null} permitted).
097     * @param pass  the pass index.
098     */
099    @Override
100    public void drawItem(Graphics2D g2, XYItemRendererState state,
101                         Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
102                         ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset,
103                         int series, int item, CrosshairState crosshairState, int pass) {
104
105        // do nothing if item is not visible
106        if (!getItemVisible(series, item)) {
107            return;
108        }
109
110        // first pass draws the shading
111        if (pass == 0) {
112            IntervalXYDataset intervalDataset = (IntervalXYDataset) dataset;
113            State drState = (State) state;
114
115            double x = intervalDataset.getXValue(series, item);
116            double yLow = intervalDataset.getStartYValue(series, item);
117            double yHigh  = intervalDataset.getEndYValue(series, item);
118
119            RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
120            RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
121
122            double xx = domainAxis.valueToJava2D(x, dataArea, xAxisLocation);
123            double yyLow = rangeAxis.valueToJava2D(yLow, dataArea,
124                    yAxisLocation);
125            double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea,
126                    yAxisLocation);
127
128
129            PlotOrientation orientation = plot.getOrientation();
130            if (item > 0 && !Double.isNaN(xx)) {
131                double yLowPrev = intervalDataset.getStartYValue(series, item-1);
132                double yHighPrev  = intervalDataset.getEndYValue(series, item-1);
133                double yyLowPrev = rangeAxis.valueToJava2D(yLowPrev, dataArea,
134                        yAxisLocation);
135                double yyHighPrev = rangeAxis.valueToJava2D(yHighPrev, dataArea,
136                        yAxisLocation);
137
138                if(!Double.isNaN(yyLow) && !Double.isNaN(yyHigh)) {
139                    if (orientation == PlotOrientation.HORIZONTAL) {
140                        drState.lowerCoordinates.add(new double[]{yyLowPrev, xx});
141                        drState.upperCoordinates.add(new double[]{yyHighPrev, xx});
142                    } else if (orientation == PlotOrientation.VERTICAL) {
143                        drState.lowerCoordinates.add(new double[]{xx, yyLowPrev});
144                        drState.upperCoordinates.add(new double[]{xx, yyHighPrev});
145                    }
146                }
147            }
148
149            boolean intervalGood = !Double.isNaN(xx) && !Double.isNaN(yLow) && !Double.isNaN(yHigh);
150            if (intervalGood) {
151                if (orientation == PlotOrientation.HORIZONTAL) {
152                    drState.lowerCoordinates.add(new double[]{yyLow, xx});
153                    drState.upperCoordinates.add(new double[]{yyHigh, xx});
154                } else if (orientation == PlotOrientation.VERTICAL) {
155                    drState.lowerCoordinates.add(new double[]{xx, yyLow});
156                    drState.upperCoordinates.add(new double[]{xx, yyHigh});
157                }
158            }
159
160            if (item == (dataset.getItemCount(series) - 1) ||
161                (!intervalGood && drState.lowerCoordinates.size() > 1)) {
162                // draw items so far, either we reached the end of the series or the next interval is invalid
163                // last item in series, draw the lot...
164                // set up the alpha-transparency...
165                Composite originalComposite = g2.getComposite();
166                g2.setComposite(AlphaComposite.getInstance(
167                        AlphaComposite.SRC_OVER, this.alpha));
168                g2.setPaint(getItemFillPaint(series, item));
169                GeneralPath area = new GeneralPath(GeneralPath.WIND_NON_ZERO,
170                        drState.lowerCoordinates.size()
171                                + drState.upperCoordinates.size());
172                double[] coords = (double[]) drState.lowerCoordinates.get(0);
173                area.moveTo((float) coords[0], (float) coords[1]);
174                for (int i = 1; i < drState.lowerCoordinates.size(); i++) {
175                    coords = (double[]) drState.lowerCoordinates.get(i);
176                    area.lineTo((float) coords[0], (float) coords[1]);
177                }
178                int count = drState.upperCoordinates.size();
179                coords = (double[]) drState.upperCoordinates.get(count - 1);
180                area.lineTo((float) coords[0], (float) coords[1]);
181                for (int i = count - 2; i >= 0; i--) {
182                    coords = (double[]) drState.upperCoordinates.get(i);
183                    area.lineTo((float) coords[0], (float) coords[1]);
184                }
185                area.closePath();
186                g2.fill(area);
187                g2.setComposite(originalComposite);
188
189                drState.lowerCoordinates.clear();
190                drState.upperCoordinates.clear();
191            }
192        }
193        if (isLinePass(pass)) {
194
195            // the following code handles the line for the y-values...it's
196            // all done by code in the super class
197            if (item == 0) {
198                State s = (State) state;
199                s.seriesPath.reset();
200                s.setLastPointGood(false);
201            }
202
203            if (getItemLineVisible(series, item)) {
204                drawPrimaryLineAsPath(state, g2, plot, dataset, pass,
205                        series, item, domainAxis, rangeAxis, dataArea);
206            }
207        }
208
209        // second pass adds shapes where the items are ..
210        else if (isItemPass(pass)) {
211
212            // setup for collecting optional entity info...
213            EntityCollection entities = null;
214            if (info != null) {
215                entities = info.getOwner().getEntityCollection();
216            }
217
218            drawSecondaryPass(g2, plot, dataset, pass, series, item,
219                    domainAxis, dataArea, rangeAxis, crosshairState, entities);
220        }
221    }
222
223    /**
224     * Draws the item (first pass). This method draws the lines
225     * connecting the items. Instead of drawing separate lines,
226     * a {@code GeneralPath} is constructed and drawn at the end of
227     * the series painting.
228     *
229     * @param g2  the graphics device.
230     * @param state  the renderer state.
231     * @param plot  the plot (can be used to obtain standard color information
232     *              etc).
233     * @param dataset  the dataset.
234     * @param pass  the pass.
235     * @param series  the series index (zero-based).
236     * @param item  the item index (zero-based).
237     * @param domainAxis  the domain axis.
238     * @param rangeAxis  the range axis.
239     * @param dataArea  the area within which the data is being drawn.
240     */
241    @Override
242    protected void drawPrimaryLineAsPath(XYItemRendererState state,
243                                         Graphics2D g2, XYPlot plot, XYDataset dataset, int pass,
244                                         int series, int item, ValueAxis domainAxis, ValueAxis rangeAxis,
245                                         Rectangle2D dataArea) {
246
247        RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
248        RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
249
250        // get the data point...
251        double x1 = dataset.getXValue(series, item);
252        double y1 = dataset.getYValue(series, item);
253        double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
254        double transY1 = rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation);
255
256        XYLineAndShapeRenderer.State s = (XYLineAndShapeRenderer.State) state;
257        // update path to reflect latest point
258        if (!Double.isNaN(transX1) && !Double.isNaN(transY1)) {
259            float x = (float) transX1;
260            float y = (float) transY1;
261            PlotOrientation orientation = plot.getOrientation();
262            if (orientation == PlotOrientation.HORIZONTAL) {
263                x = (float) transY1;
264                y = (float) transX1;
265            }
266            if (s.isLastPointGood()) {
267                if (item > 0) {
268                    if (orientation == PlotOrientation.HORIZONTAL) {
269                        s.seriesPath.lineTo(s.seriesPath.getCurrentPoint().getX(), y);
270                    } else {
271                        s.seriesPath.lineTo(x, s.seriesPath.getCurrentPoint().getY());
272                    }
273                }
274                s.seriesPath.lineTo(x, y);
275            }
276            else {
277                s.seriesPath.moveTo(x, y);
278            }
279            s.setLastPointGood(true);
280        } else {
281            s.setLastPointGood(false);
282        }
283        // if this is the last item, draw the path ...
284        if (item == s.getLastItemIndex()) {
285            // draw path
286            drawFirstPassShape(g2, pass, series, item, s.seriesPath);
287        }
288    }
289
290
291    /**
292     * Tests this renderer for equality with an arbitrary object.
293     *
294     * @param obj  the object ({@code null} permitted).
295     *
296     * @return A boolean.
297     */
298    @Override
299    public boolean equals(Object obj) {
300        if (obj == this) {
301            return true;
302        }
303        if (!(obj instanceof DeviationStepRenderer)) {
304            return false;
305        }
306        DeviationStepRenderer that = (DeviationStepRenderer) obj;
307        if (this.alpha != that.alpha) {
308            return false;
309        }
310        return super.equals(obj);
311    }
312
313}