001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2008, 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 * CandlestickRenderer.java 029 * ------------------------ 030 * (C) Copyright 2001-2008, by Object Refinery Limited. 031 * 032 * Original Authors: David Gilbert (for Object Refinery Limited); 033 * Sylvain Vieujot; 034 * Contributor(s): Richard Atkinson; 035 * Christian W. Zuckschwerdt; 036 * Jerome Fisher; 037 * 038 * Changes 039 * ------- 040 * 13-Dec-2001 : Version 1. Based on code in the (now redundant) 041 * CandlestickPlot class, written by Sylvain Vieujot (DG); 042 * 23-Jan-2002 : Added DrawInfo parameter to drawItem() method (DG); 043 * 28-Mar-2002 : Added a property change listener mechanism so that renderers 044 * no longer need to be immutable. Added properties for up and 045 * down colors (DG); 046 * 04-Apr-2002 : Updated with new automatic width calculation and optional 047 * volume display, contributed by Sylvain Vieujot (DG); 048 * 09-Apr-2002 : Removed translatedRangeZero from the drawItem() method, and 049 * changed the return type of the drawItem method to void, 050 * reflecting a change in the XYItemRenderer interface. Added 051 * tooltip code to drawItem() method (DG); 052 * 25-Jun-2002 : Removed redundant code (DG); 053 * 05-Aug-2002 : Small modification to drawItem method to support URLs for HTML 054 * image maps (RA); 055 * 19-Sep-2002 : Fixed errors reported by Checkstyle (DG); 056 * 25-Mar-2003 : Implemented Serializable (DG); 057 * 01-May-2003 : Modified drawItem() method signature (DG); 058 * 30-Jun-2003 : Added support for PlotOrientation (for completeness, this 059 * renderer is unlikely to be used with a HORIZONTAL 060 * orientation) (DG); 061 * 30-Jul-2003 : Modified entity constructor (CZ); 062 * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG); 063 * 29-Aug-2003 : Moved maxVolume calculation to initialise method (see bug 064 * report 796619) (DG); 065 * 02-Sep-2003 : Added maxCandleWidthInMilliseconds as workaround for bug 066 * 796621 (DG); 067 * 08-Sep-2003 : Changed ValueAxis API (DG); 068 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG); 069 * 13-Oct-2003 : Applied patch from Jerome Fisher to improve auto width 070 * calculations (DG); 071 * 23-Dec-2003 : Fixed bug where up and down paint are used incorrectly (DG); 072 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG); 073 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 074 * getYValue() (DG); 075 * ------------- JFREECHART 1.0.x --------------------------------------------- 076 * 06-Jul-2006 : Swapped calls to getX() --> getXValue(), and the same for the 077 * other data values (DG); 078 * 17-Aug-2006 : Corrections to the equals() method (DG); 079 * 05-Mar-2007 : Added flag to allow optional use of outline paint (DG); 080 * 08-Oct-2007 : Added new volumePaint field (DG); 081 * 08-Apr-2008 : Added findRangeBounds() method override (DG); 082 * 13-May-2008 : Fixed chart entity bugs (1962467 and 1962472) (DG); 083 * 084 */ 085 086 package org.jfree.chart.renderer.xy; 087 088 import java.awt.AlphaComposite; 089 import java.awt.Color; 090 import java.awt.Composite; 091 import java.awt.Graphics2D; 092 import java.awt.Paint; 093 import java.awt.Stroke; 094 import java.awt.geom.Line2D; 095 import java.awt.geom.Rectangle2D; 096 import java.io.IOException; 097 import java.io.ObjectInputStream; 098 import java.io.ObjectOutputStream; 099 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 }