001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2009, 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 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025 * in the United States and other countries.]
026 *
027 * -------------------
028 * XYStepRenderer.java
029 * -------------------
030 * (C) Copyright 2002-2009, 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    public void drawItem(Graphics2D g2,
196                         XYItemRendererState state,
197                         Rectangle2D dataArea,
198                         PlotRenderingInfo info,
199                         XYPlot plot,
200                         ValueAxis domainAxis,
201                         ValueAxis rangeAxis,
202                         XYDataset dataset,
203                         int series,
204                         int item,
205                         CrosshairState crosshairState,
206                         int pass) {
207
208        // do nothing if item is not visible
209        if (!getItemVisible(series, item)) {
210            return;
211        }
212
213        PlotOrientation orientation = plot.getOrientation();
214
215        Paint seriesPaint = getItemPaint(series, item);
216        Stroke seriesStroke = getItemStroke(series, item);
217        g2.setPaint(seriesPaint);
218        g2.setStroke(seriesStroke);
219
220        // get the data point...
221        double x1 = dataset.getXValue(series, item);
222        double y1 = dataset.getYValue(series, item);
223
224        RectangleEdge xAxisLocation = plot.getDomainAxisEdge();
225        RectangleEdge yAxisLocation = plot.getRangeAxisEdge();
226        double transX1 = domainAxis.valueToJava2D(x1, dataArea, xAxisLocation);
227        double transY1 = (Double.isNaN(y1) ? Double.NaN
228                : rangeAxis.valueToJava2D(y1, dataArea, yAxisLocation));
229
230        if (pass == 0 && item > 0) {
231            // get the previous data point...
232            double x0 = dataset.getXValue(series, item - 1);
233            double y0 = dataset.getYValue(series, item - 1);
234            double transX0 = domainAxis.valueToJava2D(x0, dataArea,
235                    xAxisLocation);
236            double transY0 = (Double.isNaN(y0) ? Double.NaN
237                    : rangeAxis.valueToJava2D(y0, dataArea, yAxisLocation));
238
239            if (orientation == PlotOrientation.HORIZONTAL) {
240                if (transY0 == transY1) {
241                    // this represents the situation
242                    // for drawing a horizontal bar.
243                    drawLine(g2, state.workingLine, transY0, transX0, transY1,
244                            transX1);
245                }
246                else {  //this handles the need to perform a 'step'.
247
248                    // calculate the step point
249                    double transXs = transX0 + (getStepPoint()
250                            * (transX1 - transX0));
251                    drawLine(g2, state.workingLine, transY0, transX0, transY0,
252                            transXs);
253                    drawLine(g2, state.workingLine, transY0, transXs, transY1,
254                            transXs);
255                    drawLine(g2, state.workingLine, transY1, transXs, transY1,
256                            transX1);
257                }
258            }
259            else if (orientation == PlotOrientation.VERTICAL) {
260                if (transY0 == transY1) { // this represents the situation
261                                          // for drawing a horizontal bar.
262                    drawLine(g2, state.workingLine, transX0, transY0, transX1,
263                            transY1);
264                }
265                else {  //this handles the need to perform a 'step'.
266                    // calculate the step point
267                    double transXs = transX0 + (getStepPoint()
268                            * (transX1 - transX0));
269                    drawLine(g2, state.workingLine, transX0, transY0, transXs,
270                            transY0);
271                    drawLine(g2, state.workingLine, transXs, transY0, transXs,
272                            transY1);
273                    drawLine(g2, state.workingLine, transXs, transY1, transX1,
274                            transY1);
275                }
276            }
277
278            // submit this data item as a candidate for the crosshair point
279            int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
280            int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
281            updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex,
282                    rangeAxisIndex, transX1, transY1, orientation);
283
284            // collect entity and tool tip information...
285            EntityCollection entities = state.getEntityCollection();
286            if (entities != null) {
287                addEntity(entities, null, dataset, series, item, transX1,
288                        transY1);
289            }
290
291        }
292
293        if (pass == 1) {
294            // draw the item label if there is one...
295            if (isItemLabelVisible(series, item)) {
296                double xx = transX1;
297                double yy = transY1;
298                if (orientation == PlotOrientation.HORIZONTAL) {
299                    xx = transY1;
300                    yy = transX1;
301                }
302                drawItemLabel(g2, orientation, dataset, series, item, xx, yy,
303                        (y1 < 0.0));
304            }
305        }
306    }
307
308    /**
309     * A utility method that draws a line but only if none of the coordinates
310     * are NaN values.
311     *
312     * @param g2  the graphics target.
313     * @param line  the line object.
314     * @param x0  the x-coordinate for the starting point of the line.
315     * @param y0  the y-coordinate for the starting point of the line.
316     * @param x1  the x-coordinate for the ending point of the line.
317     * @param y1  the y-coordinate for the ending point of the line.
318     */
319    private void drawLine(Graphics2D g2, Line2D line, double x0, double y0,
320            double x1, double y1) {
321        if (Double.isNaN(x0) || Double.isNaN(x1) || Double.isNaN(y0)
322                || Double.isNaN(y1)) {
323            return;
324        }
325        line.setLine(x0, y0, x1, y1);
326        g2.draw(line);
327    }
328
329    /**
330     * Tests this renderer for equality with an arbitrary object.
331     *
332     * @param obj  the object (<code>null</code> permitted).
333     *
334     * @return A boolean.
335     */
336    public boolean equals(Object obj) {
337        if (obj == this) {
338            return true;
339        }
340        if (!(obj instanceof XYLineAndShapeRenderer)) {
341            return false;
342        }
343        XYStepRenderer that = (XYStepRenderer) obj;
344        if (this.stepPoint != that.stepPoint) {
345            return false;
346        }
347        return super.equals(obj);
348    }
349
350    /**
351     * Returns a hash code for this instance.
352     *
353     * @return A hash code.
354     */
355    public int hashCode() {
356        return HashUtilities.hashCode(super.hashCode(), this.stepPoint);
357    }
358
359    /**
360     * Returns a clone of the renderer.
361     *
362     * @return A clone.
363     *
364     * @throws CloneNotSupportedException  if the renderer cannot be cloned.
365     */
366    public Object clone() throws CloneNotSupportedException {
367        return super.clone();
368    }
369
370}