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