1 83 84 package org.jfree.chart.renderer.xy; 85 86 import java.awt.AlphaComposite ; 87 import java.awt.Color ; 88 import java.awt.Composite ; 89 import java.awt.Graphics2D ; 90 import java.awt.Paint ; 91 import java.awt.Shape ; 92 import java.awt.Stroke ; 93 import java.awt.geom.Line2D ; 94 import java.awt.geom.Rectangle2D ; 95 import java.io.IOException ; 96 import java.io.ObjectInputStream ; 97 import java.io.ObjectOutputStream ; 98 import java.io.Serializable ; 99 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 125 public class CandlestickRenderer extends AbstractXYItemRenderer 126 implements XYItemRenderer, 127 Cloneable , 128 PublicCloneable, 129 Serializable { 130 131 132 private static final long serialVersionUID = 50390395841817121L; 133 134 135 public static final int WIDTHMETHOD_AVERAGE = 0; 136 137 138 public static final int WIDTHMETHOD_SMALLEST = 1; 139 140 141 public static final int WIDTHMETHOD_INTERVALDATA = 2; 142 143 144 private int autoWidthMethod = WIDTHMETHOD_AVERAGE; 145 146 151 private double autoWidthFactor = 4.5 / 7; 152 153 154 private double autoWidthGap = 0.0; 155 156 157 private double candleWidth; 158 159 160 private double maxCandleWidthInMilliseconds = 1000.0 * 60.0 * 60.0 * 20.0; 161 162 163 private double maxCandleWidth; 164 165 169 private transient Paint upPaint; 170 171 175 private transient Paint downPaint; 176 177 178 private boolean drawVolume; 179 180 181 private transient double maxVolume; 182 183 186 public CandlestickRenderer() { 187 this(-1.0); 188 } 189 190 198 public CandlestickRenderer(double candleWidth) { 199 this(candleWidth, true, new HighLowItemLabelGenerator()); 200 } 201 202 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 233 public double getCandleWidth() { 234 return this.candleWidth; 235 } 236 237 249 public void setCandleWidth(double width) { 250 if (width != this.candleWidth) { 251 this.candleWidth = width; 252 notifyListeners(new RendererChangeEvent(this)); 253 } 254 } 255 256 261 public double getMaxCandleWidthInMilliseconds() { 262 return this.maxCandleWidthInMilliseconds; 263 } 264 265 274 public void setMaxCandleWidthInMilliseconds(double millis) { 275 this.maxCandleWidthInMilliseconds = millis; 276 notifyListeners(new RendererChangeEvent(this)); 277 } 278 279 284 public int getAutoWidthMethod() { 285 return this.autoWidthMethod; 286 } 287 288 312 public void setAutoWidthMethod(int autoWidthMethod) { 313 if (this.autoWidthMethod != autoWidthMethod) { 314 this.autoWidthMethod = autoWidthMethod; 315 notifyListeners(new RendererChangeEvent(this)); 316 } 317 } 318 319 326 public double getAutoWidthFactor() { 327 return this.autoWidthFactor; 328 } 329 330 340 public void setAutoWidthFactor(double autoWidthFactor) { 341 if (this.autoWidthFactor != autoWidthFactor) { 342 this.autoWidthFactor = autoWidthFactor; 343 notifyListeners(new RendererChangeEvent(this)); 344 } 345 } 346 347 353 public double getAutoWidthGap() { 354 return this.autoWidthGap; 355 } 356 357 367 public void setAutoWidthGap(double autoWidthGap) { 368 if (this.autoWidthGap != autoWidthGap) { 369 this.autoWidthGap = autoWidthGap; 370 notifyListeners(new RendererChangeEvent(this)); 371 } 372 } 373 374 380 public Paint getUpPaint() { 381 return this.upPaint; 382 } 383 384 393 public void setUpPaint(Paint paint) { 394 this.upPaint = paint; 395 notifyListeners(new RendererChangeEvent(this)); 396 } 397 398 404 public Paint getDownPaint() { 405 return this.downPaint; 406 } 407 408 417 public void setDownPaint(Paint paint) { 418 this.downPaint = paint; 419 notifyListeners(new RendererChangeEvent(this)); 420 } 421 422 428 public boolean drawVolume() { 429 return this.drawVolume; 430 } 431 432 438 public void setDrawVolume(boolean flag) { 439 if (this.drawVolume != flag) { 440 this.drawVolume = flag; 441 notifyListeners(new RendererChangeEvent(this)); 442 } 443 } 444 445 461 public XYItemRendererState initialise(Graphics2D g2, 462 Rectangle2D dataArea, 463 XYPlot plot, 464 XYDataset dataset, 465 PlotRenderingInfo info) { 466 467 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 478 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 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 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 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 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 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 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 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 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 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 796 public Object clone() throws CloneNotSupportedException { 797 return super.clone(); 798 } 799 800 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 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 } 829 | Popular Tags |