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