001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2007, 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-2007, 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 * 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 fireChangeEvent(); 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 fireChangeEvent(); 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 fireChangeEvent(); 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 fireChangeEvent(); 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 double x = dataset.getXValue(series, item); 254 if (!domainAxis.getRange().contains(x)) { 255 return; // the x value is not within the axis range 256 } 257 double xx = domainAxis.valueToJava2D(x, dataArea, 258 plot.getDomainAxisEdge()); 259 260 // setup for collecting optional entity info... 261 Shape entityArea = null; 262 EntityCollection entities = null; 263 if (info != null) { 264 entities = info.getOwner().getEntityCollection(); 265 } 266 267 PlotOrientation orientation = plot.getOrientation(); 268 RectangleEdge location = plot.getRangeAxisEdge(); 269 270 Paint itemPaint = getItemPaint(series, item); 271 Stroke itemStroke = getItemStroke(series, item); 272 g2.setPaint(itemPaint); 273 g2.setStroke(itemStroke); 274 275 if (dataset instanceof OHLCDataset) { 276 OHLCDataset hld = (OHLCDataset) dataset; 277 278 double yHigh = hld.getHighValue(series, item); 279 double yLow = hld.getLowValue(series, item); 280 if (!Double.isNaN(yHigh) && !Double.isNaN(yLow)) { 281 double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea, 282 location); 283 double yyLow = rangeAxis.valueToJava2D(yLow, dataArea, 284 location); 285 if (orientation == PlotOrientation.HORIZONTAL) { 286 g2.draw(new Line2D.Double(yyLow, xx, yyHigh, xx)); 287 entityArea = new Rectangle2D.Double(Math.min(yyLow, yyHigh), 288 xx - 1.0, Math.abs(yyHigh - yyLow), 2.0); 289 } 290 else if (orientation == PlotOrientation.VERTICAL) { 291 g2.draw(new Line2D.Double(xx, yyLow, xx, yyHigh)); 292 entityArea = new Rectangle2D.Double(xx - 1.0, 293 Math.min(yyLow, yyHigh), 2.0, 294 Math.abs(yyHigh - yyLow)); 295 } 296 } 297 298 double delta = 2.0; 299 if (domainAxis.isInverted()) { 300 delta = -delta; 301 } 302 if (getDrawOpenTicks()) { 303 double yOpen = hld.getOpenValue(series, item); 304 if (!Double.isNaN(yOpen)) { 305 double yyOpen = rangeAxis.valueToJava2D(yOpen, dataArea, 306 location); 307 if (this.openTickPaint != null) { 308 g2.setPaint(this.openTickPaint); 309 } 310 else { 311 g2.setPaint(itemPaint); 312 } 313 if (orientation == PlotOrientation.HORIZONTAL) { 314 g2.draw(new Line2D.Double(yyOpen, xx + delta, yyOpen, 315 xx)); 316 } 317 else if (orientation == PlotOrientation.VERTICAL) { 318 g2.draw(new Line2D.Double(xx - delta, yyOpen, xx, 319 yyOpen)); 320 } 321 } 322 } 323 324 if (getDrawCloseTicks()) { 325 double yClose = hld.getCloseValue(series, item); 326 if (!Double.isNaN(yClose)) { 327 double yyClose = rangeAxis.valueToJava2D( 328 yClose, dataArea, location); 329 if (this.closeTickPaint != null) { 330 g2.setPaint(this.closeTickPaint); 331 } 332 else { 333 g2.setPaint(itemPaint); 334 } 335 if (orientation == PlotOrientation.HORIZONTAL) { 336 g2.draw(new Line2D.Double(yyClose, xx, yyClose, 337 xx - delta)); 338 } 339 else if (orientation == PlotOrientation.VERTICAL) { 340 g2.draw(new Line2D.Double(xx, yyClose, xx + delta, 341 yyClose)); 342 } 343 } 344 } 345 346 } 347 else { 348 // not a HighLowDataset, so just draw a line connecting this point 349 // with the previous point... 350 if (item > 0) { 351 double x0 = dataset.getXValue(series, item - 1); 352 double y0 = dataset.getYValue(series, item - 1); 353 double y = dataset.getYValue(series, item); 354 if (Double.isNaN(x0) || Double.isNaN(y0) || Double.isNaN(y)) { 355 return; 356 } 357 double xx0 = domainAxis.valueToJava2D(x0, dataArea, 358 plot.getDomainAxisEdge()); 359 double yy0 = rangeAxis.valueToJava2D(y0, dataArea, location); 360 double yy = rangeAxis.valueToJava2D(y, dataArea, location); 361 if (orientation == PlotOrientation.HORIZONTAL) { 362 g2.draw(new Line2D.Double(yy0, xx0, yy, xx)); 363 } 364 else if (orientation == PlotOrientation.VERTICAL) { 365 g2.draw(new Line2D.Double(xx0, yy0, xx, yy)); 366 } 367 } 368 } 369 370 // add an entity for the item... 371 if (entities != null) { 372 String tip = null; 373 XYToolTipGenerator generator = getToolTipGenerator(series, item); 374 if (generator != null) { 375 tip = generator.generateToolTip(dataset, series, item); 376 } 377 String url = null; 378 if (getURLGenerator() != null) { 379 url = getURLGenerator().generateURL(dataset, series, item); 380 } 381 XYItemEntity entity = new XYItemEntity(entityArea, dataset, 382 series, item, tip, url); 383 entities.add(entity); 384 } 385 386 } 387 388 /** 389 * Returns a clone of the renderer. 390 * 391 * @return A clone. 392 * 393 * @throws CloneNotSupportedException if the renderer cannot be cloned. 394 */ 395 public Object clone() throws CloneNotSupportedException { 396 return super.clone(); 397 } 398 399 /** 400 * Tests this renderer for equality with an arbitrary object. 401 * 402 * @param obj the object (<code>null</code> permitted). 403 * 404 * @return A boolean. 405 */ 406 public boolean equals(Object obj) { 407 if (this == obj) { 408 return true; 409 } 410 if (!(obj instanceof HighLowRenderer)) { 411 return false; 412 } 413 HighLowRenderer that = (HighLowRenderer) obj; 414 if (this.drawOpenTicks != that.drawOpenTicks) { 415 return false; 416 } 417 if (this.drawCloseTicks != that.drawCloseTicks) { 418 return false; 419 } 420 if (!PaintUtilities.equal(this.openTickPaint, that.openTickPaint)) { 421 return false; 422 } 423 if (!PaintUtilities.equal(this.closeTickPaint, that.closeTickPaint)) { 424 return false; 425 } 426 if (!super.equals(obj)) { 427 return false; 428 } 429 return true; 430 } 431 432 /** 433 * Provides serialization support. 434 * 435 * @param stream the input stream. 436 * 437 * @throws IOException if there is an I/O error. 438 * @throws ClassNotFoundException if there is a classpath problem. 439 */ 440 private void readObject(ObjectInputStream stream) 441 throws IOException, ClassNotFoundException { 442 stream.defaultReadObject(); 443 this.openTickPaint = SerialUtilities.readPaint(stream); 444 this.closeTickPaint = SerialUtilities.readPaint(stream); 445 } 446 447 /** 448 * Provides serialization support. 449 * 450 * @param stream the output stream. 451 * 452 * @throws IOException if there is an I/O error. 453 */ 454 private void writeObject(ObjectOutputStream stream) throws IOException { 455 stream.defaultWriteObject(); 456 SerialUtilities.writePaint(this.openTickPaint, stream); 457 SerialUtilities.writePaint(this.closeTickPaint, stream); 458 } 459 460 }