1 72 73 package org.jfree.chart.renderer.xy; 74 75 import java.awt.Color ; 76 import java.awt.Graphics2D ; 77 import java.awt.Paint ; 78 import java.awt.Shape ; 79 import java.awt.Stroke ; 80 import java.awt.geom.Ellipse2D ; 81 import java.awt.geom.Line2D ; 82 import java.awt.geom.Point2D ; 83 import java.awt.geom.Rectangle2D ; 84 import java.io.IOException ; 85 import java.io.ObjectInputStream ; 86 import java.io.ObjectOutputStream ; 87 import java.io.Serializable ; 88 import java.util.ArrayList ; 89 import java.util.Collections ; 90 import java.util.Iterator ; 91 import java.util.List ; 92 93 import org.jfree.chart.axis.ValueAxis; 94 import org.jfree.chart.entity.EntityCollection; 95 import org.jfree.chart.entity.XYItemEntity; 96 import org.jfree.chart.event.RendererChangeEvent; 97 import org.jfree.chart.labels.BoxAndWhiskerXYToolTipGenerator; 98 import org.jfree.chart.labels.XYToolTipGenerator; 99 import org.jfree.chart.plot.CrosshairState; 100 import org.jfree.chart.plot.PlotOrientation; 101 import org.jfree.chart.plot.PlotRenderingInfo; 102 import org.jfree.chart.plot.XYPlot; 103 import org.jfree.chart.renderer.Outlier; 104 import org.jfree.chart.renderer.OutlierList; 105 import org.jfree.chart.renderer.OutlierListCollection; 106 import org.jfree.data.statistics.BoxAndWhiskerXYDataset; 107 import org.jfree.data.xy.XYDataset; 108 import org.jfree.io.SerialUtilities; 109 import org.jfree.ui.RectangleEdge; 110 import org.jfree.util.PaintUtilities; 111 import org.jfree.util.PublicCloneable; 112 113 121 public class XYBoxAndWhiskerRenderer extends AbstractXYItemRenderer 122 implements XYItemRenderer, 123 Cloneable , 124 PublicCloneable, 125 Serializable { 126 127 128 private static final long serialVersionUID = -8020170108532232324L; 129 130 131 private double boxWidth; 132 133 134 private transient Paint boxPaint; 135 136 137 private boolean fillBox; 138 139 143 private transient Paint artifactPaint = Color.black; 144 145 148 public XYBoxAndWhiskerRenderer() { 149 this(-1.0); 150 } 151 152 160 public XYBoxAndWhiskerRenderer(double boxWidth) { 161 super(); 162 this.boxWidth = boxWidth; 163 this.boxPaint = Color.green; 164 this.fillBox = true; 165 setToolTipGenerator(new BoxAndWhiskerXYToolTipGenerator()); 166 } 167 168 173 public double getBoxWidth() { 174 return this.boxWidth; 175 } 176 177 185 public void setBoxWidth(double width) { 186 if (width != this.boxWidth) { 187 this.boxWidth = width; 188 notifyListeners(new RendererChangeEvent(this)); 189 } 190 } 191 192 197 public Paint getBoxPaint() { 198 return this.boxPaint; 199 } 200 201 207 public void setBoxPaint(Paint paint) { 208 this.boxPaint = paint; 209 notifyListeners(new RendererChangeEvent(this)); 210 } 211 212 217 public boolean getFillBox() { 218 return this.fillBox; 219 } 220 221 227 public void setFillBox(boolean flag) { 228 this.fillBox = flag; 229 notifyListeners(new RendererChangeEvent(this)); 230 } 231 232 238 public Paint getArtifactPaint() { 239 return this.artifactPaint; 240 } 241 242 248 public void setArtifactPaint(Paint artifactPaint) { 249 this.artifactPaint = artifactPaint; 250 } 251 252 270 public void drawItem(Graphics2D g2, 271 XYItemRendererState state, 272 Rectangle2D dataArea, 273 PlotRenderingInfo info, 274 XYPlot plot, 275 ValueAxis domainAxis, 276 ValueAxis rangeAxis, 277 XYDataset dataset, 278 int series, 279 int item, 280 CrosshairState crosshairState, 281 int pass) { 282 283 PlotOrientation orientation = plot.getOrientation(); 284 285 if (orientation == PlotOrientation.HORIZONTAL) { 286 drawHorizontalItem( 287 g2, dataArea, info, plot, domainAxis, rangeAxis, 288 dataset, series, item, crosshairState, pass 289 ); 290 } 291 else if (orientation == PlotOrientation.VERTICAL) { 292 drawVerticalItem( 293 g2, dataArea, info, plot, domainAxis, rangeAxis, 294 dataset, series, item, crosshairState, pass 295 ); 296 } 297 298 } 299 300 317 public void drawHorizontalItem(Graphics2D g2, 318 Rectangle2D dataArea, 319 PlotRenderingInfo info, 320 XYPlot plot, 321 ValueAxis domainAxis, 322 ValueAxis rangeAxis, 323 XYDataset dataset, 324 int series, 325 int item, 326 CrosshairState crosshairState, 327 int pass) { 328 329 EntityCollection entities = null; 331 if (info != null) { 332 entities = info.getOwner().getEntityCollection(); 333 } 334 335 BoxAndWhiskerXYDataset boxAndWhiskerData 336 = (BoxAndWhiskerXYDataset) dataset; 337 338 Number x = boxAndWhiskerData.getX(series, item); 339 Number yMax = boxAndWhiskerData.getMaxRegularValue(series, item); 340 Number yMin = boxAndWhiskerData.getMinRegularValue(series, item); 341 Number yQ1Median = boxAndWhiskerData.getQ1Value(series, item); 342 Number yQ3Median = boxAndWhiskerData.getQ3Value(series, item); 343 344 double xx = domainAxis.valueToJava2D( 345 x.doubleValue(), dataArea, plot.getDomainAxisEdge() 346 ); 347 348 RectangleEdge location = plot.getRangeAxisEdge(); 349 double yyMax = rangeAxis.valueToJava2D( 350 yMax.doubleValue(), dataArea, location 351 ); 352 double yyMin = rangeAxis.valueToJava2D( 353 yMin.doubleValue(), dataArea, location 354 ); 355 356 double yyQ1Median = rangeAxis.valueToJava2D( 357 yQ1Median.doubleValue(), dataArea, location 358 ); 359 double yyQ3Median = rangeAxis.valueToJava2D( 360 yQ3Median.doubleValue(), dataArea, location 361 ); 362 363 double exactCandleWidth = getBoxWidth(); 364 double thisCandleWidth = exactCandleWidth; 365 if (exactCandleWidth <= 0.0) { 366 int itemCount = boxAndWhiskerData.getItemCount(series); 367 exactCandleWidth = (dataArea.getHeight()) / itemCount * 4.5 / 7; 368 if (exactCandleWidth < 1) { 369 exactCandleWidth = 1; 370 } 371 thisCandleWidth = exactCandleWidth; 372 if (thisCandleWidth < 3) { 373 thisCandleWidth = 3; 374 } 375 } 376 377 Stroke s = getItemStroke(series, item); 378 379 g2.setStroke(s); 380 381 if ((yyMax > yyQ1Median) && (yyMax > yyQ3Median)) { 383 g2.draw( 384 new Line2D.Double (yyMax, xx, Math.max(yyQ1Median, yyQ3Median), 385 xx) 386 ); 387 } 388 389 if ((yyMin < yyQ1Median) && (yyMin < yyQ3Median)) { 391 g2.draw( 392 new Line2D.Double (yyMin, xx, Math.min(yyQ1Median, yyQ3Median), 393 xx) 394 ); 395 } 396 397 398 Shape box = null; 400 if (yyQ1Median < yyQ3Median) { 401 box = new Rectangle2D.Double ( 402 yyQ1Median, xx - thisCandleWidth / 2, yyQ3Median - yyQ1Median, 403 thisCandleWidth 404 ); 405 } 406 else { 407 box = new Rectangle2D.Double ( 408 yyQ3Median, xx - thisCandleWidth / 2, yyQ1Median - yyQ3Median, 409 thisCandleWidth 410 ); 411 if (getBoxPaint() != null) { 412 g2.setPaint(getBoxPaint()); 413 } 414 if (this.fillBox) { 415 g2.fill(box); 416 } 417 g2.draw(box); 418 } 419 420 if (entities != null) { 422 String tip = null; 423 XYToolTipGenerator generator = getToolTipGenerator(series, item); 424 if (generator != null) { 425 tip = generator.generateToolTip(dataset, series, item); 426 } 427 String url = null; 428 if (getURLGenerator() != null) { 429 url = getURLGenerator().generateURL(dataset, series, item); 430 } 431 XYItemEntity entity = new XYItemEntity(box, dataset, series, item, 432 tip, url); 433 entities.add(entity); 434 } 435 436 } 437 438 455 public void drawVerticalItem(Graphics2D g2, 456 Rectangle2D dataArea, 457 PlotRenderingInfo info, 458 XYPlot plot, 459 ValueAxis domainAxis, 460 ValueAxis rangeAxis, 461 XYDataset dataset, 462 int series, 463 int item, 464 CrosshairState crosshairState, 465 int pass) { 466 467 EntityCollection entities = null; 469 if (info != null) { 470 entities = info.getOwner().getEntityCollection(); 471 } 472 473 BoxAndWhiskerXYDataset boxAndWhiskerData 474 = (BoxAndWhiskerXYDataset) dataset; 475 476 Number x = boxAndWhiskerData.getX(series, item); 477 Number yMax = boxAndWhiskerData.getMaxRegularValue(series, item); 478 Number yMin = boxAndWhiskerData.getMinRegularValue(series, item); 479 Number yMedian = boxAndWhiskerData.getMedianValue(series, item); 480 Number yAverage = boxAndWhiskerData.getMeanValue(series, item); 481 Number yQ1Median = boxAndWhiskerData.getQ1Value(series, item); 482 Number yQ3Median = boxAndWhiskerData.getQ3Value(series, item); 483 List yOutliers = boxAndWhiskerData.getOutliers(series, item); 484 485 double xx = domainAxis.valueToJava2D(x.doubleValue(), dataArea, 486 plot.getDomainAxisEdge()); 487 488 RectangleEdge location = plot.getRangeAxisEdge(); 489 double yyMax = rangeAxis.valueToJava2D(yMax.doubleValue(), dataArea, 490 location); 491 double yyMin = rangeAxis.valueToJava2D(yMin.doubleValue(), dataArea, 492 location); 493 double yyMedian = rangeAxis.valueToJava2D(yMedian.doubleValue(), 494 dataArea, location); 495 double yyAverage = 0.0; 496 if (yAverage != null) { 497 yyAverage = rangeAxis.valueToJava2D(yAverage.doubleValue(), 498 dataArea, location); 499 } 500 double yyQ1Median = rangeAxis.valueToJava2D(yQ1Median.doubleValue(), 501 dataArea, location); 502 double yyQ3Median = rangeAxis.valueToJava2D(yQ3Median.doubleValue(), 503 dataArea, location); 504 double yyOutlier; 505 506 507 double exactBoxWidth = getBoxWidth(); 508 double width = exactBoxWidth; 509 double dataAreaX = dataArea.getMaxX() - dataArea.getMinX(); 510 double maxBoxPercent = 0.1; 511 double maxBoxWidth = dataAreaX * maxBoxPercent; 512 if (exactBoxWidth <= 0.0) { 513 int itemCount = boxAndWhiskerData.getItemCount(series); 514 exactBoxWidth = dataAreaX / itemCount * 4.5 / 7; 515 if (exactBoxWidth < 3) { 516 width = 3; 517 } 518 else if (exactBoxWidth > maxBoxWidth) { 519 width = maxBoxWidth; 520 } 521 else { 522 width = exactBoxWidth; 523 } 524 } 525 526 Paint p = getBoxPaint(); 527 if (p != null) { 528 g2.setPaint(p); 529 } 530 Stroke s = getItemStroke(series, item); 531 532 g2.setStroke(s); 533 534 g2.draw(new Line2D.Double (xx, yyMax, xx, yyQ3Median)); 536 g2.draw(new Line2D.Double (xx - width / 2, yyMax, xx + width / 2, 537 yyMax)); 538 539 g2.draw(new Line2D.Double (xx, yyMin, xx, yyQ1Median)); 541 g2.draw(new Line2D.Double (xx - width / 2, yyMin, xx + width / 2, 542 yyMin)); 543 544 Shape box = null; 546 if (yyQ1Median > yyQ3Median) { 547 box = new Rectangle2D.Double ( 548 xx - width / 2, yyQ3Median, width, yyQ1Median - yyQ3Median 549 ); 550 } 551 else { 552 box = new Rectangle2D.Double ( 553 xx - width / 2, yyQ1Median, width, yyQ3Median - yyQ1Median 554 ); 555 } 556 if (this.fillBox) { 557 g2.fill(box); 558 } 559 g2.draw(box); 560 561 g2.setPaint(getArtifactPaint()); 563 g2.draw(new Line2D.Double (xx - width / 2, yyMedian, xx + width / 2, 564 yyMedian)); 565 566 double aRadius = 0; double oRadius = width / 3; 569 if (yAverage != null) { 571 aRadius = width / 4; 572 Ellipse2D.Double avgEllipse = new Ellipse2D.Double ( 573 xx - aRadius, yyAverage - aRadius, aRadius * 2, aRadius * 2 574 ); 575 g2.fill(avgEllipse); 576 g2.draw(avgEllipse); 577 } 578 579 List outliers = new ArrayList (); 580 OutlierListCollection outlierListCollection 581 = new OutlierListCollection(); 582 583 587 588 for (int i = 0; i < yOutliers.size(); i++) { 589 double outlier = ((Number ) yOutliers.get(i)).doubleValue(); 590 if (outlier > boxAndWhiskerData.getMaxOutlier(series, 591 item).doubleValue()) { 592 outlierListCollection.setHighFarOut(true); 593 } 594 else if (outlier < boxAndWhiskerData.getMinOutlier(series, 595 item).doubleValue()) { 596 outlierListCollection.setLowFarOut(true); 597 } 598 else if (outlier > boxAndWhiskerData.getMaxRegularValue(series, 599 item).doubleValue()) { 600 yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea, 601 location); 602 outliers.add(new Outlier(xx, yyOutlier, oRadius)); 603 } 604 else if (outlier < boxAndWhiskerData.getMinRegularValue(series, 605 item).doubleValue()) { 606 yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea, 607 location); 608 outliers.add(new Outlier(xx, yyOutlier, oRadius)); 609 } 610 Collections.sort(outliers); 611 } 612 613 for (Iterator iterator = outliers.iterator(); iterator.hasNext();) { 616 Outlier outlier = (Outlier) iterator.next(); 617 outlierListCollection.add(outlier); 618 } 619 620 double maxAxisValue = rangeAxis.valueToJava2D( 622 rangeAxis.getUpperBound(), dataArea, location 623 ) + aRadius; 624 double minAxisValue = rangeAxis.valueToJava2D( 625 rangeAxis.getLowerBound(), dataArea, location 626 ) - aRadius; 627 628 for (Iterator iterator = outlierListCollection.iterator(); 630 iterator.hasNext();) { 631 OutlierList list = (OutlierList) iterator.next(); 632 Outlier outlier = list.getAveragedOutlier(); 633 Point2D point = outlier.getPoint(); 634 635 if (list.isMultiple()) { 636 drawMultipleEllipse(point, width, oRadius, g2); 637 } 638 else { 639 drawEllipse(point, oRadius, g2); 640 } 641 } 642 643 if (outlierListCollection.isHighFarOut()) { 645 drawHighFarOut(aRadius, g2, xx, maxAxisValue); 646 } 647 648 if (outlierListCollection.isLowFarOut()) { 649 drawLowFarOut(aRadius, g2, xx, minAxisValue); 650 } 651 652 if (entities != null) { 654 String tip = null; 655 XYToolTipGenerator generator = getToolTipGenerator(series, item); 656 if (generator != null) { 657 tip = generator.generateToolTip(dataset, series, item); 658 } 659 String url = null; 660 if (getURLGenerator() != null) { 661 url = getURLGenerator().generateURL(dataset, series, item); 662 } 663 XYItemEntity entity = new XYItemEntity(box, dataset, series, item, 664 tip, url); 665 entities.add(entity); 666 } 667 668 } 669 670 677 protected void drawEllipse(Point2D point, double oRadius, Graphics2D g2) { 678 Ellipse2D.Double dot = new Ellipse2D.Double ( 679 point.getX() + oRadius / 2, point.getY(), oRadius, oRadius 680 ); 681 g2.draw(dot); 682 } 683 684 692 protected void drawMultipleEllipse(Point2D point, double boxWidth, 693 double oRadius, Graphics2D g2) { 694 695 Ellipse2D.Double dot1 = new Ellipse2D.Double ( 696 point.getX() - (boxWidth / 2) + oRadius, point.getY(), oRadius, 697 oRadius 698 ); 699 Ellipse2D.Double dot2 = new Ellipse2D.Double ( 700 point.getX() + (boxWidth / 2), point.getY(), oRadius, oRadius 701 ); 702 g2.draw(dot1); 703 g2.draw(dot2); 704 705 } 706 707 715 protected void drawHighFarOut(double aRadius, Graphics2D g2, double xx, 716 double m) { 717 double side = aRadius * 2; 718 g2.draw(new Line2D.Double (xx - side, m + side, xx + side, m + side)); 719 g2.draw(new Line2D.Double (xx - side, m + side, xx, m)); 720 g2.draw(new Line2D.Double (xx + side, m + side, xx, m)); 721 } 722 723 731 protected void drawLowFarOut(double aRadius, Graphics2D g2, double xx, 732 double m) { 733 double side = aRadius * 2; 734 g2.draw(new Line2D.Double (xx - side, m - side, xx + side, m - side)); 735 g2.draw(new Line2D.Double (xx - side, m - side, xx, m)); 736 g2.draw(new Line2D.Double (xx + side, m - side, xx, m)); 737 } 738 739 746 public boolean equals(Object obj) { 747 if (obj == this) { 748 return true; 749 } 750 if (!(obj instanceof XYBoxAndWhiskerRenderer)) { 751 return false; 752 } 753 if (!super.equals(obj)) { 754 return false; 755 } 756 XYBoxAndWhiskerRenderer that = (XYBoxAndWhiskerRenderer) obj; 757 if (this.boxWidth != that.getBoxWidth()) { 758 return false; 759 } 760 if (!PaintUtilities.equal(this.boxPaint, that.boxPaint)) { 761 return false; 762 } 763 if (!PaintUtilities.equal(this.artifactPaint, that.artifactPaint)) { 764 return false; 765 } 766 if (this.fillBox != that.fillBox) { 767 return false; 768 } 769 return true; 770 771 } 772 773 780 private void writeObject(ObjectOutputStream stream) throws IOException { 781 782 stream.defaultWriteObject(); 783 SerialUtilities.writePaint(this.boxPaint, stream); 784 SerialUtilities.writePaint(this.artifactPaint, stream); 785 786 } 787 788 796 private void readObject(ObjectInputStream stream) 797 throws IOException , ClassNotFoundException { 798 799 stream.defaultReadObject(); 800 this.boxPaint = SerialUtilities.readPaint(stream); 801 this.artifactPaint = SerialUtilities.readPaint(stream); 802 803 } 804 805 812 public Object clone() throws CloneNotSupportedException { 813 return super.clone(); 814 } 815 816 } 817 | Popular Tags |