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