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