001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2006, 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-2006, 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.3 2006/08/17 09:21:25 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.0 --------------------------------------------- 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 * 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 /** Temporary storage for the maximum volume. */ 181 private transient double maxVolume; 182 183 /** 184 * Creates a new renderer for candlestick charts. 185 */ 186 public CandlestickRenderer() { 187 this(-1.0); 188 } 189 190 /** 191 * Creates a new renderer for candlestick charts. 192 * <P> 193 * Use -1 for the candle width if you prefer the width to be calculated 194 * automatically. 195 * 196 * @param candleWidth The candle width. 197 */ 198 public CandlestickRenderer(double candleWidth) { 199 this(candleWidth, true, new HighLowItemLabelGenerator()); 200 } 201 202 /** 203 * Creates a new renderer for candlestick charts. 204 * <P> 205 * Use -1 for the candle width if you prefer the width to be calculated 206 * automatically. 207 * 208 * @param candleWidth the candle width. 209 * @param drawVolume a flag indicating whether or not volume bars should 210 * be drawn. 211 * @param toolTipGenerator the tool tip generator. <code>null</code> is 212 * none. 213 */ 214 public CandlestickRenderer(double candleWidth, boolean drawVolume, 215 XYToolTipGenerator toolTipGenerator) { 216 217 super(); 218 setToolTipGenerator(toolTipGenerator); 219 this.candleWidth = candleWidth; 220 this.drawVolume = drawVolume; 221 this.upPaint = Color.green; 222 this.downPaint = Color.red; 223 224 } 225 226 /** 227 * Returns the width of each candle. 228 * 229 * @return The candle width. 230 * 231 * @see #setCandleWidth(double) 232 */ 233 public double getCandleWidth() { 234 return this.candleWidth; 235 } 236 237 /** 238 * Sets the candle width. 239 * <P> 240 * If you set the width to a negative value, the renderer will calculate 241 * the candle width automatically based on the space available on the chart. 242 * 243 * @param width The width. 244 * @see #setAutoWidthMethod(int) 245 * @see #setAutoWidthGap(double) 246 * @see #setAutoWidthFactor(double) 247 * @see #setMaxCandleWidthInMilliseconds(double) 248 */ 249 public void setCandleWidth(double width) { 250 if (width != this.candleWidth) { 251 this.candleWidth = width; 252 notifyListeners(new RendererChangeEvent(this)); 253 } 254 } 255 256 /** 257 * Returns the maximum width (in milliseconds) of each candle. 258 * 259 * @return The maximum candle width in milliseconds. 260 */ 261 public double getMaxCandleWidthInMilliseconds() { 262 return this.maxCandleWidthInMilliseconds; 263 } 264 265 /** 266 * Sets the maximum candle width (in milliseconds). 267 * 268 * @param millis The maximum width. 269 * @see #setCandleWidth(double) 270 * @see #setAutoWidthMethod(int) 271 * @see #setAutoWidthGap(double) 272 * @see #setAutoWidthFactor(double) 273 */ 274 public void setMaxCandleWidthInMilliseconds(double millis) { 275 this.maxCandleWidthInMilliseconds = millis; 276 notifyListeners(new RendererChangeEvent(this)); 277 } 278 279 /** 280 * Returns the method of automatically calculating the candle width. 281 * 282 * @return The method of automatically calculating the candle width. 283 */ 284 public int getAutoWidthMethod() { 285 return this.autoWidthMethod; 286 } 287 288 /** 289 * Sets the method of automatically calculating the candle width. 290 * <p> 291 * <code>WIDTHMETHOD_AVERAGE</code>: Divides the entire display (ignoring 292 * scale factor) by the number of items, and uses this as the available 293 * width.<br> 294 * <code>WIDTHMETHOD_SMALLEST</code>: Checks the interval between each 295 * item, and uses the smallest as the available width.<br> 296 * <code>WIDTHMETHOD_INTERVALDATA</code>: Assumes that the dataset supports 297 * the IntervalXYDataset interface, and uses the startXValue - endXValue as 298 * the available width. 299 * <br> 300 * 301 * @param autoWidthMethod The method of automatically calculating the 302 * candle width. 303 * 304 * @see #WIDTHMETHOD_AVERAGE 305 * @see #WIDTHMETHOD_SMALLEST 306 * @see #WIDTHMETHOD_INTERVALDATA 307 * @see #setCandleWidth(double) 308 * @see #setAutoWidthGap(double) 309 * @see #setAutoWidthFactor(double) 310 * @see #setMaxCandleWidthInMilliseconds(double) 311 */ 312 public void setAutoWidthMethod(int autoWidthMethod) { 313 if (this.autoWidthMethod != autoWidthMethod) { 314 this.autoWidthMethod = autoWidthMethod; 315 notifyListeners(new RendererChangeEvent(this)); 316 } 317 } 318 319 /** 320 * Returns the factor by which the available space automatically 321 * calculated for the candles will be multiplied to determine the actual 322 * width to use. 323 * 324 * @return The width factor (generally between 0.0 and 1.0). 325 */ 326 public double getAutoWidthFactor() { 327 return this.autoWidthFactor; 328 } 329 330 /** 331 * Sets the factor by which the available space automatically calculated 332 * for the candles will be multiplied to determine the actual width to use. 333 * 334 * @param autoWidthFactor The width factor (generally between 0.0 and 1.0). 335 * @see #setCandleWidth(double) 336 * @see #setAutoWidthMethod(int) 337 * @see #setAutoWidthGap(double) 338 * @see #setMaxCandleWidthInMilliseconds(double) 339 */ 340 public void setAutoWidthFactor(double autoWidthFactor) { 341 if (this.autoWidthFactor != autoWidthFactor) { 342 this.autoWidthFactor = autoWidthFactor; 343 notifyListeners(new RendererChangeEvent(this)); 344 } 345 } 346 347 /** 348 * Returns the amount of space to leave on the left and right of each 349 * candle when automatically calculating widths. 350 * 351 * @return The gap. 352 */ 353 public double getAutoWidthGap() { 354 return this.autoWidthGap; 355 } 356 357 /** 358 * Sets the amount of space to leave on the left and right of each candle 359 * when automatically calculating widths. 360 * 361 * @param autoWidthGap The gap. 362 * @see #setCandleWidth(double) 363 * @see #setAutoWidthMethod(int) 364 * @see #setAutoWidthFactor(double) 365 * @see #setMaxCandleWidthInMilliseconds(double) 366 */ 367 public void setAutoWidthGap(double autoWidthGap) { 368 if (this.autoWidthGap != autoWidthGap) { 369 this.autoWidthGap = autoWidthGap; 370 notifyListeners(new RendererChangeEvent(this)); 371 } 372 } 373 374 /** 375 * Returns the paint used to fill candles when the price moves up from open 376 * to close. 377 * 378 * @return The paint. 379 */ 380 public Paint getUpPaint() { 381 return this.upPaint; 382 } 383 384 /** 385 * Sets the paint used to fill candles when the price moves up from open 386 * to close. 387 * <P> 388 * Registered property change listeners are notified that the 389 * "CandleStickRenderer.upPaint" property has changed. 390 * 391 * @param paint The paint. 392 */ 393 public void setUpPaint(Paint paint) { 394 this.upPaint = paint; 395 notifyListeners(new RendererChangeEvent(this)); 396 } 397 398 /** 399 * Returns the paint used to fill candles when the price moves down from 400 * open to close. 401 * 402 * @return The paint. 403 */ 404 public Paint getDownPaint() { 405 return this.downPaint; 406 } 407 408 /** 409 * Sets the paint used to fill candles when the price moves down from open 410 * to close. 411 * <P> 412 * Registered property change listeners are notified that the 413 * "CandleStickRenderer.downPaint" property has changed. 414 * 415 * @param paint The paint. 416 */ 417 public void setDownPaint(Paint paint) { 418 this.downPaint = paint; 419 notifyListeners(new RendererChangeEvent(this)); 420 } 421 422 /** 423 * Returns a flag indicating whether or not volume bars are drawn on the 424 * chart. 425 * 426 * @return <code>true</code> if volume bars are drawn on the chart. 427 */ 428 public boolean drawVolume() { 429 return this.drawVolume; 430 } 431 432 /** 433 * Sets a flag that controls whether or not volume bars are drawn in the 434 * background. 435 * 436 * @param flag The flag. 437 */ 438 public void setDrawVolume(boolean flag) { 439 if (this.drawVolume != flag) { 440 this.drawVolume = flag; 441 notifyListeners(new RendererChangeEvent(this)); 442 } 443 } 444 445 /** 446 * Initialises the renderer then returns the number of 'passes' through the 447 * data that the renderer will require (usually just one). This method 448 * will be called before the first item is rendered, giving the renderer 449 * an opportunity to initialise any state information it wants to maintain. 450 * The renderer can do nothing if it chooses. 451 * 452 * @param g2 the graphics device. 453 * @param dataArea the area inside the axes. 454 * @param plot the plot. 455 * @param dataset the data. 456 * @param info an optional info collection object to return data back to 457 * the caller. 458 * 459 * @return The number of passes the renderer requires. 460 */ 461 public XYItemRendererState initialise(Graphics2D g2, 462 Rectangle2D dataArea, 463 XYPlot plot, 464 XYDataset dataset, 465 PlotRenderingInfo info) { 466 467 // calculate the maximum allowed candle width from the axis... 468 ValueAxis axis = plot.getDomainAxis(); 469 double x1 = axis.getLowerBound(); 470 double x2 = x1 + this.maxCandleWidthInMilliseconds; 471 RectangleEdge edge = plot.getDomainAxisEdge(); 472 double xx1 = axis.valueToJava2D(x1, dataArea, edge); 473 double xx2 = axis.valueToJava2D(x2, dataArea, edge); 474 this.maxCandleWidth = Math.abs(xx2 - xx1); 475 // Absolute value, since the relative x 476 // positions are reversed for horizontal orientation 477 478 // calculate the highest volume in the dataset... 479 if (this.drawVolume) { 480 OHLCDataset highLowDataset = (OHLCDataset) dataset; 481 this.maxVolume = 0.0; 482 for (int series = 0; series < highLowDataset.getSeriesCount(); 483 series++) { 484 for (int item = 0; item < highLowDataset.getItemCount(series); 485 item++) { 486 double volume = highLowDataset.getVolumeValue(series, item); 487 if (volume > this.maxVolume) { 488 this.maxVolume = volume; 489 } 490 491 } 492 } 493 } 494 495 return new XYItemRendererState(info); 496 } 497 498 /** 499 * Draws the visual representation of a single data item. 500 * 501 * @param g2 the graphics device. 502 * @param state the renderer state. 503 * @param dataArea the area within which the plot is being drawn. 504 * @param info collects info about the drawing. 505 * @param plot the plot (can be used to obtain standard color 506 * information etc). 507 * @param domainAxis the domain axis. 508 * @param rangeAxis the range axis. 509 * @param dataset the dataset. 510 * @param series the series index (zero-based). 511 * @param item the item index (zero-based). 512 * @param crosshairState crosshair information for the plot 513 * (<code>null</code> permitted). 514 * @param pass the pass index. 515 */ 516 public void drawItem(Graphics2D g2, 517 XYItemRendererState state, 518 Rectangle2D dataArea, 519 PlotRenderingInfo info, 520 XYPlot plot, 521 ValueAxis domainAxis, 522 ValueAxis rangeAxis, 523 XYDataset dataset, 524 int series, 525 int item, 526 CrosshairState crosshairState, 527 int pass) { 528 529 boolean horiz; 530 PlotOrientation orientation = plot.getOrientation(); 531 if (orientation == PlotOrientation.HORIZONTAL) { 532 horiz = true; 533 } 534 else if (orientation == PlotOrientation.VERTICAL) { 535 horiz = false; 536 } 537 else { 538 return; 539 } 540 541 // setup for collecting optional entity info... 542 EntityCollection entities = null; 543 if (info != null) { 544 entities = info.getOwner().getEntityCollection(); 545 } 546 547 OHLCDataset highLowData = (OHLCDataset) dataset; 548 549 double x = highLowData.getXValue(series, item); 550 double yHigh = highLowData.getHighValue(series, item); 551 double yLow = highLowData.getLowValue(series, item); 552 double yOpen = highLowData.getOpenValue(series, item); 553 double yClose = highLowData.getCloseValue(series, item); 554 555 RectangleEdge domainEdge = plot.getDomainAxisEdge(); 556 double xx = domainAxis.valueToJava2D(x, dataArea, domainEdge); 557 558 RectangleEdge edge = plot.getRangeAxisEdge(); 559 double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea, edge); 560 double yyLow = rangeAxis.valueToJava2D(yLow, dataArea, edge); 561 double yyOpen = rangeAxis.valueToJava2D(yOpen, dataArea, edge); 562 double yyClose = rangeAxis.valueToJava2D(yClose, dataArea, edge); 563 564 double volumeWidth; 565 double stickWidth; 566 if (this.candleWidth > 0) { 567 // These are deliberately not bounded to minimums/maxCandleWidth to 568 // retain old behaviour. 569 volumeWidth = this.candleWidth; 570 stickWidth = this.candleWidth; 571 } 572 else { 573 double xxWidth = 0; 574 int itemCount; 575 switch (this.autoWidthMethod) { 576 577 case WIDTHMETHOD_AVERAGE: 578 itemCount = highLowData.getItemCount(series); 579 if (horiz) { 580 xxWidth = dataArea.getHeight() / itemCount; 581 } 582 else { 583 xxWidth = dataArea.getWidth() / itemCount; 584 } 585 break; 586 587 case WIDTHMETHOD_SMALLEST: 588 // Note: It would be nice to pre-calculate this per series 589 itemCount = highLowData.getItemCount(series); 590 double lastPos = -1; 591 xxWidth = dataArea.getWidth(); 592 for (int i = 0; i < itemCount; i++) { 593 double pos = domainAxis.valueToJava2D( 594 highLowData.getXValue(series, i), dataArea, 595 domainEdge 596 ); 597 if (lastPos != -1) { 598 xxWidth = Math.min( 599 xxWidth, Math.abs(pos - lastPos) 600 ); 601 } 602 lastPos = pos; 603 } 604 break; 605 606 case WIDTHMETHOD_INTERVALDATA: 607 IntervalXYDataset intervalXYData 608 = (IntervalXYDataset) dataset; 609 double startPos = domainAxis.valueToJava2D( 610 intervalXYData.getStartXValue(series, item), dataArea, 611 plot.getDomainAxisEdge() 612 ); 613 double endPos = domainAxis.valueToJava2D( 614 intervalXYData.getEndXValue(series, item), dataArea, 615 plot.getDomainAxisEdge() 616 ); 617 xxWidth = Math.abs(endPos - startPos); 618 break; 619 620 } 621 xxWidth -= 2 * this.autoWidthGap; 622 xxWidth *= this.autoWidthFactor; 623 xxWidth = Math.min(xxWidth, this.maxCandleWidth); 624 volumeWidth = Math.max(Math.min(1, this.maxCandleWidth), xxWidth); 625 stickWidth = Math.max(Math.min(3, this.maxCandleWidth), xxWidth); 626 } 627 628 Paint p = getItemPaint(series, item); 629 Stroke s = getItemStroke(series, item); 630 631 g2.setStroke(s); 632 633 if (this.drawVolume) { 634 int volume = (int) highLowData.getVolumeValue(series, item); 635 double volumeHeight = volume / this.maxVolume; 636 637 double min, max; 638 if (horiz) { 639 min = dataArea.getMinX(); 640 max = dataArea.getMaxX(); 641 } 642 else { 643 min = dataArea.getMinY(); 644 max = dataArea.getMaxY(); 645 } 646 647 double zzVolume = volumeHeight * (max - min); 648 649 g2.setPaint(Color.gray); 650 Composite originalComposite = g2.getComposite(); 651 g2.setComposite( 652 AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f) 653 ); 654 655 if (horiz) { 656 g2.fill(new Rectangle2D.Double(min, 657 xx - volumeWidth / 2, 658 zzVolume, volumeWidth)); 659 } 660 else { 661 g2.fill( 662 new Rectangle2D.Double( 663 xx - volumeWidth / 2, 664 max - zzVolume, volumeWidth, zzVolume 665 ) 666 ); 667 } 668 669 g2.setComposite(originalComposite); 670 } 671 672 g2.setPaint(p); 673 674 double yyMaxOpenClose = Math.max(yyOpen, yyClose); 675 double yyMinOpenClose = Math.min(yyOpen, yyClose); 676 double maxOpenClose = Math.max(yOpen, yClose); 677 double minOpenClose = Math.min(yOpen, yClose); 678 679 // draw the upper shadow 680 if (yHigh > maxOpenClose) { 681 if (horiz) { 682 g2.draw(new Line2D.Double(yyHigh, xx, yyMaxOpenClose, xx)); 683 } 684 else { 685 g2.draw(new Line2D.Double(xx, yyHigh, xx, yyMaxOpenClose)); 686 } 687 } 688 689 // draw the lower shadow 690 if (yLow < minOpenClose) { 691 if (horiz) { 692 g2.draw(new Line2D.Double(yyLow, xx, yyMinOpenClose, xx)); 693 } 694 else { 695 g2.draw(new Line2D.Double(xx, yyLow, xx, yyMinOpenClose)); 696 } 697 } 698 699 // draw the body 700 Shape body = null; 701 if (horiz) { 702 body = new Rectangle2D.Double( 703 yyMinOpenClose, xx - stickWidth / 2, 704 yyMaxOpenClose - yyMinOpenClose, stickWidth 705 ); 706 } 707 else { 708 body = new Rectangle2D.Double( 709 xx - stickWidth / 2, yyMinOpenClose, 710 stickWidth, yyMaxOpenClose - yyMinOpenClose 711 ); 712 } 713 if (yClose > yOpen) { 714 if (this.upPaint != null) { 715 g2.setPaint(this.upPaint); 716 g2.fill(body); 717 } 718 } 719 else { 720 if (this.downPaint != null) { 721 g2.setPaint(this.downPaint); 722 } 723 g2.fill(body); 724 } 725 g2.setPaint(p); 726 g2.draw(body); 727 728 // add an entity for the item... 729 if (entities != null) { 730 String tip = null; 731 XYToolTipGenerator generator = getToolTipGenerator(series, item); 732 if (generator != null) { 733 tip = generator.generateToolTip(dataset, series, item); 734 } 735 String url = null; 736 if (getURLGenerator() != null) { 737 url = getURLGenerator().generateURL(dataset, series, item); 738 } 739 XYItemEntity entity = new XYItemEntity(body, dataset, series, item, 740 tip, url); 741 entities.add(entity); 742 } 743 744 } 745 746 /** 747 * Tests this renderer for equality with another object. 748 * 749 * @param obj the object (<code>null</code> permitted). 750 * 751 * @return <code>true</code> or <code>false</code>. 752 */ 753 public boolean equals(Object obj) { 754 if (obj == this) { 755 return true; 756 } 757 if (! (obj instanceof CandlestickRenderer)) { 758 return false; 759 } 760 CandlestickRenderer that = (CandlestickRenderer) obj; 761 if (this.candleWidth != that.candleWidth) { 762 return false; 763 } 764 if (!PaintUtilities.equal(this.upPaint, that.upPaint)) { 765 return false; 766 } 767 if (!PaintUtilities.equal(this.downPaint, that.downPaint)) { 768 return false; 769 } 770 if (this.drawVolume != that.drawVolume) { 771 return false; 772 } 773 if (this.maxCandleWidthInMilliseconds 774 != that.maxCandleWidthInMilliseconds) { 775 return false; 776 } 777 if (this.autoWidthMethod != that.autoWidthMethod) { 778 return false; 779 } 780 if (this.autoWidthFactor != that.autoWidthFactor) { 781 return false; 782 } 783 if (this.autoWidthGap != that.autoWidthGap) { 784 return false; 785 } 786 return super.equals(obj); 787 } 788 789 /** 790 * Returns a clone of the renderer. 791 * 792 * @return A clone. 793 * 794 * @throws CloneNotSupportedException if the renderer cannot be cloned. 795 */ 796 public Object clone() throws CloneNotSupportedException { 797 return super.clone(); 798 } 799 800 /** 801 * Provides serialization support. 802 * 803 * @param stream the output stream. 804 * 805 * @throws IOException if there is an I/O error. 806 */ 807 private void writeObject(ObjectOutputStream stream) throws IOException { 808 stream.defaultWriteObject(); 809 SerialUtilities.writePaint(this.upPaint, stream); 810 SerialUtilities.writePaint(this.downPaint, stream); 811 } 812 813 /** 814 * Provides serialization support. 815 * 816 * @param stream the input stream. 817 * 818 * @throws IOException if there is an I/O error. 819 * @throws ClassNotFoundException if there is a classpath problem. 820 */ 821 private void readObject(ObjectInputStream stream) 822 throws IOException, ClassNotFoundException { 823 stream.defaultReadObject(); 824 this.upPaint = SerialUtilities.readPaint(stream); 825 this.downPaint = SerialUtilities.readPaint(stream); 826 } 827 828 }