001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2006, 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-2006, 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 * $Id: HighLowRenderer.java,v 1.5.2.3 2006/07/06 10:03:34 mungady Exp $ 037 * 038 * Changes 039 * ------- 040 * 13-Dec-2001 : Version 1 (DG); 041 * 23-Jan-2002 : Added DrawInfo parameter to drawItem() method (DG); 042 * 28-Mar-2002 : Added a property change listener mechanism so that renderers 043 * no longer need to be immutable (DG); 044 * 09-Apr-2002 : Removed translatedRangeZero from the drawItem() method, and 045 * changed the return type of the drawItem method to void, 046 * reflecting a change in the XYItemRenderer interface. Added 047 * tooltip code to drawItem() method (DG); 048 * 05-Aug-2002 : Small modification to drawItem method to support URLs for 049 * HTML image maps (RA); 050 * 25-Mar-2003 : Implemented Serializable (DG); 051 * 01-May-2003 : Modified drawItem() method signature (DG); 052 * 30-Jul-2003 : Modified entity constructor (CZ); 053 * 31-Jul-2003 : Deprecated constructor (DG); 054 * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG); 055 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG); 056 * 29-Jan-2004 : Fixed bug (882392) when rendering with 057 * PlotOrientation.HORIZONTAL (DG); 058 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState. Renamed 059 * XYToolTipGenerator --> XYItemLabelGenerator (DG); 060 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 061 * getYValue() (DG); 062 * 01-Nov-2005 : Added optional openTickPaint and closeTickPaint settings (DG); 063 * ------------- JFREECHART 1.0.0 --------------------------------------------- 064 * 06-Jul-2006 : Replace dataset methods getX() --> getXValue() (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.entity.XYItemEntity; 084 import org.jfree.chart.event.RendererChangeEvent; 085 import org.jfree.chart.labels.XYToolTipGenerator; 086 import org.jfree.chart.plot.CrosshairState; 087 import org.jfree.chart.plot.PlotOrientation; 088 import org.jfree.chart.plot.PlotRenderingInfo; 089 import org.jfree.chart.plot.XYPlot; 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, 104 Cloneable, 105 PublicCloneable, 106 Serializable { 107 108 /** For serialization. */ 109 private static final long serialVersionUID = -8135673815876552516L; 110 111 /** A flag that controls whether the open ticks are drawn. */ 112 private boolean drawOpenTicks; 113 114 /** A flag that controls whether the close ticks are drawn. */ 115 private boolean drawCloseTicks; 116 117 /** 118 * The paint used for the open ticks (if <code>null</code>, the series 119 * paint is used instead). 120 */ 121 private transient Paint openTickPaint; 122 123 /** 124 * The paint used for the close ticks (if <code>null</code>, the series 125 * paint is used instead). 126 */ 127 private transient Paint closeTickPaint; 128 129 /** 130 * The default constructor. 131 */ 132 public HighLowRenderer() { 133 super(); 134 this.drawOpenTicks = true; 135 this.drawCloseTicks = true; 136 } 137 138 /** 139 * Returns the flag that controls whether open ticks are drawn. 140 * 141 * @return A boolean. 142 */ 143 public boolean getDrawOpenTicks() { 144 return this.drawOpenTicks; 145 } 146 147 /** 148 * Sets the flag that controls whether open ticks are drawn, and sends a 149 * {@link RendererChangeEvent} to all registered listeners. 150 * 151 * @param draw the flag. 152 */ 153 public void setDrawOpenTicks(boolean draw) { 154 this.drawOpenTicks = draw; 155 notifyListeners(new RendererChangeEvent(this)); 156 } 157 158 /** 159 * Returns the flag that controls whether close ticks are drawn. 160 * 161 * @return A boolean. 162 */ 163 public boolean getDrawCloseTicks() { 164 return this.drawCloseTicks; 165 } 166 167 /** 168 * Sets the flag that controls whether close ticks are drawn, and sends a 169 * {@link RendererChangeEvent} to all registered listeners. 170 * 171 * @param draw the flag. 172 */ 173 public void setDrawCloseTicks(boolean draw) { 174 this.drawCloseTicks = draw; 175 notifyListeners(new RendererChangeEvent(this)); 176 } 177 178 /** 179 * Returns the paint used to draw the ticks for the open values. 180 * 181 * @return The paint used to draw the ticks for the open values (possibly 182 * <code>null</code>). 183 */ 184 public Paint getOpenTickPaint() { 185 return this.openTickPaint; 186 } 187 188 /** 189 * Sets the paint used to draw the ticks for the open values and sends a 190 * {@link RendererChangeEvent} to all registered listeners. If you set 191 * this to <code>null</code> (the default), the series paint is used 192 * instead. 193 * 194 * @param paint the paint (<code>null</code> permitted). 195 */ 196 public void setOpenTickPaint(Paint paint) { 197 this.openTickPaint = paint; 198 notifyListeners(new RendererChangeEvent(this)); 199 } 200 201 /** 202 * Returns the paint used to draw the ticks for the close values. 203 * 204 * @return The paint used to draw the ticks for the close values (possibly 205 * <code>null</code>). 206 */ 207 public Paint getCloseTickPaint() { 208 return this.closeTickPaint; 209 } 210 211 /** 212 * Sets the paint used to draw the ticks for the close values and sends a 213 * {@link RendererChangeEvent} to all registered listeners. If you set 214 * this to <code>null</code> (the default), the series paint is used 215 * instead. 216 * 217 * @param paint the paint (<code>null</code> permitted). 218 */ 219 public void setCloseTickPaint(Paint paint) { 220 this.closeTickPaint = paint; 221 notifyListeners(new RendererChangeEvent(this)); 222 } 223 224 /** 225 * Draws the visual representation of a single data item. 226 * 227 * @param g2 the graphics device. 228 * @param state the renderer state. 229 * @param dataArea the area within which the plot is being drawn. 230 * @param info collects information about the drawing. 231 * @param plot the plot (can be used to obtain standard color 232 * information etc). 233 * @param domainAxis the domain axis. 234 * @param rangeAxis the range axis. 235 * @param dataset the dataset. 236 * @param series the series index (zero-based). 237 * @param item the item index (zero-based). 238 * @param crosshairState crosshair information for the plot 239 * (<code>null</code> permitted). 240 * @param pass the pass index. 241 */ 242 public void drawItem(Graphics2D g2, 243 XYItemRendererState state, 244 Rectangle2D dataArea, 245 PlotRenderingInfo info, 246 XYPlot plot, 247 ValueAxis domainAxis, 248 ValueAxis rangeAxis, 249 XYDataset dataset, 250 int series, 251 int item, 252 CrosshairState crosshairState, 253 int pass) { 254 255 double x = dataset.getXValue(series, item); 256 if (!domainAxis.getRange().contains(x)) { 257 return; // the x value is not within the axis range 258 } 259 double xx = domainAxis.valueToJava2D(x, dataArea, 260 plot.getDomainAxisEdge()); 261 262 // setup for collecting optional entity info... 263 Shape entityArea = null; 264 EntityCollection entities = null; 265 if (info != null) { 266 entities = info.getOwner().getEntityCollection(); 267 } 268 269 PlotOrientation orientation = plot.getOrientation(); 270 RectangleEdge location = plot.getRangeAxisEdge(); 271 272 Paint itemPaint = getItemPaint(series, item); 273 Stroke itemStroke = getItemStroke(series, item); 274 g2.setPaint(itemPaint); 275 g2.setStroke(itemStroke); 276 277 if (dataset instanceof OHLCDataset) { 278 OHLCDataset hld = (OHLCDataset) dataset; 279 280 double yHigh = hld.getHighValue(series, item); 281 double yLow = hld.getLowValue(series, item); 282 if (!Double.isNaN(yHigh) && !Double.isNaN(yLow)) { 283 double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea, 284 location); 285 double yyLow = rangeAxis.valueToJava2D(yLow, dataArea, 286 location); 287 if (orientation == PlotOrientation.HORIZONTAL) { 288 g2.draw(new Line2D.Double(yyLow, xx, yyHigh, xx)); 289 entityArea = new Rectangle2D.Double(Math.min(yyLow, yyHigh), 290 xx - 1.0, Math.abs(yyHigh - yyLow), 2.0); 291 } 292 else if (orientation == PlotOrientation.VERTICAL) { 293 g2.draw(new Line2D.Double(xx, yyLow, xx, yyHigh)); 294 entityArea = new Rectangle2D.Double(xx - 1.0, 295 Math.min(yyLow, yyHigh), 2.0, 296 Math.abs(yyHigh - yyLow)); 297 } 298 } 299 300 double delta = 2.0; 301 if (domainAxis.isInverted()) { 302 delta = -delta; 303 } 304 if (getDrawOpenTicks()) { 305 double yOpen = hld.getOpenValue(series, item); 306 if (!Double.isNaN(yOpen)) { 307 double yyOpen = rangeAxis.valueToJava2D(yOpen, dataArea, 308 location); 309 if (this.openTickPaint != null) { 310 g2.setPaint(this.openTickPaint); 311 } 312 else { 313 g2.setPaint(itemPaint); 314 } 315 if (orientation == PlotOrientation.HORIZONTAL) { 316 g2.draw(new Line2D.Double(yyOpen, xx + delta, yyOpen, 317 xx)); 318 } 319 else if (orientation == PlotOrientation.VERTICAL) { 320 g2.draw(new Line2D.Double(xx - delta, yyOpen, xx, 321 yyOpen)); 322 } 323 } 324 } 325 326 if (getDrawCloseTicks()) { 327 double yClose = hld.getCloseValue(series, item); 328 if (!Double.isNaN(yClose)) { 329 double yyClose = rangeAxis.valueToJava2D( 330 yClose, dataArea, location); 331 if (this.closeTickPaint != null) { 332 g2.setPaint(this.closeTickPaint); 333 } 334 else { 335 g2.setPaint(itemPaint); 336 } 337 if (orientation == PlotOrientation.HORIZONTAL) { 338 g2.draw(new Line2D.Double(yyClose, xx, yyClose, 339 xx - delta)); 340 } 341 else if (orientation == PlotOrientation.VERTICAL) { 342 g2.draw(new Line2D.Double(xx, yyClose, xx + delta, 343 yyClose)); 344 } 345 } 346 } 347 348 } 349 else { 350 // not a HighLowDataset, so just draw a line connecting this point 351 // with the previous point... 352 if (item > 0) { 353 double x0 = dataset.getXValue(series, item - 1); 354 double y0 = dataset.getYValue(series, item - 1); 355 double y = dataset.getYValue(series, item); 356 if (Double.isNaN(x0) || Double.isNaN(y0) || Double.isNaN(y)) { 357 return; 358 } 359 double xx0 = domainAxis.valueToJava2D(x0, dataArea, 360 plot.getDomainAxisEdge()); 361 double yy0 = rangeAxis.valueToJava2D(y0, dataArea, location); 362 double yy = rangeAxis.valueToJava2D(y, dataArea, location); 363 if (orientation == PlotOrientation.HORIZONTAL) { 364 g2.draw(new Line2D.Double(yy0, xx0, yy, xx)); 365 } 366 else if (orientation == PlotOrientation.VERTICAL) { 367 g2.draw(new Line2D.Double(xx0, yy0, xx, yy)); 368 } 369 } 370 } 371 372 // add an entity for the item... 373 if (entities != null) { 374 String tip = null; 375 XYToolTipGenerator generator = getToolTipGenerator(series, item); 376 if (generator != null) { 377 tip = generator.generateToolTip(dataset, series, item); 378 } 379 String url = null; 380 if (getURLGenerator() != null) { 381 url = getURLGenerator().generateURL(dataset, series, item); 382 } 383 XYItemEntity entity = new XYItemEntity(entityArea, dataset, 384 series, item, tip, url); 385 entities.add(entity); 386 } 387 388 } 389 390 /** 391 * Returns a clone of the renderer. 392 * 393 * @return A clone. 394 * 395 * @throws CloneNotSupportedException if the renderer cannot be cloned. 396 */ 397 public Object clone() throws CloneNotSupportedException { 398 return super.clone(); 399 } 400 401 /** 402 * Tests this renderer for equality with an arbitrary object. 403 * 404 * @param obj the object (<code>null</code> permitted). 405 * 406 * @return A boolean. 407 */ 408 public boolean equals(Object obj) { 409 if (this == obj) { 410 return true; 411 } 412 if (!(obj instanceof HighLowRenderer)) { 413 return false; 414 } 415 HighLowRenderer that = (HighLowRenderer) obj; 416 if (this.drawOpenTicks != that.drawOpenTicks) { 417 return false; 418 } 419 if (this.drawCloseTicks != that.drawCloseTicks) { 420 return false; 421 } 422 if (!PaintUtilities.equal(this.openTickPaint, that.openTickPaint)) { 423 return false; 424 } 425 if (!PaintUtilities.equal(this.closeTickPaint, that.closeTickPaint)) { 426 return false; 427 } 428 if (!super.equals(obj)) { 429 return false; 430 } 431 return true; 432 } 433 434 /** 435 * Provides serialization support. 436 * 437 * @param stream the input stream. 438 * 439 * @throws IOException if there is an I/O error. 440 * @throws ClassNotFoundException if there is a classpath problem. 441 */ 442 private void readObject(ObjectInputStream stream) 443 throws IOException, ClassNotFoundException { 444 stream.defaultReadObject(); 445 this.openTickPaint = SerialUtilities.readPaint(stream); 446 this.closeTickPaint = SerialUtilities.readPaint(stream); 447 } 448 449 /** 450 * Provides serialization support. 451 * 452 * @param stream the output stream. 453 * 454 * @throws IOException if there is an I/O error. 455 */ 456 private void writeObject(ObjectOutputStream stream) throws IOException { 457 stream.defaultWriteObject(); 458 SerialUtilities.writePaint(this.openTickPaint, stream); 459 SerialUtilities.writePaint(this.closeTickPaint, stream); 460 } 461 462 }