Frames | No Frames |
1: /* =========================================================== 2: * JFreeChart : a free chart library for the Java(tm) platform 3: * =========================================================== 4: * 5: * (C) Copyright 2000-2007, 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-2007, 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: * 64: */ 65: 66: package org.jfree.chart.renderer.xy; 67: 68: import java.awt.Graphics2D; 69: import java.awt.Paint; 70: import java.awt.Shape; 71: import java.awt.Stroke; 72: import java.awt.geom.Line2D; 73: import java.awt.geom.Rectangle2D; 74: import java.io.IOException; 75: import java.io.ObjectInputStream; 76: import java.io.ObjectOutputStream; 77: import java.io.Serializable; 78: 79: import org.jfree.chart.axis.ValueAxis; 80: import org.jfree.chart.entity.EntityCollection; 81: import org.jfree.chart.entity.XYItemEntity; 82: import org.jfree.chart.event.RendererChangeEvent; 83: import org.jfree.chart.labels.XYToolTipGenerator; 84: import org.jfree.chart.plot.CrosshairState; 85: import org.jfree.chart.plot.PlotOrientation; 86: import org.jfree.chart.plot.PlotRenderingInfo; 87: import org.jfree.chart.plot.XYPlot; 88: import org.jfree.data.xy.OHLCDataset; 89: import org.jfree.data.xy.XYDataset; 90: import org.jfree.io.SerialUtilities; 91: import org.jfree.ui.RectangleEdge; 92: import org.jfree.util.PaintUtilities; 93: import org.jfree.util.PublicCloneable; 94: 95: /** 96: * A renderer that draws high/low/open/close markers on an {@link XYPlot} 97: * (requires a {@link OHLCDataset}). This renderer does not include code to 98: * calculate the crosshair point for the plot. 99: */ 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: 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: }