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 * XYStepRenderer.java
029 * -------------------
030 * (C) Copyright 2002-present, 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.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.ui.RectangleEdge;
061import org.jfree.chart.urls.XYURLGenerator;
062import org.jfree.chart.util.LineUtils;
063import org.jfree.chart.util.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    private double stepPoint = 1.0d;
086
087    /**
088     * Constructs a new renderer with no tooltip or URL generation.
089     */
090    public XYStepRenderer() {
091        this(null, null);
092    }
093
094    /**
095     * Constructs a new renderer with the specified tool tip and URL
096     * generators.
097     *
098     * @param toolTipGenerator  the item label generator ({@code null}
099     *     permitted).
100     * @param urlGenerator  the URL generator ({@code null} permitted).
101     */
102    public XYStepRenderer(XYToolTipGenerator toolTipGenerator,
103                          XYURLGenerator urlGenerator) {
104        super();
105        setDefaultToolTipGenerator(toolTipGenerator);
106        setURLGenerator(urlGenerator);
107        setDefaultShapesVisible(false);
108    }
109
110    /**
111     * Returns the fraction of the domain position between two points on which
112     * the step is drawn.  The default is 1.0d, which means the step is drawn
113     * at the domain position of the second`point. If the stepPoint is 0.5d the
114     * step is drawn at half between the two points.
115     *
116     * @return The fraction of the domain position between two points where the
117     *         step is drawn.
118     *
119     * @see #setStepPoint(double)
120     */
121    public double getStepPoint() {
122        return this.stepPoint;
123    }
124
125    /**
126     * Sets the step point and sends a {@link RendererChangeEvent} to all
127     * registered listeners.
128     *
129     * @param stepPoint  the step point (in the range 0.0 to 1.0)
130     *
131     * @see #getStepPoint()
132     */
133    public void setStepPoint(double stepPoint) {
134        if (stepPoint < 0.0d || stepPoint > 1.0d) {
135            throw new IllegalArgumentException(
136                    "Requires stepPoint in [0.0;1.0]");
137        }
138        this.stepPoint = stepPoint;
139        fireChangeEvent();
140    }
141
142    /**
143     * Draws the visual representation of a single data item.
144     *
145     * @param g2  the graphics device.
146     * @param state  the renderer state.
147     * @param dataArea  the area within which the data is being drawn.
148     * @param info  collects information about the drawing.
149     * @param plot  the plot (can be used to obtain standard color
150     *              information etc).
151     * @param domainAxis  the domain axis.
152     * @param rangeAxis  the vertical axis.
153     * @param dataset  the dataset.
154     * @param series  the series index (zero-based).
155     * @param item  the item index (zero-based).
156     * @param crosshairState  crosshair information for the plot
157     *                        ({@code null} permitted).
158     * @param pass  the pass index.
159     */
160    @Override
161    public void drawItem(Graphics2D g2, XYItemRendererState state,
162            Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot,
163            ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset,
164            int series, int item, CrosshairState crosshairState, int pass) {
165
166        // do nothing if item is not visible
167        if (!getItemVisible(series, item)) {
168            return;
169        }
170
171        PlotOrientation orientation = plot.getOrientation();
172
173        Paint seriesPaint = getItemPaint(series, item);
174        Stroke seriesStroke = getItemStroke(series, item);
175        g2.setPaint(seriesPaint);
176        g2.setStroke(seriesStroke);
177
178        // get the data point...
179        double x1 = dataset.getXValue(series, item);
180        double y1 = dataset.getYValue(series, item);
181
182        RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
183        RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
184        double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
185        double transY1 = (Double.isNaN(y1) ? Double.NaN
186                : rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation));
187
188        if (pass == 0 && item > 0) {
189            // get the previous data point...
190            double x0 = dataset.getXValue(series, item - 1);
191            double y0 = dataset.getYValue(series, item - 1);
192            double transX0 = domainAxis.valueToJava2D(x0, dataArea,
193                    xAxisLocation);
194            double transY0 = (Double.isNaN(y0) ? Double.NaN
195                    : rangeAxis.valueToJava2D(y0, dataArea, yAxisLocation));
196
197            if (orientation == PlotOrientation.HORIZONTAL) {
198                if (transY0 == transY1) {
199                    // this represents the situation
200                    // for drawing a horizontal bar.
201                    drawLine(g2, state.workingLine, transY0, transX0, transY1,
202                            transX1, dataArea);
203                }
204                else {  //this handles the need to perform a 'step'.
205
206                    // calculate the step point
207                    double transXs = transX0 + (getStepPoint()
208                            * (transX1 - transX0));
209                    drawLine(g2, state.workingLine, transY0, transX0, transY0,
210                            transXs, dataArea);
211                    drawLine(g2, state.workingLine, transY0, transXs, transY1,
212                            transXs, dataArea);
213                    drawLine(g2, state.workingLine, transY1, transXs, transY1,
214                            transX1, dataArea);
215                }
216            }
217            else if (orientation == PlotOrientation.VERTICAL) {
218                if (transY0 == transY1) { // this represents the situation
219                                          // for drawing a horizontal bar.
220                    drawLine(g2, state.workingLine, transX0, transY0, transX1,
221                            transY1, dataArea);
222                }
223                else {  //this handles the need to perform a 'step'.
224                    // calculate the step point
225                    double transXs = transX0 + (getStepPoint()
226                            * (transX1 - transX0));
227                    drawLine(g2, state.workingLine, transX0, transY0, transXs,
228                            transY0, dataArea);
229                    drawLine(g2, state.workingLine, transXs, transY0, transXs,
230                            transY1, dataArea);
231                    drawLine(g2, state.workingLine, transXs, transY1, transX1,
232                            transY1, dataArea);
233                }
234            }
235
236            // submit this data item as a candidate for the crosshair point
237            int datasetIndex = plot.indexOf(dataset);
238            updateCrosshairValues(crosshairState, x1, y1, datasetIndex,
239                    transX1, transY1, orientation);
240
241            // collect entity and tool tip information...
242            EntityCollection entities = state.getEntityCollection();
243            if (entities != null) {
244                if (orientation == PlotOrientation.HORIZONTAL) {
245                    addEntity(entities, null, dataset, series, item, transY1,
246                            transX1);
247                } else {
248                    addEntity(entities, null, dataset, series, item, transX1,
249                            transY1);
250                }
251            }
252
253        }
254
255        if (pass == 1) {
256            // draw the item label if there is one...
257            if (isItemLabelVisible(series, item)) {
258                double xx = transX1;
259                double yy = transY1;
260                if (orientation == PlotOrientation.HORIZONTAL) {
261                    xx = transY1;
262                    yy = transX1;
263                }
264                drawItemLabel(g2, orientation, dataset, series, item, xx, yy,
265                        (y1 < 0.0));
266            }
267        }
268    }
269
270    /**
271     * A utility method that draws a line but only if none of the coordinates
272     * are NaN values.
273     *
274     * @param g2  the graphics target.
275     * @param line  the line object.
276     * @param x0  the x-coordinate for the starting point of the line.
277     * @param y0  the y-coordinate for the starting point of the line.
278     * @param x1  the x-coordinate for the ending point of the line.
279     * @param y1  the y-coordinate for the ending point of the line.
280     */
281    private void drawLine(Graphics2D g2, Line2D line, double x0, double y0,
282            double x1, double y1, Rectangle2D dataArea) {
283        if (Double.isNaN(x0) || Double.isNaN(x1) || Double.isNaN(y0)
284                || Double.isNaN(y1)) {
285            return;
286        }
287        line.setLine(x0, y0, x1, y1);
288        boolean visible = LineUtils.clipLine(line, dataArea);
289        if (visible) {
290            g2.draw(line);
291        }
292    }
293
294    /**
295     * Tests this renderer for equality with an arbitrary object.
296     *
297     * @param obj  the object ({@code null} permitted).
298     *
299     * @return A boolean.
300     */
301    @Override
302    public boolean equals(Object obj) {
303        if (obj == this) {
304            return true;
305        }
306        if (!(obj instanceof XYLineAndShapeRenderer)) {
307            return false;
308        }
309        XYStepRenderer that = (XYStepRenderer) obj;
310        if (this.stepPoint != that.stepPoint) {
311            return false;
312        }
313        return super.equals(obj);
314    }
315
316    /**
317     * Returns a hash code for this instance.
318     *
319     * @return A hash code.
320     */
321    @Override
322    public int hashCode() {
323        return HashUtils.hashCode(super.hashCode(), this.stepPoint);
324    }
325
326    /**
327     * Returns a clone of the renderer.
328     *
329     * @return A clone.
330     *
331     * @throws CloneNotSupportedException  if the renderer cannot be cloned.
332     */
333    @Override
334    public Object clone() throws CloneNotSupportedException {
335        return super.clone();
336    }
337
338}