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