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