Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors. 6: * 7: * Project Info: http://www.jfree.org/jfreechart/index.html 8: * 9: * This library is free software; you can redistribute it and/or modify it 10: * under the terms of the GNU Lesser General Public License as published by 11: * the Free Software Foundation; either version 2.1 of the License, or 12: * (at your option) any later version. 13: * 14: * This library is distributed in the hope that it will be useful, but 15: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 16: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 17: * License for more details. 18: * 19: * You should have received a copy of the GNU Lesser General Public 20: * License along with this library; if not, write to the Free Software 21: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 22: * USA. 23: * 24: * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 25: * in the United States and other countries.] 26: * 27: * -------------------- 28: * HighLowRenderer.java 29: * -------------------- 30: * (C) Copyright 2001-2008, by Object Refinery Limited. 31: * 32: * Original Author: David Gilbert (for Object Refinery Limited); 33: * Contributor(s): Richard Atkinson; 34: * Christian W. Zuckschwerdt; 35: * 36: * Changes 37: * ------- 38: * 13-Dec-2001 : Version 1 (DG); 39: * 23-Jan-2002 : Added DrawInfo parameter to drawItem() method (DG); 40: * 28-Mar-2002 : Added a property change listener mechanism so that renderers 41: * no longer need to be immutable (DG); 42: * 09-Apr-2002 : Removed translatedRangeZero from the drawItem() method, and 43: * changed the return type of the drawItem method to void, 44: * reflecting a change in the XYItemRenderer interface. Added 45: * tooltip code to drawItem() method (DG); 46: * 05-Aug-2002 : Small modification to drawItem method to support URLs for 47: * HTML image maps (RA); 48: * 25-Mar-2003 : Implemented Serializable (DG); 49: * 01-May-2003 : Modified drawItem() method signature (DG); 50: * 30-Jul-2003 : Modified entity constructor (CZ); 51: * 31-Jul-2003 : Deprecated constructor (DG); 52: * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG); 53: * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG); 54: * 29-Jan-2004 : Fixed bug (882392) when rendering with 55: * PlotOrientation.HORIZONTAL (DG); 56: * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState. Renamed 57: * XYToolTipGenerator --> XYItemLabelGenerator (DG); 58: * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 59: * getYValue() (DG); 60: * 01-Nov-2005 : Added optional openTickPaint and closeTickPaint settings (DG); 61: * ------------- JFREECHART 1.0.0 --------------------------------------------- 62: * 06-Jul-2006 : Replace dataset methods getX() --> getXValue() (DG); 63: * 08-Apr-2008 : Added findRangeBounds() override (DG); 64: * 29-Apr-2008 : Added tickLength field (DG); 65: * 66: */ 67: 68: package org.jfree.chart.renderer.xy; 69: 70: import java.awt.Graphics2D; 71: import java.awt.Paint; 72: import java.awt.Shape; 73: import java.awt.Stroke; 74: import java.awt.geom.Line2D; 75: import java.awt.geom.Rectangle2D; 76: import java.io.IOException; 77: import java.io.ObjectInputStream; 78: import java.io.ObjectOutputStream; 79: import java.io.Serializable; 80: 81: import org.jfree.chart.axis.ValueAxis; 82: import org.jfree.chart.entity.EntityCollection; 83: import org.jfree.chart.entity.XYItemEntity; 84: import org.jfree.chart.event.RendererChangeEvent; 85: import org.jfree.chart.labels.XYToolTipGenerator; 86: import org.jfree.chart.plot.CrosshairState; 87: import org.jfree.chart.plot.PlotOrientation; 88: import org.jfree.chart.plot.PlotRenderingInfo; 89: import org.jfree.chart.plot.XYPlot; 90: import org.jfree.data.Range; 91: import org.jfree.data.general.DatasetUtilities; 92: import org.jfree.data.xy.OHLCDataset; 93: import org.jfree.data.xy.XYDataset; 94: import org.jfree.io.SerialUtilities; 95: import org.jfree.ui.RectangleEdge; 96: import org.jfree.util.PaintUtilities; 97: import org.jfree.util.PublicCloneable; 98: 99: /** 100: * A renderer that draws high/low/open/close markers on an {@link XYPlot} 101: * (requires a {@link OHLCDataset}). This renderer does not include code to 102: * calculate the crosshair point for the plot. 103: */ 104: public class HighLowRenderer extends AbstractXYItemRenderer 105: implements XYItemRenderer, Cloneable, PublicCloneable, Serializable { 106: 107: /** For serialization. */ 108: private static final long serialVersionUID = -8135673815876552516L; 109: 110: /** A flag that controls whether the open ticks are drawn. */ 111: private boolean drawOpenTicks; 112: 113: /** A flag that controls whether the close ticks are drawn. */ 114: private boolean drawCloseTicks; 115: 116: /** 117: * The paint used for the open ticks (if <code>null</code>, the series 118: * paint is used instead). 119: */ 120: private transient Paint openTickPaint; 121: 122: /** 123: * The paint used for the close ticks (if <code>null</code>, the series 124: * paint is used instead). 125: */ 126: private transient Paint closeTickPaint; 127: 128: /** 129: * The tick length (in Java2D units). 130: * 131: * @since 1.0.10 132: */ 133: private double tickLength; 134: 135: /** 136: * The default constructor. 137: */ 138: public HighLowRenderer() { 139: super(); 140: this.drawOpenTicks = true; 141: this.drawCloseTicks = true; 142: this.tickLength = 2.0; 143: } 144: 145: /** 146: * Returns the flag that controls whether open ticks are drawn. 147: * 148: * @return A boolean. 149: * 150: * @see #getDrawCloseTicks() 151: * @see #setDrawOpenTicks(boolean) 152: */ 153: public boolean getDrawOpenTicks() { 154: return this.drawOpenTicks; 155: } 156: 157: /** 158: * Sets the flag that controls whether open ticks are drawn, and sends a 159: * {@link RendererChangeEvent} to all registered listeners. 160: * 161: * @param draw the flag. 162: * 163: * @see #getDrawOpenTicks() 164: */ 165: public void setDrawOpenTicks(boolean draw) { 166: this.drawOpenTicks = draw; 167: fireChangeEvent(); 168: } 169: 170: /** 171: * Returns the flag that controls whether close ticks are drawn. 172: * 173: * @return A boolean. 174: * 175: * @see #getDrawOpenTicks() 176: * @see #setDrawCloseTicks(boolean) 177: */ 178: public boolean getDrawCloseTicks() { 179: return this.drawCloseTicks; 180: } 181: 182: /** 183: * Sets the flag that controls whether close ticks are drawn, and sends a 184: * {@link RendererChangeEvent} to all registered listeners. 185: * 186: * @param draw the flag. 187: * 188: * @see #getDrawCloseTicks() 189: */ 190: public void setDrawCloseTicks(boolean draw) { 191: this.drawCloseTicks = draw; 192: fireChangeEvent(); 193: } 194: 195: /** 196: * Returns the paint used to draw the ticks for the open values. 197: * 198: * @return The paint used to draw the ticks for the open values (possibly 199: * <code>null</code>). 200: * 201: * @see #setOpenTickPaint(Paint) 202: */ 203: public Paint getOpenTickPaint() { 204: return this.openTickPaint; 205: } 206: 207: /** 208: * Sets the paint used to draw the ticks for the open values and sends a 209: * {@link RendererChangeEvent} to all registered listeners. If you set 210: * this to <code>null</code> (the default), the series paint is used 211: * instead. 212: * 213: * @param paint the paint (<code>null</code> permitted). 214: * 215: * @see #getOpenTickPaint() 216: */ 217: public void setOpenTickPaint(Paint paint) { 218: this.openTickPaint = paint; 219: fireChangeEvent(); 220: } 221: 222: /** 223: * Returns the paint used to draw the ticks for the close values. 224: * 225: * @return The paint used to draw the ticks for the close values (possibly 226: * <code>null</code>). 227: * 228: * @see #setCloseTickPaint(Paint) 229: */ 230: public Paint getCloseTickPaint() { 231: return this.closeTickPaint; 232: } 233: 234: /** 235: * Sets the paint used to draw the ticks for the close values and sends a 236: * {@link RendererChangeEvent} to all registered listeners. If you set 237: * this to <code>null</code> (the default), the series paint is used 238: * instead. 239: * 240: * @param paint the paint (<code>null</code> permitted). 241: * 242: * @see #getCloseTickPaint() 243: */ 244: public void setCloseTickPaint(Paint paint) { 245: this.closeTickPaint = paint; 246: fireChangeEvent(); 247: } 248: 249: /** 250: * Returns the tick length (in Java2D units). 251: * 252: * @return The tick length. 253: * 254: * @since 1.0.10 255: * 256: * @see #setTickLength(double) 257: */ 258: public double getTickLength() { 259: return this.tickLength; 260: } 261: 262: /** 263: * Sets the tick length (in Java2D units) and sends a 264: * {@link RendererChangeEvent} to all registered listeners. 265: * 266: * @param length the length. 267: * 268: * @since 1.0.10 269: * 270: * @see #getTickLength() 271: */ 272: public void setTickLength(double length) { 273: this.tickLength = length; 274: fireChangeEvent(); 275: } 276: 277: /** 278: * Returns the range of values the renderer requires to display all the 279: * items from the specified dataset. 280: * 281: * @param dataset the dataset (<code>null</code> permitted). 282: * 283: * @return The range (<code>null</code> if the dataset is <code>null</code> 284: * or empty). 285: */ 286: public Range findRangeBounds(XYDataset dataset) { 287: if (dataset != null) { 288: return DatasetUtilities.findRangeBounds(dataset, true); 289: } 290: else { 291: return null; 292: } 293: } 294: 295: /** 296: * Draws the visual representation of a single data item. 297: * 298: * @param g2 the graphics device. 299: * @param state the renderer state. 300: * @param dataArea the area within which the plot is being drawn. 301: * @param info collects information about the drawing. 302: * @param plot the plot (can be used to obtain standard color 303: * information etc). 304: * @param domainAxis the domain axis. 305: * @param rangeAxis the range axis. 306: * @param dataset the dataset. 307: * @param series the series index (zero-based). 308: * @param item the item index (zero-based). 309: * @param crosshairState crosshair information for the plot 310: * (<code>null</code> permitted). 311: * @param pass the pass index. 312: */ 313: public void drawItem(Graphics2D g2, 314: XYItemRendererState state, 315: Rectangle2D dataArea, 316: PlotRenderingInfo info, 317: XYPlot plot, 318: ValueAxis domainAxis, 319: ValueAxis rangeAxis, 320: XYDataset dataset, 321: int series, 322: int item, 323: CrosshairState crosshairState, 324: int pass) { 325: 326: double x = dataset.getXValue(series, item); 327: if (!domainAxis.getRange().contains(x)) { 328: return; // the x value is not within the axis range 329: } 330: double xx = domainAxis.valueToJava2D(x, dataArea, 331: plot.getDomainAxisEdge()); 332: 333: // setup for collecting optional entity info... 334: Shape entityArea = null; 335: EntityCollection entities = null; 336: if (info != null) { 337: entities = info.getOwner().getEntityCollection(); 338: } 339: 340: PlotOrientation orientation = plot.getOrientation(); 341: RectangleEdge location = plot.getRangeAxisEdge(); 342: 343: Paint itemPaint = getItemPaint(series, item); 344: Stroke itemStroke = getItemStroke(series, item); 345: g2.setPaint(itemPaint); 346: g2.setStroke(itemStroke); 347: 348: if (dataset instanceof OHLCDataset) { 349: OHLCDataset hld = (OHLCDataset) dataset; 350: 351: double yHigh = hld.getHighValue(series, item); 352: double yLow = hld.getLowValue(series, item); 353: if (!Double.isNaN(yHigh) && !Double.isNaN(yLow)) { 354: double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea, 355: location); 356: double yyLow = rangeAxis.valueToJava2D(yLow, dataArea, 357: location); 358: if (orientation == PlotOrientation.HORIZONTAL) { 359: g2.draw(new Line2D.Double(yyLow, xx, yyHigh, xx)); 360: entityArea = new Rectangle2D.Double(Math.min(yyLow, yyHigh), 361: xx - 1.0, Math.abs(yyHigh - yyLow), 2.0); 362: } 363: else if (orientation == PlotOrientation.VERTICAL) { 364: g2.draw(new Line2D.Double(xx, yyLow, xx, yyHigh)); 365: entityArea = new Rectangle2D.Double(xx - 1.0, 366: Math.min(yyLow, yyHigh), 2.0, 367: Math.abs(yyHigh - yyLow)); 368: } 369: } 370: 371: double delta = getTickLength(); 372: if (domainAxis.isInverted()) { 373: delta = -delta; 374: } 375: if (getDrawOpenTicks()) { 376: double yOpen = hld.getOpenValue(series, item); 377: if (!Double.isNaN(yOpen)) { 378: double yyOpen = rangeAxis.valueToJava2D(yOpen, dataArea, 379: location); 380: if (this.openTickPaint != null) { 381: g2.setPaint(this.openTickPaint); 382: } 383: else { 384: g2.setPaint(itemPaint); 385: } 386: if (orientation == PlotOrientation.HORIZONTAL) { 387: g2.draw(new Line2D.Double(yyOpen, xx + delta, yyOpen, 388: xx)); 389: } 390: else if (orientation == PlotOrientation.VERTICAL) { 391: g2.draw(new Line2D.Double(xx - delta, yyOpen, xx, 392: yyOpen)); 393: } 394: } 395: } 396: 397: if (getDrawCloseTicks()) { 398: double yClose = hld.getCloseValue(series, item); 399: if (!Double.isNaN(yClose)) { 400: double yyClose = rangeAxis.valueToJava2D( 401: yClose, dataArea, location); 402: if (this.closeTickPaint != null) { 403: g2.setPaint(this.closeTickPaint); 404: } 405: else { 406: g2.setPaint(itemPaint); 407: } 408: if (orientation == PlotOrientation.HORIZONTAL) { 409: g2.draw(new Line2D.Double(yyClose, xx, yyClose, 410: xx - delta)); 411: } 412: else if (orientation == PlotOrientation.VERTICAL) { 413: g2.draw(new Line2D.Double(xx, yyClose, xx + delta, 414: yyClose)); 415: } 416: } 417: } 418: 419: } 420: else { 421: // not a HighLowDataset, so just draw a line connecting this point 422: // with the previous point... 423: if (item > 0) { 424: double x0 = dataset.getXValue(series, item - 1); 425: double y0 = dataset.getYValue(series, item - 1); 426: double y = dataset.getYValue(series, item); 427: if (Double.isNaN(x0) || Double.isNaN(y0) || Double.isNaN(y)) { 428: return; 429: } 430: double xx0 = domainAxis.valueToJava2D(x0, dataArea, 431: plot.getDomainAxisEdge()); 432: double yy0 = rangeAxis.valueToJava2D(y0, dataArea, location); 433: double yy = rangeAxis.valueToJava2D(y, dataArea, location); 434: if (orientation == PlotOrientation.HORIZONTAL) { 435: g2.draw(new Line2D.Double(yy0, xx0, yy, xx)); 436: } 437: else if (orientation == PlotOrientation.VERTICAL) { 438: g2.draw(new Line2D.Double(xx0, yy0, xx, yy)); 439: } 440: } 441: } 442: 443: // add an entity for the item... 444: if (entities != null) { 445: String tip = null; 446: XYToolTipGenerator generator = getToolTipGenerator(series, item); 447: if (generator != null) { 448: tip = generator.generateToolTip(dataset, series, item); 449: } 450: String url = null; 451: if (getURLGenerator() != null) { 452: url = getURLGenerator().generateURL(dataset, series, item); 453: } 454: XYItemEntity entity = new XYItemEntity(entityArea, dataset, 455: series, item, tip, url); 456: entities.add(entity); 457: } 458: 459: } 460: 461: /** 462: * Returns a clone of the renderer. 463: * 464: * @return A clone. 465: * 466: * @throws CloneNotSupportedException if the renderer cannot be cloned. 467: */ 468: public Object clone() throws CloneNotSupportedException { 469: return super.clone(); 470: } 471: 472: /** 473: * Tests this renderer for equality with an arbitrary object. 474: * 475: * @param obj the object (<code>null</code> permitted). 476: * 477: * @return A boolean. 478: */ 479: public boolean equals(Object obj) { 480: if (this == obj) { 481: return true; 482: } 483: if (!(obj instanceof HighLowRenderer)) { 484: return false; 485: } 486: HighLowRenderer that = (HighLowRenderer) obj; 487: if (this.drawOpenTicks != that.drawOpenTicks) { 488: return false; 489: } 490: if (this.drawCloseTicks != that.drawCloseTicks) { 491: return false; 492: } 493: if (!PaintUtilities.equal(this.openTickPaint, that.openTickPaint)) { 494: return false; 495: } 496: if (!PaintUtilities.equal(this.closeTickPaint, that.closeTickPaint)) { 497: return false; 498: } 499: if (this.tickLength != that.tickLength) { 500: return false; 501: } 502: if (!super.equals(obj)) { 503: return false; 504: } 505: return true; 506: } 507: 508: /** 509: * Provides serialization support. 510: * 511: * @param stream the input stream. 512: * 513: * @throws IOException if there is an I/O error. 514: * @throws ClassNotFoundException if there is a classpath problem. 515: */ 516: private void readObject(ObjectInputStream stream) 517: throws IOException, ClassNotFoundException { 518: stream.defaultReadObject(); 519: this.openTickPaint = SerialUtilities.readPaint(stream); 520: this.closeTickPaint = SerialUtilities.readPaint(stream); 521: } 522: 523: /** 524: * Provides serialization support. 525: * 526: * @param stream the output stream. 527: * 528: * @throws IOException if there is an I/O error. 529: */ 530: private void writeObject(ObjectOutputStream stream) throws IOException { 531: stream.defaultWriteObject(); 532: SerialUtilities.writePaint(this.openTickPaint, stream); 533: SerialUtilities.writePaint(this.closeTickPaint, stream); 534: } 535: 536: }