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