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