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