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    
071    package org.jfree.chart.renderer.xy;
072    
073    import java.awt.Graphics2D;
074    import java.awt.Paint;
075    import java.awt.Stroke;
076    import java.awt.geom.Line2D;
077    import java.awt.geom.Rectangle2D;
078    import java.io.Serializable;
079    
080    import org.jfree.chart.HashUtilities;
081    import org.jfree.chart.axis.ValueAxis;
082    import org.jfree.chart.entity.EntityCollection;
083    import org.jfree.chart.event.RendererChangeEvent;
084    import org.jfree.chart.labels.XYToolTipGenerator;
085    import org.jfree.chart.plot.CrosshairState;
086    import org.jfree.chart.plot.PlotOrientation;
087    import org.jfree.chart.plot.PlotRenderingInfo;
088    import org.jfree.chart.plot.XYPlot;
089    import org.jfree.chart.urls.XYURLGenerator;
090    import org.jfree.data.xy.XYDataset;
091    import org.jfree.ui.RectangleEdge;
092    import 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     */
104    public 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    }