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}