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 * HighLowRenderer.java 029 * -------------------- 030 * (C) Copyright 2001-2008, 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 069 package org.jfree.chart.renderer.xy; 070 071 import java.awt.Graphics2D; 072 import java.awt.Paint; 073 import java.awt.Shape; 074 import java.awt.Stroke; 075 import java.awt.geom.Line2D; 076 import java.awt.geom.Rectangle2D; 077 import java.io.IOException; 078 import java.io.ObjectInputStream; 079 import java.io.ObjectOutputStream; 080 import java.io.Serializable; 081 082 import org.jfree.chart.axis.ValueAxis; 083 import org.jfree.chart.entity.EntityCollection; 084 import org.jfree.chart.event.RendererChangeEvent; 085 import org.jfree.chart.plot.CrosshairState; 086 import org.jfree.chart.plot.PlotOrientation; 087 import org.jfree.chart.plot.PlotRenderingInfo; 088 import org.jfree.chart.plot.XYPlot; 089 import org.jfree.data.Range; 090 import org.jfree.data.general.DatasetUtilities; 091 import org.jfree.data.xy.OHLCDataset; 092 import org.jfree.data.xy.XYDataset; 093 import org.jfree.io.SerialUtilities; 094 import org.jfree.ui.RectangleEdge; 095 import org.jfree.util.PaintUtilities; 096 import 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 */ 110 public 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 public Range findRangeBounds(XYDataset dataset) { 293 if (dataset != null) { 294 return DatasetUtilities.findRangeBounds(dataset, true); 295 } 296 else { 297 return null; 298 } 299 } 300 301 /** 302 * Draws the visual representation of a single data item. 303 * 304 * @param g2 the graphics device. 305 * @param state the renderer state. 306 * @param dataArea the area within which the plot is being drawn. 307 * @param info collects information about the drawing. 308 * @param plot the plot (can be used to obtain standard color 309 * information etc). 310 * @param domainAxis the domain axis. 311 * @param rangeAxis the range axis. 312 * @param dataset the dataset. 313 * @param series the series index (zero-based). 314 * @param item the item index (zero-based). 315 * @param crosshairState crosshair information for the plot 316 * (<code>null</code> permitted). 317 * @param pass the pass index. 318 */ 319 public void drawItem(Graphics2D g2, 320 XYItemRendererState state, 321 Rectangle2D dataArea, 322 PlotRenderingInfo info, 323 XYPlot plot, 324 ValueAxis domainAxis, 325 ValueAxis rangeAxis, 326 XYDataset dataset, 327 int series, 328 int item, 329 CrosshairState crosshairState, 330 int pass) { 331 332 double x = dataset.getXValue(series, item); 333 if (!domainAxis.getRange().contains(x)) { 334 return; // the x value is not within the axis range 335 } 336 double xx = domainAxis.valueToJava2D(x, dataArea, 337 plot.getDomainAxisEdge()); 338 339 // setup for collecting optional entity info... 340 Shape entityArea = null; 341 EntityCollection entities = null; 342 if (info != null) { 343 entities = info.getOwner().getEntityCollection(); 344 } 345 346 PlotOrientation orientation = plot.getOrientation(); 347 RectangleEdge location = plot.getRangeAxisEdge(); 348 349 Paint itemPaint = getItemPaint(series, item); 350 Stroke itemStroke = getItemStroke(series, item); 351 g2.setPaint(itemPaint); 352 g2.setStroke(itemStroke); 353 354 if (dataset instanceof OHLCDataset) { 355 OHLCDataset hld = (OHLCDataset) dataset; 356 357 double yHigh = hld.getHighValue(series, item); 358 double yLow = hld.getLowValue(series, item); 359 if (!Double.isNaN(yHigh) && !Double.isNaN(yLow)) { 360 double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea, 361 location); 362 double yyLow = rangeAxis.valueToJava2D(yLow, dataArea, 363 location); 364 if (orientation == PlotOrientation.HORIZONTAL) { 365 g2.draw(new Line2D.Double(yyLow, xx, yyHigh, xx)); 366 entityArea = new Rectangle2D.Double(Math.min(yyLow, yyHigh), 367 xx - 1.0, Math.abs(yyHigh - yyLow), 2.0); 368 } 369 else if (orientation == PlotOrientation.VERTICAL) { 370 g2.draw(new Line2D.Double(xx, yyLow, xx, yyHigh)); 371 entityArea = new Rectangle2D.Double(xx - 1.0, 372 Math.min(yyLow, yyHigh), 2.0, 373 Math.abs(yyHigh - yyLow)); 374 } 375 } 376 377 double delta = getTickLength(); 378 if (domainAxis.isInverted()) { 379 delta = -delta; 380 } 381 if (getDrawOpenTicks()) { 382 double yOpen = hld.getOpenValue(series, item); 383 if (!Double.isNaN(yOpen)) { 384 double yyOpen = rangeAxis.valueToJava2D(yOpen, dataArea, 385 location); 386 if (this.openTickPaint != null) { 387 g2.setPaint(this.openTickPaint); 388 } 389 else { 390 g2.setPaint(itemPaint); 391 } 392 if (orientation == PlotOrientation.HORIZONTAL) { 393 g2.draw(new Line2D.Double(yyOpen, xx + delta, yyOpen, 394 xx)); 395 } 396 else if (orientation == PlotOrientation.VERTICAL) { 397 g2.draw(new Line2D.Double(xx - delta, yyOpen, xx, 398 yyOpen)); 399 } 400 } 401 } 402 403 if (getDrawCloseTicks()) { 404 double yClose = hld.getCloseValue(series, item); 405 if (!Double.isNaN(yClose)) { 406 double yyClose = rangeAxis.valueToJava2D( 407 yClose, dataArea, location); 408 if (this.closeTickPaint != null) { 409 g2.setPaint(this.closeTickPaint); 410 } 411 else { 412 g2.setPaint(itemPaint); 413 } 414 if (orientation == PlotOrientation.HORIZONTAL) { 415 g2.draw(new Line2D.Double(yyClose, xx, yyClose, 416 xx - delta)); 417 } 418 else if (orientation == PlotOrientation.VERTICAL) { 419 g2.draw(new Line2D.Double(xx, yyClose, xx + delta, 420 yyClose)); 421 } 422 } 423 } 424 425 } 426 else { 427 // not a HighLowDataset, so just draw a line connecting this point 428 // with the previous point... 429 if (item > 0) { 430 double x0 = dataset.getXValue(series, item - 1); 431 double y0 = dataset.getYValue(series, item - 1); 432 double y = dataset.getYValue(series, item); 433 if (Double.isNaN(x0) || Double.isNaN(y0) || Double.isNaN(y)) { 434 return; 435 } 436 double xx0 = domainAxis.valueToJava2D(x0, dataArea, 437 plot.getDomainAxisEdge()); 438 double yy0 = rangeAxis.valueToJava2D(y0, dataArea, location); 439 double yy = rangeAxis.valueToJava2D(y, dataArea, location); 440 if (orientation == PlotOrientation.HORIZONTAL) { 441 g2.draw(new Line2D.Double(yy0, xx0, yy, xx)); 442 } 443 else if (orientation == PlotOrientation.VERTICAL) { 444 g2.draw(new Line2D.Double(xx0, yy0, xx, yy)); 445 } 446 } 447 } 448 449 if (entities != null) { 450 addEntity(entities, entityArea, dataset, series, item, 0.0, 0.0); 451 } 452 453 } 454 455 /** 456 * Returns a clone of the renderer. 457 * 458 * @return A clone. 459 * 460 * @throws CloneNotSupportedException if the renderer cannot be cloned. 461 */ 462 public Object clone() throws CloneNotSupportedException { 463 return super.clone(); 464 } 465 466 /** 467 * Tests this renderer for equality with an arbitrary object. 468 * 469 * @param obj the object (<code>null</code> permitted). 470 * 471 * @return A boolean. 472 */ 473 public boolean equals(Object obj) { 474 if (this == obj) { 475 return true; 476 } 477 if (!(obj instanceof HighLowRenderer)) { 478 return false; 479 } 480 HighLowRenderer that = (HighLowRenderer) obj; 481 if (this.drawOpenTicks != that.drawOpenTicks) { 482 return false; 483 } 484 if (this.drawCloseTicks != that.drawCloseTicks) { 485 return false; 486 } 487 if (!PaintUtilities.equal(this.openTickPaint, that.openTickPaint)) { 488 return false; 489 } 490 if (!PaintUtilities.equal(this.closeTickPaint, that.closeTickPaint)) { 491 return false; 492 } 493 if (this.tickLength != that.tickLength) { 494 return false; 495 } 496 if (!super.equals(obj)) { 497 return false; 498 } 499 return true; 500 } 501 502 /** 503 * Provides serialization support. 504 * 505 * @param stream the input stream. 506 * 507 * @throws IOException if there is an I/O error. 508 * @throws ClassNotFoundException if there is a classpath problem. 509 */ 510 private void readObject(ObjectInputStream stream) 511 throws IOException, ClassNotFoundException { 512 stream.defaultReadObject(); 513 this.openTickPaint = SerialUtilities.readPaint(stream); 514 this.closeTickPaint = SerialUtilities.readPaint(stream); 515 } 516 517 /** 518 * Provides serialization support. 519 * 520 * @param stream the output stream. 521 * 522 * @throws IOException if there is an I/O error. 523 */ 524 private void writeObject(ObjectOutputStream stream) throws IOException { 525 stream.defaultWriteObject(); 526 SerialUtilities.writePaint(this.openTickPaint, stream); 527 SerialUtilities.writePaint(this.closeTickPaint, stream); 528 } 529 530 }