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