001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2020, 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 * [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-2020, 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 */ 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 * @since 1.0.7 143 */ 144 private transient Paint volumePaint; 145 146 /** Temporary storage for the maximum volume. */ 147 private transient double maxVolume; 148 149 /** 150 * A flag that controls whether or not the renderer's outline paint is 151 * used to draw the outline of the candlestick. The default value is 152 * {@code false} to avoid a change of behaviour for existing code. 153 * 154 * @since 1.0.5 155 */ 156 private boolean useOutlinePaint; 157 158 /** 159 * Creates a new renderer for candlestick charts. 160 */ 161 public CandlestickRenderer() { 162 this(-1.0); 163 } 164 165 /** 166 * Creates a new renderer for candlestick charts. 167 * <P> 168 * Use -1 for the candle width if you prefer the width to be calculated 169 * automatically. 170 * 171 * @param candleWidth The candle width. 172 */ 173 public CandlestickRenderer(double candleWidth) { 174 this(candleWidth, true, new HighLowItemLabelGenerator()); 175 } 176 177 /** 178 * Creates a new renderer for candlestick charts. 179 * <P> 180 * Use -1 for the candle width if you prefer the width to be calculated 181 * automatically. 182 * 183 * @param candleWidth the candle width. 184 * @param drawVolume a flag indicating whether or not volume bars should 185 * be drawn. 186 * @param toolTipGenerator the tool tip generator. {@code null} is 187 * none. 188 */ 189 public CandlestickRenderer(double candleWidth, boolean drawVolume, 190 XYToolTipGenerator toolTipGenerator) { 191 super(); 192 setDefaultToolTipGenerator(toolTipGenerator); 193 this.candleWidth = candleWidth; 194 this.drawVolume = drawVolume; 195 this.volumePaint = Color.GRAY; 196 this.upPaint = Color.GREEN; 197 this.downPaint = Color.RED; 198 this.useOutlinePaint = false; // false preserves the old behaviour 199 // prior to introducing this flag 200 } 201 202 /** 203 * Returns the width of each candle. 204 * 205 * @return The candle width. 206 * 207 * @see #setCandleWidth(double) 208 */ 209 public double getCandleWidth() { 210 return this.candleWidth; 211 } 212 213 /** 214 * Sets the candle width and sends a {@link RendererChangeEvent} to all 215 * registered listeners. 216 * <P> 217 * If you set the width to a negative value, the renderer will calculate 218 * the candle width automatically based on the space available on the chart. 219 * 220 * @param width The width. 221 * @see #setAutoWidthMethod(int) 222 * @see #setAutoWidthGap(double) 223 * @see #setAutoWidthFactor(double) 224 * @see #setMaxCandleWidthInMilliseconds(double) 225 */ 226 public void setCandleWidth(double width) { 227 if (width != this.candleWidth) { 228 this.candleWidth = width; 229 fireChangeEvent(); 230 } 231 } 232 233 /** 234 * Returns the maximum width (in milliseconds) of each candle. 235 * 236 * @return The maximum candle width in milliseconds. 237 * 238 * @see #setMaxCandleWidthInMilliseconds(double) 239 */ 240 public double getMaxCandleWidthInMilliseconds() { 241 return this.maxCandleWidthInMilliseconds; 242 } 243 244 /** 245 * Sets the maximum candle width (in milliseconds) and sends a 246 * {@link RendererChangeEvent} to all registered listeners. 247 * 248 * @param millis The maximum width. 249 * 250 * @see #getMaxCandleWidthInMilliseconds() 251 * @see #setCandleWidth(double) 252 * @see #setAutoWidthMethod(int) 253 * @see #setAutoWidthGap(double) 254 * @see #setAutoWidthFactor(double) 255 */ 256 public void setMaxCandleWidthInMilliseconds(double millis) { 257 this.maxCandleWidthInMilliseconds = millis; 258 fireChangeEvent(); 259 } 260 261 /** 262 * Returns the method of automatically calculating the candle width. 263 * 264 * @return The method of automatically calculating the candle width. 265 * 266 * @see #setAutoWidthMethod(int) 267 */ 268 public int getAutoWidthMethod() { 269 return this.autoWidthMethod; 270 } 271 272 /** 273 * Sets the method of automatically calculating the candle width and 274 * sends a {@link RendererChangeEvent} to all registered listeners. 275 * <p> 276 * {@code WIDTHMETHOD_AVERAGE}: Divides the entire display (ignoring 277 * scale factor) by the number of items, and uses this as the available 278 * width.<br> 279 * {@code WIDTHMETHOD_SMALLEST}: Checks the interval between each 280 * item, and uses the smallest as the available width.<br> 281 * {@code WIDTHMETHOD_INTERVALDATA}: Assumes that the dataset supports 282 * the IntervalXYDataset interface, and uses the startXValue - endXValue as 283 * the available width. 284 * <br> 285 * 286 * @param autoWidthMethod The method of automatically calculating the 287 * candle width. 288 * 289 * @see #WIDTHMETHOD_AVERAGE 290 * @see #WIDTHMETHOD_SMALLEST 291 * @see #WIDTHMETHOD_INTERVALDATA 292 * @see #getAutoWidthMethod() 293 * @see #setCandleWidth(double) 294 * @see #setAutoWidthGap(double) 295 * @see #setAutoWidthFactor(double) 296 * @see #setMaxCandleWidthInMilliseconds(double) 297 */ 298 public void setAutoWidthMethod(int autoWidthMethod) { 299 if (this.autoWidthMethod != autoWidthMethod) { 300 this.autoWidthMethod = autoWidthMethod; 301 fireChangeEvent(); 302 } 303 } 304 305 /** 306 * Returns the factor by which the available space automatically 307 * calculated for the candles will be multiplied to determine the actual 308 * width to use. 309 * 310 * @return The width factor (generally between 0.0 and 1.0). 311 * 312 * @see #setAutoWidthFactor(double) 313 */ 314 public double getAutoWidthFactor() { 315 return this.autoWidthFactor; 316 } 317 318 /** 319 * Sets the factor by which the available space automatically calculated 320 * for the candles will be multiplied to determine the actual width to use. 321 * 322 * @param autoWidthFactor The width factor (generally between 0.0 and 1.0). 323 * 324 * @see #getAutoWidthFactor() 325 * @see #setCandleWidth(double) 326 * @see #setAutoWidthMethod(int) 327 * @see #setAutoWidthGap(double) 328 * @see #setMaxCandleWidthInMilliseconds(double) 329 */ 330 public void setAutoWidthFactor(double autoWidthFactor) { 331 if (this.autoWidthFactor != autoWidthFactor) { 332 this.autoWidthFactor = autoWidthFactor; 333 fireChangeEvent(); 334 } 335 } 336 337 /** 338 * Returns the amount of space to leave on the left and right of each 339 * candle when automatically calculating widths. 340 * 341 * @return The gap. 342 * 343 * @see #setAutoWidthGap(double) 344 */ 345 public double getAutoWidthGap() { 346 return this.autoWidthGap; 347 } 348 349 /** 350 * Sets the amount of space to leave on the left and right of each candle 351 * when automatically calculating widths and sends a 352 * {@link RendererChangeEvent} to all registered listeners. 353 * 354 * @param autoWidthGap The gap. 355 * 356 * @see #getAutoWidthGap() 357 * @see #setCandleWidth(double) 358 * @see #setAutoWidthMethod(int) 359 * @see #setAutoWidthFactor(double) 360 * @see #setMaxCandleWidthInMilliseconds(double) 361 */ 362 public void setAutoWidthGap(double autoWidthGap) { 363 if (this.autoWidthGap != autoWidthGap) { 364 this.autoWidthGap = autoWidthGap; 365 fireChangeEvent(); 366 } 367 } 368 369 /** 370 * Returns the paint used to fill candles when the price moves up from open 371 * to close. 372 * 373 * @return The paint (possibly {@code null}). 374 * 375 * @see #setUpPaint(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 and sends a {@link RendererChangeEvent} to all registered 384 * listeners. 385 * 386 * @param paint the paint ({@code null} permitted). 387 * 388 * @see #getUpPaint() 389 */ 390 public void setUpPaint(Paint paint) { 391 this.upPaint = paint; 392 fireChangeEvent(); 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 (possibly {@code null}). 400 * 401 * @see #setDownPaint(Paint) 402 */ 403 public Paint getDownPaint() { 404 return this.downPaint; 405 } 406 407 /** 408 * Sets the paint used to fill candles when the price moves down from open 409 * to close and sends a {@link RendererChangeEvent} to all registered 410 * listeners. 411 * 412 * @param paint The paint ({@code null} permitted). 413 */ 414 public void setDownPaint(Paint paint) { 415 this.downPaint = paint; 416 fireChangeEvent(); 417 } 418 419 /** 420 * Returns a flag indicating whether or not volume bars are drawn on the 421 * chart. 422 * 423 * @return A boolean. 424 * 425 * @since 1.0.5 426 * 427 * @see #setDrawVolume(boolean) 428 */ 429 public boolean getDrawVolume() { 430 return this.drawVolume; 431 } 432 433 /** 434 * Sets a flag that controls whether or not volume bars are drawn in the 435 * background and sends a {@link RendererChangeEvent} to all registered 436 * listeners. 437 * 438 * @param flag the flag. 439 * 440 * @see #getDrawVolume() 441 */ 442 public void setDrawVolume(boolean flag) { 443 if (this.drawVolume != flag) { 444 this.drawVolume = flag; 445 fireChangeEvent(); 446 } 447 } 448 449 /** 450 * Returns the paint that is used to fill the volume bars if they are 451 * visible. 452 * 453 * @return The paint (never {@code null}). 454 * 455 * @see #setVolumePaint(Paint) 456 * 457 * @since 1.0.7 458 */ 459 public Paint getVolumePaint() { 460 return this.volumePaint; 461 } 462 463 /** 464 * Sets the paint used to fill the volume bars, and sends a 465 * {@link RendererChangeEvent} to all registered listeners. 466 * 467 * @param paint the paint ({@code null} not permitted). 468 * 469 * @see #getVolumePaint() 470 * @see #getDrawVolume() 471 * 472 * @since 1.0.7 473 */ 474 public void setVolumePaint(Paint paint) { 475 Args.nullNotPermitted(paint, "paint"); 476 this.volumePaint = paint; 477 fireChangeEvent(); 478 } 479 480 /** 481 * Returns the flag that controls whether or not the renderer's outline 482 * paint is used to draw the candlestick outline. The default value is 483 * {@code false}. 484 * 485 * @return A boolean. 486 * 487 * @since 1.0.5 488 * 489 * @see #setUseOutlinePaint(boolean) 490 */ 491 public boolean getUseOutlinePaint() { 492 return this.useOutlinePaint; 493 } 494 495 /** 496 * Sets the flag that controls whether or not the renderer's outline 497 * paint is used to draw the candlestick outline, and sends a 498 * {@link RendererChangeEvent} to all registered listeners. 499 * 500 * @param use the new flag value. 501 * 502 * @since 1.0.5 503 * 504 * @see #getUseOutlinePaint() 505 */ 506 public void setUseOutlinePaint(boolean use) { 507 if (this.useOutlinePaint != use) { 508 this.useOutlinePaint = use; 509 fireChangeEvent(); 510 } 511 } 512 513 /** 514 * Returns the range of values the renderer requires to display all the 515 * items from the specified dataset. 516 * 517 * @param dataset the dataset ({@code null} permitted). 518 * 519 * @return The range ({@code null} if the dataset is {@code null} 520 * or empty). 521 */ 522 @Override 523 public Range findRangeBounds(XYDataset dataset) { 524 return findRangeBounds(dataset, true); 525 } 526 527 /** 528 * Initialises the renderer then returns the number of 'passes' through the 529 * data that the renderer will require (usually just one). This method 530 * will be called before the first item is rendered, giving the renderer 531 * an opportunity to initialise any state information it wants to maintain. 532 * The renderer can do nothing if it chooses. 533 * 534 * @param g2 the graphics device. 535 * @param dataArea the area inside the axes. 536 * @param plot the plot. 537 * @param dataset the data. 538 * @param info an optional info collection object to return data back to 539 * the caller. 540 * 541 * @return The number of passes the renderer requires. 542 */ 543 @Override 544 public XYItemRendererState initialise(Graphics2D g2, Rectangle2D dataArea, 545 XYPlot plot, XYDataset dataset, 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} permitted). 594 * @param pass the pass index. 595 */ 596 @Override 597 public void drawItem(Graphics2D g2, XYItemRendererState state, 598 Rectangle2D dataArea, PlotRenderingInfo info, XYPlot plot, 599 ValueAxis domainAxis, ValueAxis rangeAxis, XYDataset dataset, 600 int series, int item, CrosshairState crosshairState, int pass) { 601 602 boolean horiz; 603 PlotOrientation orientation = plot.getOrientation(); 604 if (orientation == PlotOrientation.HORIZONTAL) { 605 horiz = true; 606 } 607 else if (orientation == PlotOrientation.VERTICAL) { 608 horiz = false; 609 } 610 else { 611 return; 612 } 613 614 // setup for collecting optional entity info... 615 EntityCollection entities = null; 616 if (info != null) { 617 entities = info.getOwner().getEntityCollection(); 618 } 619 620 OHLCDataset highLowData = (OHLCDataset) dataset; 621 622 double x = highLowData.getXValue(series, item); 623 double yHigh = highLowData.getHighValue(series, item); 624 double yLow = highLowData.getLowValue(series, item); 625 double yOpen = highLowData.getOpenValue(series, item); 626 double yClose = highLowData.getCloseValue(series, item); 627 628 RectangleEdge domainEdge = plot.getDomainAxisEdge(); 629 double xx = domainAxis.valueToJava2D(x, dataArea, domainEdge); 630 631 RectangleEdge edge = plot.getRangeAxisEdge(); 632 double yyHigh = rangeAxis.valueToJava2D(yHigh, dataArea, edge); 633 double yyLow = rangeAxis.valueToJava2D(yLow, dataArea, edge); 634 double yyOpen = rangeAxis.valueToJava2D(yOpen, dataArea, edge); 635 double yyClose = rangeAxis.valueToJava2D(yClose, dataArea, edge); 636 637 double volumeWidth; 638 double stickWidth; 639 if (this.candleWidth > 0) { 640 // These are deliberately not bounded to minimums/maxCandleWidth to 641 // retain old behaviour. 642 volumeWidth = this.candleWidth; 643 stickWidth = this.candleWidth; 644 } 645 else { 646 double xxWidth = 0; 647 int itemCount; 648 switch (this.autoWidthMethod) { 649 650 case WIDTHMETHOD_AVERAGE: 651 itemCount = highLowData.getItemCount(series); 652 if (horiz) { 653 xxWidth = dataArea.getHeight() / itemCount; 654 } 655 else { 656 xxWidth = dataArea.getWidth() / itemCount; 657 } 658 break; 659 660 case WIDTHMETHOD_SMALLEST: 661 // Note: It would be nice to pre-calculate this per series 662 itemCount = highLowData.getItemCount(series); 663 double lastPos = -1; 664 xxWidth = dataArea.getWidth(); 665 for (int i = 0; i < itemCount; i++) { 666 double pos = domainAxis.valueToJava2D( 667 highLowData.getXValue(series, i), dataArea, 668 domainEdge); 669 if (lastPos != -1) { 670 xxWidth = Math.min(xxWidth, 671 Math.abs(pos - lastPos)); 672 } 673 lastPos = pos; 674 } 675 break; 676 677 case WIDTHMETHOD_INTERVALDATA: 678 IntervalXYDataset intervalXYData 679 = (IntervalXYDataset) dataset; 680 double startPos = domainAxis.valueToJava2D( 681 intervalXYData.getStartXValue(series, item), 682 dataArea, plot.getDomainAxisEdge()); 683 double endPos = domainAxis.valueToJava2D( 684 intervalXYData.getEndXValue(series, item), 685 dataArea, plot.getDomainAxisEdge()); 686 xxWidth = Math.abs(endPos - startPos); 687 break; 688 689 } 690 xxWidth -= 2 * this.autoWidthGap; 691 xxWidth *= this.autoWidthFactor; 692 xxWidth = Math.min(xxWidth, this.maxCandleWidth); 693 volumeWidth = Math.max(Math.min(1, this.maxCandleWidth), xxWidth); 694 stickWidth = Math.max(Math.min(3, this.maxCandleWidth), xxWidth); 695 } 696 697 Paint p = getItemPaint(series, item); 698 Paint outlinePaint = null; 699 if (this.useOutlinePaint) { 700 outlinePaint = getItemOutlinePaint(series, item); 701 } 702 Stroke s = getItemStroke(series, item); 703 704 g2.setStroke(s); 705 706 if (this.drawVolume) { 707 int volume = (int) highLowData.getVolumeValue(series, item); 708 double volumeHeight = volume / this.maxVolume; 709 710 double min, max; 711 if (horiz) { 712 min = dataArea.getMinX(); 713 max = dataArea.getMaxX(); 714 } 715 else { 716 min = dataArea.getMinY(); 717 max = dataArea.getMaxY(); 718 } 719 720 double zzVolume = volumeHeight * (max - min); 721 722 g2.setPaint(getVolumePaint()); 723 Composite originalComposite = g2.getComposite(); 724 g2.setComposite(AlphaComposite.getInstance( 725 AlphaComposite.SRC_OVER, 0.3f)); 726 727 if (horiz) { 728 g2.fill(new Rectangle2D.Double(min, xx - volumeWidth / 2, 729 zzVolume, volumeWidth)); 730 } 731 else { 732 g2.fill(new Rectangle2D.Double(xx - volumeWidth / 2, 733 max - zzVolume, volumeWidth, zzVolume)); 734 } 735 736 g2.setComposite(originalComposite); 737 } 738 739 if (this.useOutlinePaint) { 740 g2.setPaint(outlinePaint); 741 } 742 else { 743 g2.setPaint(p); 744 } 745 746 double yyMaxOpenClose = Math.max(yyOpen, yyClose); 747 double yyMinOpenClose = Math.min(yyOpen, yyClose); 748 double maxOpenClose = Math.max(yOpen, yClose); 749 double minOpenClose = Math.min(yOpen, yClose); 750 751 // draw the upper shadow 752 if (yHigh > maxOpenClose) { 753 if (horiz) { 754 g2.draw(new Line2D.Double(yyHigh, xx, yyMaxOpenClose, xx)); 755 } 756 else { 757 g2.draw(new Line2D.Double(xx, yyHigh, xx, yyMaxOpenClose)); 758 } 759 } 760 761 // draw the lower shadow 762 if (yLow < minOpenClose) { 763 if (horiz) { 764 g2.draw(new Line2D.Double(yyLow, xx, yyMinOpenClose, xx)); 765 } 766 else { 767 g2.draw(new Line2D.Double(xx, yyLow, xx, yyMinOpenClose)); 768 } 769 } 770 771 // draw the body 772 Rectangle2D body; 773 Rectangle2D hotspot; 774 double length = Math.abs(yyHigh - yyLow); 775 double base = Math.min(yyHigh, yyLow); 776 if (horiz) { 777 body = new Rectangle2D.Double(yyMinOpenClose, xx - stickWidth / 2, 778 yyMaxOpenClose - yyMinOpenClose, stickWidth); 779 hotspot = new Rectangle2D.Double(base, xx - stickWidth / 2, 780 length, stickWidth); 781 } 782 else { 783 body = new Rectangle2D.Double(xx - stickWidth / 2, yyMinOpenClose, 784 stickWidth, yyMaxOpenClose - yyMinOpenClose); 785 hotspot = new Rectangle2D.Double(xx - stickWidth / 2, 786 base, stickWidth, length); 787 } 788 if (yClose > yOpen) { 789 if (this.upPaint != null) { 790 g2.setPaint(this.upPaint); 791 } 792 else { 793 g2.setPaint(p); 794 } 795 g2.fill(body); 796 } 797 else { 798 if (this.downPaint != null) { 799 g2.setPaint(this.downPaint); 800 } 801 else { 802 g2.setPaint(p); 803 } 804 g2.fill(body); 805 } 806 if (this.useOutlinePaint) { 807 g2.setPaint(outlinePaint); 808 } 809 else { 810 g2.setPaint(p); 811 } 812 g2.draw(body); 813 814 // add an entity for the item... 815 if (entities != null) { 816 addEntity(entities, hotspot, dataset, series, item, 0.0, 0.0); 817 } 818 819 } 820 821 /** 822 * Tests this renderer for equality with another object. 823 * 824 * @param obj the object ({@code null} permitted). 825 * 826 * @return {@code true} or {@code false}. 827 */ 828 @Override 829 public boolean equals(Object obj) { 830 if (obj == this) { 831 return true; 832 } 833 if (!(obj instanceof CandlestickRenderer)) { 834 return false; 835 } 836 CandlestickRenderer that = (CandlestickRenderer) obj; 837 if (this.candleWidth != that.candleWidth) { 838 return false; 839 } 840 if (!PaintUtils.equal(this.upPaint, that.upPaint)) { 841 return false; 842 } 843 if (!PaintUtils.equal(this.downPaint, that.downPaint)) { 844 return false; 845 } 846 if (this.drawVolume != that.drawVolume) { 847 return false; 848 } 849 if (this.maxCandleWidthInMilliseconds 850 != that.maxCandleWidthInMilliseconds) { 851 return false; 852 } 853 if (this.autoWidthMethod != that.autoWidthMethod) { 854 return false; 855 } 856 if (this.autoWidthFactor != that.autoWidthFactor) { 857 return false; 858 } 859 if (this.autoWidthGap != that.autoWidthGap) { 860 return false; 861 } 862 if (this.useOutlinePaint != that.useOutlinePaint) { 863 return false; 864 } 865 if (!PaintUtils.equal(this.volumePaint, that.volumePaint)) { 866 return false; 867 } 868 return super.equals(obj); 869 } 870 871 /** 872 * Returns a clone of the renderer. 873 * 874 * @return A clone. 875 * 876 * @throws CloneNotSupportedException if the renderer cannot be cloned. 877 */ 878 @Override 879 public Object clone() throws CloneNotSupportedException { 880 return super.clone(); 881 } 882 883 /** 884 * Provides serialization support. 885 * 886 * @param stream the output stream. 887 * 888 * @throws IOException if there is an I/O error. 889 */ 890 private void writeObject(ObjectOutputStream stream) throws IOException { 891 stream.defaultWriteObject(); 892 SerialUtils.writePaint(this.upPaint, stream); 893 SerialUtils.writePaint(this.downPaint, stream); 894 SerialUtils.writePaint(this.volumePaint, stream); 895 } 896 897 /** 898 * Provides serialization support. 899 * 900 * @param stream the input stream. 901 * 902 * @throws IOException if there is an I/O error. 903 * @throws ClassNotFoundException if there is a classpath problem. 904 */ 905 private void readObject(ObjectInputStream stream) 906 throws IOException, ClassNotFoundException { 907 stream.defaultReadObject(); 908 this.upPaint = SerialUtils.readPaint(stream); 909 this.downPaint = SerialUtils.readPaint(stream); 910 this.volumePaint = SerialUtils.readPaint(stream); 911 } 912 913}