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