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