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 * HighLowRenderer.java 029 * -------------------- 030 * (C) Copyright 2001-present, by David Gilbert. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): Richard Atkinson; 034 * Christian W. Zuckschwerdt; 035 * 036 */ 037 038package org.jfree.chart.renderer.xy; 039 040import java.awt.Graphics2D; 041import java.awt.Paint; 042import java.awt.Shape; 043import java.awt.Stroke; 044import java.awt.geom.Line2D; 045import java.awt.geom.Rectangle2D; 046import java.io.IOException; 047import java.io.ObjectInputStream; 048import java.io.ObjectOutputStream; 049import java.io.Serializable; 050 051import org.jfree.chart.axis.ValueAxis; 052import org.jfree.chart.entity.EntityCollection; 053import org.jfree.chart.event.RendererChangeEvent; 054import org.jfree.chart.plot.CrosshairState; 055import org.jfree.chart.plot.PlotOrientation; 056import org.jfree.chart.plot.PlotRenderingInfo; 057import org.jfree.chart.plot.XYPlot; 058import org.jfree.chart.ui.RectangleEdge; 059import org.jfree.chart.util.PaintUtils; 060import org.jfree.chart.util.PublicCloneable; 061import org.jfree.chart.util.SerialUtils; 062import org.jfree.data.Range; 063import org.jfree.data.general.DatasetUtils; 064import org.jfree.data.xy.OHLCDataset; 065import org.jfree.data.xy.XYDataset; 066 067/** 068 * A renderer that draws high/low/open/close markers on an {@link XYPlot} 069 * (requires a {@link OHLCDataset}). This renderer does not include code to 070 * calculate the crosshair point for the plot. 071 * 072 * The example shown here is generated by the {@code HighLowChartDemo1.java} 073 * program included in the JFreeChart Demo Collection: 074 * <br><br> 075 * <img src="doc-files/HighLowRendererSample.png" alt="HighLowRendererSample.png"> 076 */ 077public class HighLowRenderer extends AbstractXYItemRenderer 078 implements XYItemRenderer, Cloneable, PublicCloneable, Serializable { 079 080 /** For serialization. */ 081 private static final long serialVersionUID = -8135673815876552516L; 082 083 /** A flag that controls whether the open ticks are drawn. */ 084 private boolean drawOpenTicks; 085 086 /** A flag that controls whether the close ticks are drawn. */ 087 private boolean drawCloseTicks; 088 089 /** 090 * The paint used for the open ticks (if {@code null}, the series 091 * paint is used instead). 092 */ 093 private transient Paint openTickPaint; 094 095 /** 096 * The paint used for the close ticks (if {@code null}, the series 097 * paint is used instead). 098 */ 099 private transient Paint closeTickPaint; 100 101 /** 102 * The tick length (in Java2D units). 103 */ 104 private double tickLength; 105 106 /** 107 * The default constructor. 108 */ 109 public HighLowRenderer() { 110 super(); 111 this.drawOpenTicks = true; 112 this.drawCloseTicks = true; 113 this.tickLength = 2.0; 114 } 115 116 /** 117 * Returns the flag that controls whether open ticks are drawn. 118 * 119 * @return A boolean. 120 * 121 * @see #getDrawCloseTicks() 122 * @see #setDrawOpenTicks(boolean) 123 */ 124 public boolean getDrawOpenTicks() { 125 return this.drawOpenTicks; 126 } 127 128 /** 129 * Sets the flag that controls whether open ticks are drawn, and sends a 130 * {@link RendererChangeEvent} to all registered listeners. 131 * 132 * @param draw the flag. 133 * 134 * @see #getDrawOpenTicks() 135 */ 136 public void setDrawOpenTicks(boolean draw) { 137 this.drawOpenTicks = draw; 138 fireChangeEvent(); 139 } 140 141 /** 142 * Returns the flag that controls whether close ticks are drawn. 143 * 144 * @return A boolean. 145 * 146 * @see #getDrawOpenTicks() 147 * @see #setDrawCloseTicks(boolean) 148 */ 149 public boolean getDrawCloseTicks() { 150 return this.drawCloseTicks; 151 } 152 153 /** 154 * Sets the flag that controls whether close ticks are drawn, and sends a 155 * {@link RendererChangeEvent} to all registered listeners. 156 * 157 * @param draw the flag. 158 * 159 * @see #getDrawCloseTicks() 160 */ 161 public void setDrawCloseTicks(boolean draw) { 162 this.drawCloseTicks = draw; 163 fireChangeEvent(); 164 } 165 166 /** 167 * Returns the paint used to draw the ticks for the open values. 168 * 169 * @return The paint used to draw the ticks for the open values (possibly 170 * {@code null}). 171 * 172 * @see #setOpenTickPaint(Paint) 173 */ 174 public Paint getOpenTickPaint() { 175 return this.openTickPaint; 176 } 177 178 /** 179 * Sets the paint used to draw the ticks for the open values and sends a 180 * {@link RendererChangeEvent} to all registered listeners. If you set 181 * this to {@code null} (the default), the series paint is used 182 * instead. 183 * 184 * @param paint the paint ({@code null} permitted). 185 * 186 * @see #getOpenTickPaint() 187 */ 188 public void setOpenTickPaint(Paint paint) { 189 this.openTickPaint = paint; 190 fireChangeEvent(); 191 } 192 193 /** 194 * Returns the paint used to draw the ticks for the close values. 195 * 196 * @return The paint used to draw the ticks for the close values (possibly 197 * {@code null}). 198 * 199 * @see #setCloseTickPaint(Paint) 200 */ 201 public Paint getCloseTickPaint() { 202 return this.closeTickPaint; 203 } 204 205 /** 206 * Sets the paint used to draw the ticks for the close values and sends a 207 * {@link RendererChangeEvent} to all registered listeners. If you set 208 * this to {@code null} (the default), the series paint is used 209 * instead. 210 * 211 * @param paint the paint ({@code null} permitted). 212 * 213 * @see #getCloseTickPaint() 214 */ 215 public void setCloseTickPaint(Paint paint) { 216 this.closeTickPaint = paint; 217 fireChangeEvent(); 218 } 219 220 /** 221 * Returns the tick length (in Java2D units). 222 * 223 * @return The tick length. 224 * 225 * @see #setTickLength(double) 226 */ 227 public double getTickLength() { 228 return this.tickLength; 229 } 230 231 /** 232 * Sets the tick length (in Java2D units) and sends a 233 * {@link RendererChangeEvent} to all registered listeners. 234 * 235 * @param length the length. 236 * 237 * @see #getTickLength() 238 */ 239 public void setTickLength(double length) { 240 this.tickLength = length; 241 fireChangeEvent(); 242 } 243 244 /** 245 * Returns the range of values the renderer requires to display all the 246 * items from the specified dataset. 247 * 248 * @param dataset the dataset ({@code null} permitted). 249 * 250 * @return The range ({@code null} if the dataset is {@code null} 251 * or empty). 252 */ 253 @Override 254 public Range findRangeBounds(XYDataset dataset) { 255 if (dataset != null) { 256 return DatasetUtils.findRangeBounds(dataset, true); 257 } 258 else { 259 return null; 260 } 261 } 262 263 /** 264 * Draws the visual representation of a single data item. 265 * 266 * @param g2 the graphics device. 267 * @param state the renderer state. 268 * @param dataArea the area within which the plot is being drawn. 269 * @param info collects information about the drawing. 270 * @param plot the plot (can be used to obtain standard color 271 * information etc). 272 * @param domainAxis the domain axis. 273 * @param rangeAxis the range axis. 274 * @param dataset the dataset. 275 * @param series the series index (zero-based). 276 * @param item the item index (zero-based). 277 * @param crosshairState crosshair information for the plot 278 * ({@code null} permitted). 279 * @param pass the pass index. 280 */ 281 @Override 282 public void drawItem(Graphics2D g2, XYItemRendererState state, 283 Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, 284 ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, 285 int series, int item, CrosshairState crosshairState, int pass) { 286 287 double x = dataset.getXValue(series, item); 288 if (!domainAxis.getRange().contains(x)) { 289 return; // the x value is not within the axis range 290 } 291 double xx = domainAxis.valueToJava2D(x, dataArea, 292 plot.getDomainAxisEdge()); 293 294 // setup for collecting optional entity info... 295 Shape entityArea = null; 296 EntityCollection entities = null; 297 if (info != null) { 298 entities = info.getOwner().getEntityCollection(); 299 } 300 301 PlotOrientation orientation = plot.getOrientation(); 302 RectangleEdge location = plot.getRangeAxisEdge(); 303 304 Paint itemPaint = getItemPaint(series, item); 305 Stroke itemStroke = getItemStroke(series, item); 306 g2.setPaint(itemPaint); 307 g2.setStroke(itemStroke); 308 309 if (dataset instanceof OHLCDataset) { 310 OHLCDataset hld = (OHLCDataset) dataset; 311 312 double yHigh = hld.getHighValue(series, item); 313 double yLow = hld.getLowValue(series, item); 314 if (!Double.isNaN(yHigh) && !Double.isNaN(yLow)) { 315 double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea, 316 location); 317 double yyLow = rangeAxis.valueToJava2D(yLow, dataArea, 318 location); 319 if (orientation == PlotOrientation.HORIZONTAL) { 320 g2.draw(new Line2D.Double(yyLow, xx, yyHigh, xx)); 321 entityArea = new Rectangle2D.Double(Math.min(yyLow, yyHigh), 322 xx - 1.0, Math.abs(yyHigh - yyLow), 2.0); 323 } 324 else if (orientation == PlotOrientation.VERTICAL) { 325 g2.draw(new Line2D.Double(xx, yyLow, xx, yyHigh)); 326 entityArea = new Rectangle2D.Double(xx - 1.0, 327 Math.min(yyLow, yyHigh), 2.0, 328 Math.abs(yyHigh - yyLow)); 329 } 330 } 331 332 double delta = getTickLength(); 333 if (domainAxis.isInverted()) { 334 delta = -delta; 335 } 336 if (getDrawOpenTicks()) { 337 double yOpen = hld.getOpenValue(series, item); 338 if (!Double.isNaN(yOpen)) { 339 double yyOpen = rangeAxis.valueToJava2D(yOpen, dataArea, 340 location); 341 if (this.openTickPaint != null) { 342 g2.setPaint(this.openTickPaint); 343 } 344 else { 345 g2.setPaint(itemPaint); 346 } 347 if (orientation == PlotOrientation.HORIZONTAL) { 348 g2.draw(new Line2D.Double(yyOpen, xx + delta, yyOpen, 349 xx)); 350 } 351 else if (orientation == PlotOrientation.VERTICAL) { 352 g2.draw(new Line2D.Double(xx - delta, yyOpen, xx, 353 yyOpen)); 354 } 355 } 356 } 357 358 if (getDrawCloseTicks()) { 359 double yClose = hld.getCloseValue(series, item); 360 if (!Double.isNaN(yClose)) { 361 double yyClose = rangeAxis.valueToJava2D( 362 yClose, dataArea, location); 363 if (this.closeTickPaint != null) { 364 g2.setPaint(this.closeTickPaint); 365 } 366 else { 367 g2.setPaint(itemPaint); 368 } 369 if (orientation == PlotOrientation.HORIZONTAL) { 370 g2.draw(new Line2D.Double(yyClose, xx, yyClose, 371 xx - delta)); 372 } 373 else if (orientation == PlotOrientation.VERTICAL) { 374 g2.draw(new Line2D.Double(xx, yyClose, xx + delta, 375 yyClose)); 376 } 377 } 378 } 379 380 } 381 else { 382 // not a HighLowDataset, so just draw a line connecting this point 383 // with the previous point... 384 if (item > 0) { 385 double x0 = dataset.getXValue(series, item - 1); 386 double y0 = dataset.getYValue(series, item - 1); 387 double y = dataset.getYValue(series, item); 388 if (Double.isNaN(x0) || Double.isNaN(y0) || Double.isNaN(y)) { 389 return; 390 } 391 double xx0 = domainAxis.valueToJava2D(x0, dataArea, 392 plot.getDomainAxisEdge()); 393 double yy0 = rangeAxis.valueToJava2D(y0, dataArea, location); 394 double yy = rangeAxis.valueToJava2D(y, dataArea, location); 395 if (orientation == PlotOrientation.HORIZONTAL) { 396 g2.draw(new Line2D.Double(yy0, xx0, yy, xx)); 397 } 398 else if (orientation == PlotOrientation.VERTICAL) { 399 g2.draw(new Line2D.Double(xx0, yy0, xx, yy)); 400 } 401 } 402 } 403 404 if (entities != null) { 405 addEntity(entities, entityArea, dataset, series, item, 0.0, 0.0); 406 } 407 408 } 409 410 /** 411 * Returns a clone of the renderer. 412 * 413 * @return A clone. 414 * 415 * @throws CloneNotSupportedException if the renderer cannot be cloned. 416 */ 417 @Override 418 public Object clone() throws CloneNotSupportedException { 419 return super.clone(); 420 } 421 422 /** 423 * Tests this renderer for equality with an arbitrary object. 424 * 425 * @param obj the object ({@code null} permitted). 426 * 427 * @return A boolean. 428 */ 429 @Override 430 public boolean equals(Object obj) { 431 if (this == obj) { 432 return true; 433 } 434 if (!(obj instanceof HighLowRenderer)) { 435 return false; 436 } 437 HighLowRenderer that = (HighLowRenderer) obj; 438 if (this.drawOpenTicks != that.drawOpenTicks) { 439 return false; 440 } 441 if (this.drawCloseTicks != that.drawCloseTicks) { 442 return false; 443 } 444 if (!PaintUtils.equal(this.openTickPaint, that.openTickPaint)) { 445 return false; 446 } 447 if (!PaintUtils.equal(this.closeTickPaint, that.closeTickPaint)) { 448 return false; 449 } 450 if (this.tickLength != that.tickLength) { 451 return false; 452 } 453 if (!super.equals(obj)) { 454 return false; 455 } 456 return true; 457 } 458 459 /** 460 * Provides serialization support. 461 * 462 * @param stream the input stream. 463 * 464 * @throws IOException if there is an I/O error. 465 * @throws ClassNotFoundException if there is a classpath problem. 466 */ 467 private void readObject(ObjectInputStream stream) 468 throws IOException, ClassNotFoundException { 469 stream.defaultReadObject(); 470 this.openTickPaint = SerialUtils.readPaint(stream); 471 this.closeTickPaint = SerialUtils.readPaint(stream); 472 } 473 474 /** 475 * Provides serialization support. 476 * 477 * @param stream the output stream. 478 * 479 * @throws IOException if there is an I/O error. 480 */ 481 private void writeObject(ObjectOutputStream stream) throws IOException { 482 stream.defaultWriteObject(); 483 SerialUtils.writePaint(this.openTickPaint, stream); 484 SerialUtils.writePaint(this.closeTickPaint, stream); 485 } 486 487}