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