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: * CandlestickRenderer.java 29: * ------------------------ 30: * (C) Copyright 2001-2008, by Object Refinery Limited. 31: * 32: * Original Authors: David Gilbert (for Object Refinery Limited); 33: * Sylvain Vieujot; 34: * Contributor(s): Richard Atkinson; 35: * Christian W. Zuckschwerdt; 36: * Jerome Fisher; 37: * 38: * Changes 39: * ------- 40: * 13-Dec-2001 : Version 1. Based on code in the (now redundant) 41: * CandlestickPlot class, written by Sylvain Vieujot (DG); 42: * 23-Jan-2002 : Added DrawInfo parameter to drawItem() method (DG); 43: * 28-Mar-2002 : Added a property change listener mechanism so that renderers 44: * no longer need to be immutable. Added properties for up and 45: * down colors (DG); 46: * 04-Apr-2002 : Updated with new automatic width calculation and optional 47: * volume display, contributed by Sylvain Vieujot (DG); 48: * 09-Apr-2002 : Removed translatedRangeZero from the drawItem() method, and 49: * changed the return type of the drawItem method to void, 50: * reflecting a change in the XYItemRenderer interface. Added 51: * tooltip code to drawItem() method (DG); 52: * 25-Jun-2002 : Removed redundant code (DG); 53: * 05-Aug-2002 : Small modification to drawItem method to support URLs for HTML 54: * image maps (RA); 55: * 19-Sep-2002 : Fixed errors reported by Checkstyle (DG); 56: * 25-Mar-2003 : Implemented Serializable (DG); 57: * 01-May-2003 : Modified drawItem() method signature (DG); 58: * 30-Jun-2003 : Added support for PlotOrientation (for completeness, this 59: * renderer is unlikely to be used with a HORIZONTAL 60: * orientation) (DG); 61: * 30-Jul-2003 : Modified entity constructor (CZ); 62: * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG); 63: * 29-Aug-2003 : Moved maxVolume calculation to initialise method (see bug 64: * report 796619) (DG); 65: * 02-Sep-2003 : Added maxCandleWidthInMilliseconds as workaround for bug 66: * 796621 (DG); 67: * 08-Sep-2003 : Changed ValueAxis API (DG); 68: * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG); 69: * 13-Oct-2003 : Applied patch from Jerome Fisher to improve auto width 70: * calculations (DG); 71: * 23-Dec-2003 : Fixed bug where up and down paint are used incorrectly (DG); 72: * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG); 73: * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 74: * getYValue() (DG); 75: * ------------- JFREECHART 1.0.x --------------------------------------------- 76: * 06-Jul-2006 : Swapped calls to getX() --> getXValue(), and the same for the 77: * other data values (DG); 78: * 17-Aug-2006 : Corrections to the equals() method (DG); 79: * 05-Mar-2007 : Added flag to allow optional use of outline paint (DG); 80: * 08-Oct-2007 : Added new volumePaint field (DG); 81: * 08-Apr-2008 : Added findRangeBounds() method override (DG); 82: * 13-May-2008 : Fixed chart entity bugs (1962467 and 1962472) (DG); 83: * 84: */ 85: 86: package org.jfree.chart.renderer.xy; 87: 88: import java.awt.AlphaComposite; 89: import java.awt.Color; 90: import java.awt.Composite; 91: import java.awt.Graphics2D; 92: import java.awt.Paint; 93: import java.awt.Stroke; 94: import java.awt.geom.Line2D; 95: import java.awt.geom.Rectangle2D; 96: import java.io.IOException; 97: import java.io.ObjectInputStream; 98: import java.io.ObjectOutputStream; 99: import java.io.Serializable; 100: 101: import org.jfree.chart.axis.ValueAxis; 102: import org.jfree.chart.entity.EntityCollection; 103: import org.jfree.chart.event.RendererChangeEvent; 104: import org.jfree.chart.labels.HighLowItemLabelGenerator; 105: import org.jfree.chart.labels.XYToolTipGenerator; 106: import org.jfree.chart.plot.CrosshairState; 107: import org.jfree.chart.plot.PlotOrientation; 108: import org.jfree.chart.plot.PlotRenderingInfo; 109: import org.jfree.chart.plot.XYPlot; 110: import org.jfree.data.Range; 111: import org.jfree.data.general.DatasetUtilities; 112: import org.jfree.data.xy.IntervalXYDataset; 113: import org.jfree.data.xy.OHLCDataset; 114: import org.jfree.data.xy.XYDataset; 115: import org.jfree.io.SerialUtilities; 116: import org.jfree.ui.RectangleEdge; 117: import org.jfree.util.PaintUtilities; 118: import org.jfree.util.PublicCloneable; 119: 120: /** 121: * A renderer that draws candlesticks on an {@link XYPlot} (requires a 122: * {@link OHLCDataset}). 123: * <P> 124: * This renderer does not include code to calculate the crosshair point for the 125: * plot. 126: */ 127: public class CandlestickRenderer extends AbstractXYItemRenderer 128: implements XYItemRenderer, Cloneable, PublicCloneable, Serializable { 129: 130: /** For serialization. */ 131: private static final long serialVersionUID = 50390395841817121L; 132: 133: /** The average width method. */ 134: public static final int WIDTHMETHOD_AVERAGE = 0; 135: 136: /** The smallest width method. */ 137: public static final int WIDTHMETHOD_SMALLEST = 1; 138: 139: /** The interval data method. */ 140: public static final int WIDTHMETHOD_INTERVALDATA = 2; 141: 142: /** The method of automatically calculating the candle width. */ 143: private int autoWidthMethod = WIDTHMETHOD_AVERAGE; 144: 145: /** 146: * The number (generally between 0.0 and 1.0) by which the available space 147: * automatically calculated for the candles will be multiplied to determine 148: * the actual width to use. 149: */ 150: private double autoWidthFactor = 4.5 / 7; 151: 152: /** The minimum gap between one candle and the next */ 153: private double autoWidthGap = 0.0; 154: 155: /** The candle width. */ 156: private double candleWidth; 157: 158: /** The maximum candlewidth in milliseconds. */ 159: private double maxCandleWidthInMilliseconds = 1000.0 * 60.0 * 60.0 * 20.0; 160: 161: /** Temporary storage for the maximum candle width. */ 162: private double maxCandleWidth; 163: 164: /** 165: * The paint used to fill the candle when the price moved up from open to 166: * close. 167: */ 168: private transient Paint upPaint; 169: 170: /** 171: * The paint used to fill the candle when the price moved down from open 172: * to close. 173: */ 174: private transient Paint downPaint; 175: 176: /** A flag controlling whether or not volume bars are drawn on the chart. */ 177: private boolean drawVolume; 178: 179: /** 180: * The paint used to fill the volume bars (if they are visible). Once 181: * initialised, this field should never be set to <code>null</code>. 182: * 183: * @since 1.0.7 184: */ 185: private transient Paint volumePaint; 186: 187: /** Temporary storage for the maximum volume. */ 188: private transient double maxVolume; 189: 190: /** 191: * A flag that controls whether or not the renderer's outline paint is 192: * used to draw the outline of the candlestick. The default value is 193: * <code>false</code> to avoid a change of behaviour for existing code. 194: * 195: * @since 1.0.5 196: */ 197: private boolean useOutlinePaint; 198: 199: /** 200: * Creates a new renderer for candlestick charts. 201: */ 202: public CandlestickRenderer() { 203: this(-1.0); 204: } 205: 206: /** 207: * Creates a new renderer for candlestick charts. 208: * <P> 209: * Use -1 for the candle width if you prefer the width to be calculated 210: * automatically. 211: * 212: * @param candleWidth The candle width. 213: */ 214: public CandlestickRenderer(double candleWidth) { 215: this(candleWidth, true, new HighLowItemLabelGenerator()); 216: } 217: 218: /** 219: * Creates a new renderer for candlestick charts. 220: * <P> 221: * Use -1 for the candle width if you prefer the width to be calculated 222: * automatically. 223: * 224: * @param candleWidth the candle width. 225: * @param drawVolume a flag indicating whether or not volume bars should 226: * be drawn. 227: * @param toolTipGenerator the tool tip generator. <code>null</code> is 228: * none. 229: */ 230: public CandlestickRenderer(double candleWidth, boolean drawVolume, 231: XYToolTipGenerator toolTipGenerator) { 232: super(); 233: setBaseToolTipGenerator(toolTipGenerator); 234: this.candleWidth = candleWidth; 235: this.drawVolume = drawVolume; 236: this.volumePaint = Color.gray; 237: this.upPaint = Color.green; 238: this.downPaint = Color.red; 239: this.useOutlinePaint = false; // false preserves the old behaviour 240: // prior to introducing this flag 241: } 242: 243: /** 244: * Returns the width of each candle. 245: * 246: * @return The candle width. 247: * 248: * @see #setCandleWidth(double) 249: */ 250: public double getCandleWidth() { 251: return this.candleWidth; 252: } 253: 254: /** 255: * Sets the candle width and sends a {@link RendererChangeEvent} to all 256: * registered listeners. 257: * <P> 258: * If you set the width to a negative value, the renderer will calculate 259: * the candle width automatically based on the space available on the chart. 260: * 261: * @param width The width. 262: * @see #setAutoWidthMethod(int) 263: * @see #setAutoWidthGap(double) 264: * @see #setAutoWidthFactor(double) 265: * @see #setMaxCandleWidthInMilliseconds(double) 266: */ 267: public void setCandleWidth(double width) { 268: if (width != this.candleWidth) { 269: this.candleWidth = width; 270: fireChangeEvent(); 271: } 272: } 273: 274: /** 275: * Returns the maximum width (in milliseconds) of each candle. 276: * 277: * @return The maximum candle width in milliseconds. 278: * 279: * @see #setMaxCandleWidthInMilliseconds(double) 280: */ 281: public double getMaxCandleWidthInMilliseconds() { 282: return this.maxCandleWidthInMilliseconds; 283: } 284: 285: /** 286: * Sets the maximum candle width (in milliseconds) and sends a 287: * {@link RendererChangeEvent} to all registered listeners. 288: * 289: * @param millis The maximum width. 290: * 291: * @see #getMaxCandleWidthInMilliseconds() 292: * @see #setCandleWidth(double) 293: * @see #setAutoWidthMethod(int) 294: * @see #setAutoWidthGap(double) 295: * @see #setAutoWidthFactor(double) 296: */ 297: public void setMaxCandleWidthInMilliseconds(double millis) { 298: this.maxCandleWidthInMilliseconds = millis; 299: fireChangeEvent(); 300: } 301: 302: /** 303: * Returns the method of automatically calculating the candle width. 304: * 305: * @return The method of automatically calculating the candle width. 306: * 307: * @see #setAutoWidthMethod(int) 308: */ 309: public int getAutoWidthMethod() { 310: return this.autoWidthMethod; 311: } 312: 313: /** 314: * Sets the method of automatically calculating the candle width and 315: * sends a {@link RendererChangeEvent} to all registered listeners. 316: * <p> 317: * <code>WIDTHMETHOD_AVERAGE</code>: Divides the entire display (ignoring 318: * scale factor) by the number of items, and uses this as the available 319: * width.<br> 320: * <code>WIDTHMETHOD_SMALLEST</code>: Checks the interval between each 321: * item, and uses the smallest as the available width.<br> 322: * <code>WIDTHMETHOD_INTERVALDATA</code>: Assumes that the dataset supports 323: * the IntervalXYDataset interface, and uses the startXValue - endXValue as 324: * the available width. 325: * <br> 326: * 327: * @param autoWidthMethod The method of automatically calculating the 328: * candle width. 329: * 330: * @see #WIDTHMETHOD_AVERAGE 331: * @see #WIDTHMETHOD_SMALLEST 332: * @see #WIDTHMETHOD_INTERVALDATA 333: * @see #getAutoWidthMethod() 334: * @see #setCandleWidth(double) 335: * @see #setAutoWidthGap(double) 336: * @see #setAutoWidthFactor(double) 337: * @see #setMaxCandleWidthInMilliseconds(double) 338: */ 339: public void setAutoWidthMethod(int autoWidthMethod) { 340: if (this.autoWidthMethod != autoWidthMethod) { 341: this.autoWidthMethod = autoWidthMethod; 342: fireChangeEvent(); 343: } 344: } 345: 346: /** 347: * Returns the factor by which the available space automatically 348: * calculated for the candles will be multiplied to determine the actual 349: * width to use. 350: * 351: * @return The width factor (generally between 0.0 and 1.0). 352: * 353: * @see #setAutoWidthFactor(double) 354: */ 355: public double getAutoWidthFactor() { 356: return this.autoWidthFactor; 357: } 358: 359: /** 360: * Sets the factor by which the available space automatically calculated 361: * for the candles will be multiplied to determine the actual width to use. 362: * 363: * @param autoWidthFactor The width factor (generally between 0.0 and 1.0). 364: * 365: * @see #getAutoWidthFactor() 366: * @see #setCandleWidth(double) 367: * @see #setAutoWidthMethod(int) 368: * @see #setAutoWidthGap(double) 369: * @see #setMaxCandleWidthInMilliseconds(double) 370: */ 371: public void setAutoWidthFactor(double autoWidthFactor) { 372: if (this.autoWidthFactor != autoWidthFactor) { 373: this.autoWidthFactor = autoWidthFactor; 374: fireChangeEvent(); 375: } 376: } 377: 378: /** 379: * Returns the amount of space to leave on the left and right of each 380: * candle when automatically calculating widths. 381: * 382: * @return The gap. 383: * 384: * @see #setAutoWidthGap(double) 385: */ 386: public double getAutoWidthGap() { 387: return this.autoWidthGap; 388: } 389: 390: /** 391: * Sets the amount of space to leave on the left and right of each candle 392: * when automatically calculating widths and sends a 393: * {@link RendererChangeEvent} to all registered listeners. 394: * 395: * @param autoWidthGap The gap. 396: * 397: * @see #getAutoWidthGap() 398: * @see #setCandleWidth(double) 399: * @see #setAutoWidthMethod(int) 400: * @see #setAutoWidthFactor(double) 401: * @see #setMaxCandleWidthInMilliseconds(double) 402: */ 403: public void setAutoWidthGap(double autoWidthGap) { 404: if (this.autoWidthGap != autoWidthGap) { 405: this.autoWidthGap = autoWidthGap; 406: fireChangeEvent(); 407: } 408: } 409: 410: /** 411: * Returns the paint used to fill candles when the price moves up from open 412: * to close. 413: * 414: * @return The paint (possibly <code>null</code>). 415: * 416: * @see #setUpPaint(Paint) 417: */ 418: public Paint getUpPaint() { 419: return this.upPaint; 420: } 421: 422: /** 423: * Sets the paint used to fill candles when the price moves up from open 424: * to close and sends a {@link RendererChangeEvent} to all registered 425: * listeners. 426: * 427: * @param paint the paint (<code>null</code> permitted). 428: * 429: * @see #getUpPaint() 430: */ 431: public void setUpPaint(Paint paint) { 432: this.upPaint = paint; 433: fireChangeEvent(); 434: } 435: 436: /** 437: * Returns the paint used to fill candles when the price moves down from 438: * open to close. 439: * 440: * @return The paint (possibly <code>null</code>). 441: * 442: * @see #setDownPaint(Paint) 443: */ 444: public Paint getDownPaint() { 445: return this.downPaint; 446: } 447: 448: /** 449: * Sets the paint used to fill candles when the price moves down from open 450: * to close and sends a {@link RendererChangeEvent} to all registered 451: * listeners. 452: * 453: * @param paint The paint (<code>null</code> permitted). 454: */ 455: public void setDownPaint(Paint paint) { 456: this.downPaint = paint; 457: fireChangeEvent(); 458: } 459: 460: /** 461: * Returns a flag indicating whether or not volume bars are drawn on the 462: * chart. 463: * 464: * @return A boolean. 465: * 466: * @since 1.0.5 467: * 468: * @see #setDrawVolume(boolean) 469: */ 470: public boolean getDrawVolume() { 471: return this.drawVolume; 472: } 473: 474: /** 475: * Sets a flag that controls whether or not volume bars are drawn in the 476: * background and sends a {@link RendererChangeEvent} to all registered 477: * listeners. 478: * 479: * @param flag the flag. 480: * 481: * @see #getDrawVolume() 482: */ 483: public void setDrawVolume(boolean flag) { 484: if (this.drawVolume != flag) { 485: this.drawVolume = flag; 486: fireChangeEvent(); 487: } 488: } 489: 490: /** 491: * Returns the paint that is used to fill the volume bars if they are 492: * visible. 493: * 494: * @return The paint (never <code>null</code>). 495: * 496: * @see #setVolumePaint(Paint) 497: * 498: * @since 1.0.7 499: */ 500: public Paint getVolumePaint() { 501: return this.volumePaint; 502: } 503: 504: /** 505: * Sets the paint used to fill the volume bars, and sends a 506: * {@link RendererChangeEvent} to all registered listeners. 507: * 508: * @param paint the paint (<code>null</code> not permitted). 509: * 510: * @see #getVolumePaint() 511: * @see #getDrawVolume() 512: * 513: * @since 1.0.7 514: */ 515: public void setVolumePaint(Paint paint) { 516: if (paint == null) { 517: throw new IllegalArgumentException("Null 'paint' argument."); 518: } 519: this.volumePaint = paint; 520: fireChangeEvent(); 521: } 522: 523: /** 524: * Returns the flag that controls whether or not the renderer's outline 525: * paint is used to draw the candlestick outline. The default value is 526: * <code>false</code>. 527: * 528: * @return A boolean. 529: * 530: * @since 1.0.5 531: * 532: * @see #setUseOutlinePaint(boolean) 533: */ 534: public boolean getUseOutlinePaint() { 535: return this.useOutlinePaint; 536: } 537: 538: /** 539: * Sets the flag that controls whether or not the renderer's outline 540: * paint is used to draw the candlestick outline, and sends a 541: * {@link RendererChangeEvent} to all registered listeners. 542: * 543: * @param use the new flag value. 544: * 545: * @since 1.0.5 546: * 547: * @see #getUseOutlinePaint() 548: */ 549: public void setUseOutlinePaint(boolean use) { 550: if (this.useOutlinePaint != use) { 551: this.useOutlinePaint = use; 552: fireChangeEvent(); 553: } 554: } 555: 556: /** 557: * Returns the range of values the renderer requires to display all the 558: * items from the specified dataset. 559: * 560: * @param dataset the dataset (<code>null</code> permitted). 561: * 562: * @return The range (<code>null</code> if the dataset is <code>null</code> 563: * or empty). 564: */ 565: public Range findRangeBounds(XYDataset dataset) { 566: if (dataset != null) { 567: return DatasetUtilities.findRangeBounds(dataset, true); 568: } 569: else { 570: return null; 571: } 572: } 573: 574: /** 575: * Initialises the renderer then returns the number of 'passes' through the 576: * data that the renderer will require (usually just one). This method 577: * will be called before the first item is rendered, giving the renderer 578: * an opportunity to initialise any state information it wants to maintain. 579: * The renderer can do nothing if it chooses. 580: * 581: * @param g2 the graphics device. 582: * @param dataArea the area inside the axes. 583: * @param plot the plot. 584: * @param dataset the data. 585: * @param info an optional info collection object to return data back to 586: * the caller. 587: * 588: * @return The number of passes the renderer requires. 589: */ 590: public XYItemRendererState initialise(Graphics2D g2, 591: Rectangle2D dataArea, 592: XYPlot plot, 593: XYDataset dataset, 594: PlotRenderingInfo info) { 595: 596: // calculate the maximum allowed candle width from the axis... 597: ValueAxis axis = plot.getDomainAxis(); 598: double x1 = axis.getLowerBound(); 599: double x2 = x1 + this.maxCandleWidthInMilliseconds; 600: RectangleEdge edge = plot.getDomainAxisEdge(); 601: double xx1 = axis.valueToJava2D(x1, dataArea, edge); 602: double xx2 = axis.valueToJava2D(x2, dataArea, edge); 603: this.maxCandleWidth = Math.abs(xx2 - xx1); 604: // Absolute value, since the relative x 605: // positions are reversed for horizontal orientation 606: 607: // calculate the highest volume in the dataset... 608: if (this.drawVolume) { 609: OHLCDataset highLowDataset = (OHLCDataset) dataset; 610: this.maxVolume = 0.0; 611: for (int series = 0; series < highLowDataset.getSeriesCount(); 612: series++) { 613: for (int item = 0; item < highLowDataset.getItemCount(series); 614: item++) { 615: double volume = highLowDataset.getVolumeValue(series, item); 616: if (volume > this.maxVolume) { 617: this.maxVolume = volume; 618: } 619: 620: } 621: } 622: } 623: 624: return new XYItemRendererState(info); 625: } 626: 627: /** 628: * Draws the visual representation of a single data item. 629: * 630: * @param g2 the graphics device. 631: * @param state the renderer state. 632: * @param dataArea the area within which the plot is being drawn. 633: * @param info collects info about the drawing. 634: * @param plot the plot (can be used to obtain standard color 635: * information etc). 636: * @param domainAxis the domain axis. 637: * @param rangeAxis the range axis. 638: * @param dataset the dataset. 639: * @param series the series index (zero-based). 640: * @param item the item index (zero-based). 641: * @param crosshairState crosshair information for the plot 642: * (<code>null</code> permitted). 643: * @param pass the pass index. 644: */ 645: public void drawItem(Graphics2D g2, 646: XYItemRendererState state, 647: Rectangle2D dataArea, 648: PlotRenderingInfo info, 649: XYPlot plot, 650: ValueAxis domainAxis, 651: ValueAxis rangeAxis, 652: XYDataset dataset, 653: int series, 654: int item, 655: CrosshairState crosshairState, 656: int pass) { 657: 658: boolean horiz; 659: PlotOrientation orientation = plot.getOrientation(); 660: if (orientation == PlotOrientation.HORIZONTAL) { 661: horiz = true; 662: } 663: else if (orientation == PlotOrientation.VERTICAL) { 664: horiz = false; 665: } 666: else { 667: return; 668: } 669: 670: // setup for collecting optional entity info... 671: EntityCollection entities = null; 672: if (info != null) { 673: entities = info.getOwner().getEntityCollection(); 674: } 675: 676: OHLCDataset highLowData = (OHLCDataset) dataset; 677: 678: double x = highLowData.getXValue(series, item); 679: double yHigh = highLowData.getHighValue(series, item); 680: double yLow = highLowData.getLowValue(series, item); 681: double yOpen = highLowData.getOpenValue(series, item); 682: double yClose = highLowData.getCloseValue(series, item); 683: 684: RectangleEdge domainEdge = plot.getDomainAxisEdge(); 685: double xx = domainAxis.valueToJava2D(x, dataArea, domainEdge); 686: 687: RectangleEdge edge = plot.getRangeAxisEdge(); 688: double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea, edge); 689: double yyLow = rangeAxis.valueToJava2D(yLow, dataArea, edge); 690: double yyOpen = rangeAxis.valueToJava2D(yOpen, dataArea, edge); 691: double yyClose = rangeAxis.valueToJava2D(yClose, dataArea, edge); 692: 693: double volumeWidth; 694: double stickWidth; 695: if (this.candleWidth > 0) { 696: // These are deliberately not bounded to minimums/maxCandleWidth to 697: // retain old behaviour. 698: volumeWidth = this.candleWidth; 699: stickWidth = this.candleWidth; 700: } 701: else { 702: double xxWidth = 0; 703: int itemCount; 704: switch (this.autoWidthMethod) { 705: 706: case WIDTHMETHOD_AVERAGE: 707: itemCount = highLowData.getItemCount(series); 708: if (horiz) { 709: xxWidth = dataArea.getHeight() / itemCount; 710: } 711: else { 712: xxWidth = dataArea.getWidth() / itemCount; 713: } 714: break; 715: 716: case WIDTHMETHOD_SMALLEST: 717: // Note: It would be nice to pre-calculate this per series 718: itemCount = highLowData.getItemCount(series); 719: double lastPos = -1; 720: xxWidth = dataArea.getWidth(); 721: for (int i = 0; i < itemCount; i++) { 722: double pos = domainAxis.valueToJava2D( 723: highLowData.getXValue(series, i), dataArea, 724: domainEdge); 725: if (lastPos != -1) { 726: xxWidth = Math.min(xxWidth, 727: Math.abs(pos - lastPos)); 728: } 729: lastPos = pos; 730: } 731: break; 732: 733: case WIDTHMETHOD_INTERVALDATA: 734: IntervalXYDataset intervalXYData 735: = (IntervalXYDataset) dataset; 736: double startPos = domainAxis.valueToJava2D( 737: intervalXYData.getStartXValue(series, item), 738: dataArea, plot.getDomainAxisEdge()); 739: double endPos = domainAxis.valueToJava2D( 740: intervalXYData.getEndXValue(series, item), 741: dataArea, plot.getDomainAxisEdge()); 742: xxWidth = Math.abs(endPos - startPos); 743: break; 744: 745: } 746: xxWidth -= 2 * this.autoWidthGap; 747: xxWidth *= this.autoWidthFactor; 748: xxWidth = Math.min(xxWidth, this.maxCandleWidth); 749: volumeWidth = Math.max(Math.min(1, this.maxCandleWidth), xxWidth); 750: stickWidth = Math.max(Math.min(3, this.maxCandleWidth), xxWidth); 751: } 752: 753: Paint p = getItemPaint(series, item); 754: Paint outlinePaint = null; 755: if (this.useOutlinePaint) { 756: outlinePaint = getItemOutlinePaint(series, item); 757: } 758: Stroke s = getItemStroke(series, item); 759: 760: g2.setStroke(s); 761: 762: if (this.drawVolume) { 763: int volume = (int) highLowData.getVolumeValue(series, item); 764: double volumeHeight = volume / this.maxVolume; 765: 766: double min, max; 767: if (horiz) { 768: min = dataArea.getMinX(); 769: max = dataArea.getMaxX(); 770: } 771: else { 772: min = dataArea.getMinY(); 773: max = dataArea.getMaxY(); 774: } 775: 776: double zzVolume = volumeHeight * (max - min); 777: 778: g2.setPaint(getVolumePaint()); 779: Composite originalComposite = g2.getComposite(); 780: g2.setComposite(AlphaComposite.getInstance( 781: AlphaComposite.SRC_OVER, 0.3f)); 782: 783: if (horiz) { 784: g2.fill(new Rectangle2D.Double(min, xx - volumeWidth / 2, 785: zzVolume, volumeWidth)); 786: } 787: else { 788: g2.fill(new Rectangle2D.Double(xx - volumeWidth / 2, 789: max - zzVolume, volumeWidth, zzVolume)); 790: } 791: 792: g2.setComposite(originalComposite); 793: } 794: 795: if (this.useOutlinePaint) { 796: g2.setPaint(outlinePaint); 797: } 798: else { 799: g2.setPaint(p); 800: } 801: 802: double yyMaxOpenClose = Math.max(yyOpen, yyClose); 803: double yyMinOpenClose = Math.min(yyOpen, yyClose); 804: double maxOpenClose = Math.max(yOpen, yClose); 805: double minOpenClose = Math.min(yOpen, yClose); 806: 807: // draw the upper shadow 808: if (yHigh > maxOpenClose) { 809: if (horiz) { 810: g2.draw(new Line2D.Double(yyHigh, xx, yyMaxOpenClose, xx)); 811: } 812: else { 813: g2.draw(new Line2D.Double(xx, yyHigh, xx, yyMaxOpenClose)); 814: } 815: } 816: 817: // draw the lower shadow 818: if (yLow < minOpenClose) { 819: if (horiz) { 820: g2.draw(new Line2D.Double(yyLow, xx, yyMinOpenClose, xx)); 821: } 822: else { 823: g2.draw(new Line2D.Double(xx, yyLow, xx, yyMinOpenClose)); 824: } 825: } 826: 827: // draw the body 828: Rectangle2D body = null; 829: Rectangle2D hotspot = null; 830: double length = Math.abs(yyHigh - yyLow); 831: double base = Math.min(yyHigh, yyLow); 832: if (horiz) { 833: body = new Rectangle2D.Double(yyMinOpenClose, xx - stickWidth / 2, 834: yyMaxOpenClose - yyMinOpenClose, stickWidth); 835: hotspot = new Rectangle2D.Double(base, xx - stickWidth / 2, 836: length, stickWidth); 837: } 838: else { 839: body = new Rectangle2D.Double(xx - stickWidth / 2, yyMinOpenClose, 840: stickWidth, yyMaxOpenClose - yyMinOpenClose); 841: hotspot = new Rectangle2D.Double(xx - stickWidth / 2, 842: base, stickWidth, length); 843: } 844: if (yClose > yOpen) { 845: if (this.upPaint != null) { 846: g2.setPaint(this.upPaint); 847: } 848: else { 849: g2.setPaint(p); 850: } 851: g2.fill(body); 852: } 853: else { 854: if (this.downPaint != null) { 855: g2.setPaint(this.downPaint); 856: } 857: else { 858: g2.setPaint(p); 859: } 860: g2.fill(body); 861: } 862: if (this.useOutlinePaint) { 863: g2.setPaint(outlinePaint); 864: } 865: else { 866: g2.setPaint(p); 867: } 868: g2.draw(body); 869: 870: // add an entity for the item... 871: if (entities != null) { 872: addEntity(entities, hotspot, dataset, series, item, 0.0, 0.0); 873: } 874: 875: } 876: 877: /** 878: * Tests this renderer for equality with another object. 879: * 880: * @param obj the object (<code>null</code> permitted). 881: * 882: * @return <code>true</code> or <code>false</code>. 883: */ 884: public boolean equals(Object obj) { 885: if (obj == this) { 886: return true; 887: } 888: if (!(obj instanceof CandlestickRenderer)) { 889: return false; 890: } 891: CandlestickRenderer that = (CandlestickRenderer) obj; 892: if (this.candleWidth != that.candleWidth) { 893: return false; 894: } 895: if (!PaintUtilities.equal(this.upPaint, that.upPaint)) { 896: return false; 897: } 898: if (!PaintUtilities.equal(this.downPaint, that.downPaint)) { 899: return false; 900: } 901: if (this.drawVolume != that.drawVolume) { 902: return false; 903: } 904: if (this.maxCandleWidthInMilliseconds 905: != that.maxCandleWidthInMilliseconds) { 906: return false; 907: } 908: if (this.autoWidthMethod != that.autoWidthMethod) { 909: return false; 910: } 911: if (this.autoWidthFactor != that.autoWidthFactor) { 912: return false; 913: } 914: if (this.autoWidthGap != that.autoWidthGap) { 915: return false; 916: } 917: if (this.useOutlinePaint != that.useOutlinePaint) { 918: return false; 919: } 920: if (!PaintUtilities.equal(this.volumePaint, that.volumePaint)) { 921: return false; 922: } 923: return super.equals(obj); 924: } 925: 926: /** 927: * Returns a clone of the renderer. 928: * 929: * @return A clone. 930: * 931: * @throws CloneNotSupportedException if the renderer cannot be cloned. 932: */ 933: public Object clone() throws CloneNotSupportedException { 934: return super.clone(); 935: } 936: 937: /** 938: * Provides serialization support. 939: * 940: * @param stream the output stream. 941: * 942: * @throws IOException if there is an I/O error. 943: */ 944: private void writeObject(ObjectOutputStream stream) throws IOException { 945: stream.defaultWriteObject(); 946: SerialUtilities.writePaint(this.upPaint, stream); 947: SerialUtilities.writePaint(this.downPaint, stream); 948: SerialUtilities.writePaint(this.volumePaint, stream); 949: } 950: 951: /** 952: * Provides serialization support. 953: * 954: * @param stream the input stream. 955: * 956: * @throws IOException if there is an I/O error. 957: * @throws ClassNotFoundException if there is a classpath problem. 958: */ 959: private void readObject(ObjectInputStream stream) 960: throws IOException, ClassNotFoundException { 961: stream.defaultReadObject(); 962: this.upPaint = SerialUtilities.readPaint(stream); 963: this.downPaint = SerialUtilities.readPaint(stream); 964: this.volumePaint = SerialUtilities.readPaint(stream); 965: } 966: 967: // --- DEPRECATED CODE ---------------------------------------------------- 968: 969: /** 970: * Returns a flag indicating whether or not volume bars are drawn on the 971: * chart. 972: * 973: * @return <code>true</code> if volume bars are drawn on the chart. 974: * 975: * @deprecated As of 1.0.5, you should use the {@link #getDrawVolume()} 976: * method. 977: */ 978: public boolean drawVolume() { 979: return this.drawVolume; 980: } 981: 982: }