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