Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2006, 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-2006, 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: * $Id: HighLowRenderer.java,v 1.5.2.3 2006/07/06 10:03:34 mungady Exp $ 37: * 38: * Changes 39: * ------- 40: * 13-Dec-2001 : Version 1 (DG); 41: * 23-Jan-2002 : Added DrawInfo parameter to drawItem() method (DG); 42: * 28-Mar-2002 : Added a property change listener mechanism so that renderers 43: * no longer need to be immutable (DG); 44: * 09-Apr-2002 : Removed translatedRangeZero from the drawItem() method, and 45: * changed the return type of the drawItem method to void, 46: * reflecting a change in the XYItemRenderer interface. Added 47: * tooltip code to drawItem() method (DG); 48: * 05-Aug-2002 : Small modification to drawItem method to support URLs for 49: * HTML image maps (RA); 50: * 25-Mar-2003 : Implemented Serializable (DG); 51: * 01-May-2003 : Modified drawItem() method signature (DG); 52: * 30-Jul-2003 : Modified entity constructor (CZ); 53: * 31-Jul-2003 : Deprecated constructor (DG); 54: * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG); 55: * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG); 56: * 29-Jan-2004 : Fixed bug (882392) when rendering with 57: * PlotOrientation.HORIZONTAL (DG); 58: * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState. Renamed 59: * XYToolTipGenerator --> XYItemLabelGenerator (DG); 60: * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 61: * getYValue() (DG); 62: * 01-Nov-2005 : Added optional openTickPaint and closeTickPaint settings (DG); 63: * ------------- JFREECHART 1.0.0 --------------------------------------------- 64: * 06-Jul-2006 : Replace dataset methods getX() --> getXValue() (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.xy.OHLCDataset; 91: import org.jfree.data.xy.XYDataset; 92: import org.jfree.io.SerialUtilities; 93: import org.jfree.ui.RectangleEdge; 94: import org.jfree.util.PaintUtilities; 95: import org.jfree.util.PublicCloneable; 96: 97: /** 98: * A renderer that draws high/low/open/close markers on an {@link XYPlot} 99: * (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: }