001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2022, 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 * XYStepRenderer.java
029 * -------------------
030 * (C) Copyright 2002-2021, by Roger Studner and Contributors.
031 *
032 * Original Author:  Roger Studner;
033 * Contributor(s):   David Gilbert;
034 *                   Matthias Rose;
035 *                   Gerald Struck (fix for bug 1569094);
036 *                   Ulrich Voigt (patch 1874890);
037 *                   Martin Hoeller (contribution to patch 1874890);
038 *                   Matthias Noebl (for Cropster GmbH);
039 *
040 */
041
042package org.jfree.chart.renderer.xy;
043
044import java.awt.Graphics2D;
045import java.awt.Paint;
046import java.awt.Stroke;
047import java.awt.geom.Line2D;
048import java.awt.geom.Rectangle2D;
049import java.io.Serializable;
050
051import org.jfree.chart.internal.HashUtils;
052import org.jfree.chart.axis.ValueAxis;
053import org.jfree.chart.entity.EntityCollection;
054import org.jfree.chart.event.RendererChangeEvent;
055import org.jfree.chart.labels.XYToolTipGenerator;
056import org.jfree.chart.plot.CrosshairState;
057import org.jfree.chart.plot.PlotOrientation;
058import org.jfree.chart.plot.PlotRenderingInfo;
059import org.jfree.chart.plot.XYPlot;
060import org.jfree.chart.api.RectangleEdge;
061import org.jfree.chart.urls.XYURLGenerator;
062import org.jfree.chart.internal.LineUtils;
063import org.jfree.chart.api.PublicCloneable;
064import org.jfree.data.xy.XYDataset;
065
066/**
067 * Line/Step item renderer for an {@link XYPlot}.  This class draws lines
068 * between data points, only allowing horizontal or vertical lines (steps).
069 * The example shown here is generated by the
070 * {@code XYStepRendererDemo1.java} program included in the JFreeChart
071 * demo collection:
072 * <br><br>
073 * <img src="doc-files/XYStepRendererSample.png" alt="XYStepRendererSample.png">
074 */
075public class XYStepRenderer extends XYLineAndShapeRenderer
076        implements XYItemRenderer, Cloneable, PublicCloneable, Serializable {
077
078    /** For serialization. */
079    private static final long serialVersionUID = -8918141928884796108L;
080
081    /**
082     * The factor (from 0.0 to 1.0) that determines the position of the
083     * step.
084     *
085     * @since 1.0.10.
086     */
087    private double stepPoint = 1.0d;
088
089    /**
090     * Constructs a new renderer with no tooltip or URL generation.
091     */
092    public XYStepRenderer() {
093        this(null, null);
094    }
095
096    /**
097     * Constructs a new renderer with the specified tool tip and URL
098     * generators.
099     *
100     * @param toolTipGenerator  the item label generator ({@code null}
101     *     permitted).
102     * @param urlGenerator  the URL generator ({@code null} permitted).
103     */
104    public XYStepRenderer(XYToolTipGenerator toolTipGenerator,
105                          XYURLGenerator urlGenerator) {
106        super();
107        setDefaultToolTipGenerator(toolTipGenerator);
108        setURLGenerator(urlGenerator);
109        setDefaultShapesVisible(false);
110    }
111
112    /**
113     * Returns the fraction of the domain position between two points on which
114     * the step is drawn.  The default is 1.0d, which means the step is drawn
115     * at the domain position of the second`point. If the stepPoint is 0.5d the
116     * step is drawn at half between the two points.
117     *
118     * @return The fraction of the domain position between two points where the
119     *         step is drawn.
120     *
121     * @see #setStepPoint(double)
122     *
123     * @since 1.0.10
124     */
125    public double getStepPoint() {
126        return this.stepPoint;
127    }
128
129    /**
130     * Sets the step point and sends a {@link RendererChangeEvent} to all
131     * registered listeners.
132     *
133     * @param stepPoint  the step point (in the range 0.0 to 1.0)
134     *
135     * @see #getStepPoint()
136     *
137     * @since 1.0.10
138     */
139    public void setStepPoint(double stepPoint) {
140        if (stepPoint < 0.0d || stepPoint > 1.0d) {
141            throw new IllegalArgumentException(
142                    "Requires stepPoint in [0.0;1.0]");
143        }
144        this.stepPoint = stepPoint;
145        fireChangeEvent();
146    }
147
148    /**
149     * Draws the visual representation of a single data item.
150     *
151     * @param g2  the graphics device.
152     * @param state  the renderer state.
153     * @param dataArea  the area within which the data is being drawn.
154     * @param info  collects information about the drawing.
155     * @param plot  the plot (can be used to obtain standard color
156     *              information etc).
157     * @param domainAxis  the domain axis.
158     * @param rangeAxis  the vertical axis.
159     * @param dataset  the dataset.
160     * @param series  the series index (zero-based).
161     * @param item  the item index (zero-based).
162     * @param crosshairState  crosshair information for the plot
163     *                        ({@code null} permitted).
164     * @param pass  the pass index.
165     */
166    @Override
167    public void drawItem(Graphics2D g2, XYItemRendererState state,
168            Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
169            ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset,
170            int series, int item, CrosshairState crosshairState, int pass) {
171
172        // do nothing if item is not visible
173        if (!getItemVisible(series, item)) {
174            return;
175        }
176
177        PlotOrientation orientation = plot.getOrientation();
178
179        Paint seriesPaint = getItemPaint(series, item);
180        Stroke seriesStroke = getItemStroke(series, item);
181        g2.setPaint(seriesPaint);
182        g2.setStroke(seriesStroke);
183
184        // get the data point...
185        double x1 = dataset.getXValue(series, item);
186        double y1 = dataset.getYValue(series, item);
187
188        RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
189        RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
190        double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
191        double transY1 = (Double.isNaN(y1) ? Double.NaN
192                : rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation));
193
194        if (pass == 0 && item > 0) {
195            // get the previous data point...
196            double x0 = dataset.getXValue(series, item - 1);
197            double y0 = dataset.getYValue(series, item - 1);
198            double transX0 = domainAxis.valueToJava2D(x0, dataArea,
199                    xAxisLocation);
200            double transY0 = (Double.isNaN(y0) ? Double.NaN
201                    : rangeAxis.valueToJava2D(y0, dataArea, yAxisLocation));
202
203            if (orientation == PlotOrientation.HORIZONTAL) {
204                if (transY0 == transY1) {
205                    // this represents the situation
206                    // for drawing a horizontal bar.
207                    drawLine(g2, state.workingLine, transY0, transX0, transY1,
208                            transX1, dataArea);
209                }
210                else {  //this handles the need to perform a 'step'.
211
212                    // calculate the step point
213                    double transXs = transX0 + (getStepPoint()
214                            * (transX1 - transX0));
215                    drawLine(g2, state.workingLine, transY0, transX0, transY0,
216                            transXs, dataArea);
217                    drawLine(g2, state.workingLine, transY0, transXs, transY1,
218                            transXs, dataArea);
219                    drawLine(g2, state.workingLine, transY1, transXs, transY1,
220                            transX1, dataArea);
221                }
222            }
223            else if (orientation == PlotOrientation.VERTICAL) {
224                if (transY0 == transY1) { // this represents the situation
225                                          // for drawing a horizontal bar.
226                    drawLine(g2, state.workingLine, transX0, transY0, transX1,
227                            transY1, dataArea);
228                }
229                else {  //this handles the need to perform a 'step'.
230                    // calculate the step point
231                    double transXs = transX0 + (getStepPoint()
232                            * (transX1 - transX0));
233                    drawLine(g2, state.workingLine, transX0, transY0, transXs,
234                            transY0, dataArea);
235                    drawLine(g2, state.workingLine, transXs, transY0, transXs,
236                            transY1, dataArea);
237                    drawLine(g2, state.workingLine, transXs, transY1, transX1,
238                            transY1, dataArea);
239                }
240            }
241
242            // submit this data item as a candidate for the crosshair point
243            int datasetIndex = plot.indexOf(dataset);
244            updateCrosshairValues(crosshairState, x1, y1, datasetIndex,
245                    transX1, transY1, orientation);
246
247            // collect entity and tool tip information...
248            EntityCollection entities = state.getEntityCollection();
249            if (entities != null) {
250                if (orientation == PlotOrientation.HORIZONTAL) {
251                    addEntity(entities, null, dataset, series, item, transY1,
252                            transX1);
253                } else {
254                    addEntity(entities, null, dataset, series, item, transX1,
255                            transY1);
256                }
257            }
258
259        }
260
261        if (pass == 1) {
262            // draw the item label if there is one...
263            if (isItemLabelVisible(series, item)) {
264                double xx = transX1;
265                double yy = transY1;
266                if (orientation == PlotOrientation.HORIZONTAL) {
267                    xx = transY1;
268                    yy = transX1;
269                }
270                drawItemLabel(g2, orientation, dataset, series, item, xx, yy,
271                        (y1 < 0.0));
272            }
273        }
274    }
275
276    /**
277     * A utility method that draws a line but only if none of the coordinates
278     * are NaN values.
279     *
280     * @param g2  the graphics target.
281     * @param line  the line object.
282     * @param x0  the x-coordinate for the starting point of the line.
283     * @param y0  the y-coordinate for the starting point of the line.
284     * @param x1  the x-coordinate for the ending point of the line.
285     * @param y1  the y-coordinate for the ending point of the line.
286     */
287    private void drawLine(Graphics2D g2, Line2D line, double x0, double y0,
288            double x1, double y1, Rectangle2D dataArea) {
289        if (Double.isNaN(x0) || Double.isNaN(x1) || Double.isNaN(y0)
290                || Double.isNaN(y1)) {
291            return;
292        }
293        line.setLine(x0, y0, x1, y1);
294        boolean visible = LineUtils.clipLine(line, dataArea);
295        if (visible) {
296            g2.draw(line);
297        }
298    }
299
300    /**
301     * Tests this renderer for equality with an arbitrary object.
302     *
303     * @param obj  the object ({@code null} permitted).
304     *
305     * @return A boolean.
306     */
307    @Override
308    public boolean equals(Object obj) {
309        if (obj == this) {
310            return true;
311        }
312        if (!(obj instanceof XYLineAndShapeRenderer)) {
313            return false;
314        }
315        XYStepRenderer that = (XYStepRenderer) obj;
316        if (this.stepPoint != that.stepPoint) {
317            return false;
318        }
319        return super.equals(obj);
320    }
321
322    /**
323     * Returns a hash code for this instance.
324     *
325     * @return A hash code.
326     */
327    @Override
328    public int hashCode() {
329        return HashUtils.hashCode(super.hashCode(), this.stepPoint);
330    }
331
332    /**
333     * Returns a clone of the renderer.
334     *
335     * @return A clone.
336     *
337     * @throws CloneNotSupportedException  if the renderer cannot be cloned.
338     */
339    @Override
340    public Object clone() throws CloneNotSupportedException {
341        return super.clone();
342    }
343
344}