1 56 57 package org.jfree.chart.renderer; 58 59 import java.awt.Color ; 60 import java.awt.Graphics2D ; 61 import java.awt.Paint ; 62 import java.awt.Shape ; 63 import java.awt.Stroke ; 64 import java.awt.geom.Ellipse2D ; 65 import java.awt.geom.Line2D ; 66 import java.awt.geom.Point2D ; 67 import java.awt.geom.Rectangle2D ; 68 import java.io.IOException ; 69 import java.io.ObjectInputStream ; 70 import java.io.ObjectOutputStream ; 71 import java.io.Serializable ; 72 import java.util.ArrayList ; 73 import java.util.Collections ; 74 import java.util.Iterator ; 75 import java.util.List ; 76 77 import org.jfree.chart.CrosshairInfo; 78 import org.jfree.chart.axis.ValueAxis; 79 import org.jfree.chart.entity.EntityCollection; 80 import org.jfree.chart.entity.XYItemEntity; 81 import org.jfree.chart.labels.BoxAndWhiskerToolTipGenerator; 82 import org.jfree.chart.plot.PlotOrientation; 83 import org.jfree.chart.plot.PlotRenderingInfo; 84 import org.jfree.chart.plot.XYPlot; 85 import org.jfree.data.XYDataset; 86 import org.jfree.data.statistics.BoxAndWhiskerXYDataset; 87 import org.jfree.io.SerialUtilities; 88 import org.jfree.ui.RectangleEdge; 89 import org.jfree.util.PublicCloneable; 90 91 97 public class XYBoxAndWhiskerRenderer extends AbstractXYItemRenderer 98 implements XYItemRenderer, 99 Cloneable , 100 PublicCloneable, 101 Serializable { 102 103 104 private double boxWidth; 105 106 107 private transient Paint paint; 108 109 113 private Paint artifactPaint = Color.black; 114 115 118 public XYBoxAndWhiskerRenderer() { 119 this(-1.0); 120 } 121 122 129 public XYBoxAndWhiskerRenderer(double boxWidth) { 130 super(); 131 this.boxWidth = boxWidth; 132 this.paint = Color.green; 133 setToolTipGenerator(new BoxAndWhiskerToolTipGenerator()); 134 } 135 136 141 public double getBoxWidth() { 142 return this.boxWidth; 143 } 144 145 153 public void setBoxWidth(double width) { 154 155 if (width != this.boxWidth) { 156 Double old = new Double (this.boxWidth); 157 this.boxWidth = width; 158 this.firePropertyChanged("BoxAndWhiskerRenderer.boxWidth", old, new Double (width)); 159 } 160 161 } 162 163 168 public Paint getPaint() { 169 return this.paint; 170 } 171 172 180 public void setPaint(Paint paint) { 181 182 Paint old = this.paint; 183 this.paint = paint; 184 this.firePropertyChanged("BoxAndWhiskerRenderer.paint", old, paint); 185 186 } 187 188 194 public Paint getArtifactPaint() { 195 return artifactPaint; 196 } 197 198 204 public void setArtifactPaint(Paint artifactPaint) { 205 this.artifactPaint = artifactPaint; 206 } 207 208 209 225 public void drawItem(Graphics2D g2, 226 XYItemRendererState state, 227 Rectangle2D dataArea, 228 PlotRenderingInfo info, 229 XYPlot plot, 230 ValueAxis domainAxis, 231 ValueAxis rangeAxis, 232 XYDataset dataset, 233 int series, 234 int item, 235 CrosshairInfo crosshairInfo, 236 int pass) { 237 238 PlotOrientation orientation = plot.getOrientation(); 239 this.setPaint(getItemPaint(series, item)); 240 241 if (orientation == PlotOrientation.HORIZONTAL) { 242 drawHorizontalItem(g2, dataArea, info, plot, domainAxis, rangeAxis, 243 dataset, series, item, 244 crosshairInfo, pass); 245 } 246 else if (orientation == PlotOrientation.VERTICAL) { 247 drawVerticalItem(g2, dataArea, info, plot, domainAxis, rangeAxis, 248 dataset, series, item, 249 crosshairInfo, pass); 250 } 251 252 } 253 254 269 public void drawHorizontalItem(Graphics2D g2, 270 Rectangle2D dataArea, 271 PlotRenderingInfo info, 272 XYPlot plot, 273 ValueAxis domainAxis, 274 ValueAxis rangeAxis, 275 XYDataset dataset, 276 int series, 277 int item, 278 CrosshairInfo crosshairInfo, 279 int pass) { 280 281 EntityCollection entities = null; 283 if (info != null) { 284 entities = info.getOwner().getEntityCollection(); 285 } 286 287 288 BoxAndWhiskerXYDataset boxAndWhiskerData = (BoxAndWhiskerXYDataset) dataset; 289 290 Number x = boxAndWhiskerData.getXValue(series, item); 291 Number yMax = boxAndWhiskerData.getMaxRegularValue(series, item); 292 Number yMin = boxAndWhiskerData.getMinRegularValue(series, item); 293 Number yQ1Median = boxAndWhiskerData.getQ1Value(series, item); 294 Number yQ3Median = boxAndWhiskerData.getQ3Value(series, item); 295 296 double xx = domainAxis.translateValueToJava2D(x.doubleValue(), dataArea, 297 plot.getDomainAxisEdge()); 298 299 RectangleEdge location = plot.getRangeAxisEdge(); 300 double yyMax = rangeAxis.translateValueToJava2D(yMax.doubleValue(), dataArea, location); 301 double yyMin = rangeAxis.translateValueToJava2D(yMin.doubleValue(), dataArea, location); 302 303 double yyQ1Median = rangeAxis.translateValueToJava2D(yQ1Median.doubleValue(), 304 dataArea, location); 305 double yyQ3Median = rangeAxis.translateValueToJava2D(yQ3Median.doubleValue(), 306 dataArea, location); 307 308 double exactCandleWidth = boxWidth; 309 double thisCandleWidth = boxWidth; 310 if (boxWidth <= 0.0) { 311 int itemCount = boxAndWhiskerData.getItemCount(series); 312 exactCandleWidth = (dataArea.getHeight()) / itemCount * 4.5 / 7; 313 if (exactCandleWidth < 1) { 314 exactCandleWidth = 1; 315 } 316 thisCandleWidth = exactCandleWidth; 317 if (thisCandleWidth < 3) { 318 thisCandleWidth = 3; 319 } 320 } 321 322 Stroke s = getItemStroke(series, item); 323 324 g2.setStroke(s); 325 326 if ((yyMax > yyQ1Median) && (yyMax > yyQ3Median)) { 328 g2.draw(new Line2D.Double (yyMax, xx, Math.max(yyQ1Median, yyQ3Median), xx)); 329 } 330 331 if ((yyMin < yyQ1Median) && (yyMin < yyQ3Median)) { 333 g2.draw(new Line2D.Double (yyMin, xx, Math.min(yyQ1Median, yyQ3Median), xx)); 334 } 335 336 337 Shape body = null; 339 if (yyQ1Median < yyQ3Median) { 340 body = new Rectangle2D.Double (yyQ1Median, xx - thisCandleWidth / 2, 341 yyQ3Median - yyQ1Median, thisCandleWidth); 342 } 343 else { 344 body = new Rectangle2D.Double (yyQ3Median, xx - thisCandleWidth / 2, 345 yyQ1Median - yyQ3Median, thisCandleWidth); 346 if (paint != null) { 347 g2.setPaint(paint); 348 g2.fill(body); 349 } 350 g2.draw(body); 351 } 352 353 if (entities != null) { 355 String tip = null; 356 if (getToolTipGenerator() != null) { 357 tip = getToolTipGenerator().generateToolTip(dataset, series, item); 358 } 359 String url = null; 360 if (getURLGenerator() != null) { 361 url = getURLGenerator().generateURL(dataset, series, item); 362 } 363 XYItemEntity entity = new XYItemEntity(body, dataset, series, item, tip, url); 364 entities.addEntity(entity); 365 } 366 367 } 368 369 384 public void drawVerticalItem(Graphics2D g2, 385 Rectangle2D dataArea, 386 PlotRenderingInfo info, 387 XYPlot plot, 388 ValueAxis domainAxis, 389 ValueAxis rangeAxis, 390 XYDataset dataset, 391 int series, 392 int item, 393 CrosshairInfo crosshairInfo, 394 int pass) { 395 396 EntityCollection entities = null; 398 if (info != null) { 399 entities = info.getOwner().getEntityCollection(); 400 } 401 402 BoxAndWhiskerXYDataset boxAndWhiskerData = (BoxAndWhiskerXYDataset) dataset; 403 404 Number x = boxAndWhiskerData.getXValue(series, item); 405 Number yMax = boxAndWhiskerData.getMaxRegularValue(series, item); 406 Number yMin = boxAndWhiskerData.getMinRegularValue(series, item); 407 Number yMedian = boxAndWhiskerData.getMedianValue(series, item); 408 Number yAverage = boxAndWhiskerData.getMeanValue(series, item); 409 Number yQ1Median = boxAndWhiskerData.getQ1Value(series, item); 410 Number yQ3Median = boxAndWhiskerData.getQ3Value(series, item); 411 List yOutliers = boxAndWhiskerData.getOutliers(series, item); 412 413 double xx = domainAxis.translateValueToJava2D(x.doubleValue(), dataArea, 414 plot.getDomainAxisEdge()); 415 416 RectangleEdge location = plot.getRangeAxisEdge(); 417 double yyMax = rangeAxis.translateValueToJava2D(yMax.doubleValue(), dataArea, location); 418 double yyMin = rangeAxis.translateValueToJava2D(yMin.doubleValue(), dataArea, location); 419 double yyMedian = rangeAxis.translateValueToJava2D(yMedian.doubleValue(), 420 dataArea, location); 421 double yyAverage = 0.0; 422 if (yAverage != null) { 423 yyAverage = rangeAxis.translateValueToJava2D(yAverage.doubleValue(), 424 dataArea, location); 425 } 426 double yyQ1Median = rangeAxis.translateValueToJava2D(yQ1Median.doubleValue(), 427 dataArea, location); double yyQ3Median = rangeAxis.translateValueToJava2D(yQ3Median.doubleValue(), 429 dataArea, location); double yyOutlier; 431 432 433 double exactBoxWidth = boxWidth; 434 double width = this.boxWidth; 435 double dataAreaX = dataArea.getMaxX() - dataArea.getMinX(); 436 double maxBoxPercent = 0.1; 437 double maxBoxWidth = dataAreaX * maxBoxPercent; 438 if (boxWidth <= 0.0) { 439 int itemCount = boxAndWhiskerData.getItemCount(series); 440 exactBoxWidth = dataAreaX / itemCount * 4.5 / 7; 441 if (exactBoxWidth < 3) { 442 width = 3; 443 } 444 else if (exactBoxWidth > maxBoxWidth) { 445 width = maxBoxWidth; 446 } 447 else { 448 width = exactBoxWidth; 449 } 450 } 451 452 Paint p = this.getPaint(); 453 if (p != null) { 454 g2.setPaint(p); 455 } 456 Stroke s = getItemStroke(series, item); 457 458 g2.setStroke(s); 459 460 if ((yyMax < yyQ1Median) && (yyMax < yyQ3Median)) { g2.draw(new Line2D.Double (xx, yyMax, xx, Math.max(yyQ1Median, yyQ3Median))); 463 g2.draw(new Line2D.Double (xx - width / 2, yyMax, xx + width / 2, yyMax)); 464 } 465 466 if ((yyMin > yyQ1Median) && (yyMin > yyQ3Median)) { 468 g2.draw(new Line2D.Double (xx, yyMin, xx, 469 Math.min(yyQ1Median, yyQ3Median))); 470 g2.draw(new Line2D.Double (xx - width / 2, yyMin, xx + width / 2, yyMin)); 471 } 472 473 474 Shape body = null; 476 if (yyQ1Median > yyQ3Median) { 477 body = new Rectangle2D.Double (xx - width / 2, yyQ3Median, 478 width, yyQ1Median - yyQ3Median); 479 } 480 else { 481 body = new Rectangle2D.Double (xx - width / 2, yyQ1Median, 482 width, yyQ3Median - yyQ1Median); 483 } 484 g2.fill(body); 485 g2.draw(body); 486 487 g2.setPaint(this.artifactPaint); 489 g2.draw(new Line2D.Double (xx - width / 2, yyMedian, xx + width / 2, yyMedian)); 490 491 double aRadius = 0; double oRadius = width / 3; 494 if (yAverage != null) { 496 aRadius = width / 4; 497 Ellipse2D.Double avgEllipse = new Ellipse2D.Double (xx - aRadius, yyAverage - aRadius, 498 aRadius * 2, aRadius * 2); 499 g2.fill(avgEllipse); 500 g2.draw(avgEllipse); 501 } 502 503 List outliers = new ArrayList (); 504 OutlierListCollection outlierListCollection = new OutlierListCollection(); 505 506 507 510 511 for (int i = 0; i < yOutliers.size(); i++) { 512 double outlier = ((Number ) yOutliers.get(i)).doubleValue(); 513 if (outlier > boxAndWhiskerData.getMaxOutlier(series, item).doubleValue()) { 514 outlierListCollection.setHighFarOut(true); 515 } 516 else if (outlier < boxAndWhiskerData.getMinOutlier(series, item).doubleValue()) { 517 outlierListCollection.setLowFarOut(true); 518 } 519 else if (outlier > boxAndWhiskerData.getMaxRegularValue(series, item).doubleValue()) { 520 yyOutlier = rangeAxis.translateValueToJava2D(outlier, dataArea, location); 521 outliers.add(new Outlier(xx, yyOutlier, oRadius)); 522 } 523 else if (outlier < boxAndWhiskerData.getMinRegularValue(series, item).doubleValue()) { 524 yyOutlier = rangeAxis.translateValueToJava2D(outlier, dataArea, location); 525 outliers.add(new Outlier(xx, yyOutlier, oRadius)); 526 } 527 Collections.sort(outliers); 528 } 529 530 for (Iterator iterator = outliers.iterator(); iterator.hasNext();) { 533 Outlier outlier = (Outlier) iterator.next(); 534 outlierListCollection.add(outlier); 535 } 536 537 double maxAxisValue = rangeAxis.translateValueToJava2D(rangeAxis.getUpperBound(), 539 dataArea, location) + aRadius; 540 double minAxisValue = rangeAxis.translateValueToJava2D(rangeAxis.getLowerBound(), 541 dataArea, location) - aRadius; 542 543 545 for (Iterator iterator = outlierListCollection.iterator(); iterator.hasNext();) { 547 OutlierList list = (OutlierList) iterator.next(); 548 Outlier outlier = list.getAveragedOutlier(); 549 Point2D point = outlier.getPoint(); 550 551 if (list.isMultiple()) { 552 drawMultipleEllipse(point, width, oRadius, g2); 553 } 554 else { 555 drawEllipse(point, oRadius, g2); 556 } 557 } 558 559 if (outlierListCollection.isHighFarOut()) { 561 drawHighFarOut(aRadius, g2, xx, maxAxisValue); 562 } 563 564 if (outlierListCollection.isLowFarOut()) { 565 drawLowFarOut(aRadius, g2, xx, minAxisValue); 566 } 567 568 if (entities != null) { 570 String tip = null; 571 if (getToolTipGenerator() != null) { 572 tip = getToolTipGenerator().generateToolTip(dataset, series, item); 573 } 574 String url = null; 575 if (getURLGenerator() != null) { 576 url = getURLGenerator().generateURL(dataset, series, item); 577 } 578 XYItemEntity entity = new XYItemEntity(body, dataset, series, item, tip, url); 579 entities.addEntity(entity); 580 } 581 582 } 583 584 591 private void drawEllipse(Point2D point, double oRadius, Graphics2D g2) { 592 Ellipse2D.Double dot = new Ellipse2D.Double (point.getX() + oRadius / 2, point.getY(), 593 oRadius, oRadius); 594 g2.draw(dot); 595 } 596 597 605 private void drawMultipleEllipse(Point2D point, double boxWidth, double oRadius, 606 Graphics2D g2) { 607 608 Ellipse2D.Double dot1 = new Ellipse2D.Double (point.getX() - (boxWidth / 2) + oRadius, 609 point.getY(), oRadius, oRadius); 610 Ellipse2D.Double dot2 = new Ellipse2D.Double (point.getX() + (boxWidth / 2), 611 point.getY(), oRadius, oRadius); 612 g2.draw(dot1); 613 g2.draw(dot2); 614 615 } 616 617 625 private void drawHighFarOut(double aRadius, Graphics2D g2, double xx, double m) { 626 double side = aRadius * 2; 627 g2.draw(new Line2D.Double (xx - side, m + side, xx + side, m + side)); 628 g2.draw(new Line2D.Double (xx - side, m + side, xx, m)); 629 g2.draw(new Line2D.Double (xx + side, m + side, xx, m)); 630 } 631 632 640 private void drawLowFarOut(double aRadius, Graphics2D g2, double xx, double m) { 641 double side = aRadius * 2; 642 g2.draw(new Line2D.Double (xx - side, m - side, xx + side, m - side)); 643 g2.draw(new Line2D.Double (xx - side, m - side, xx, m)); 644 g2.draw(new Line2D.Double (xx + side, m - side, xx, m)); 645 } 646 647 654 public boolean equals(Object obj) { 655 656 if (obj == null) { 657 return false; 658 } 659 660 if (obj == this) { 661 return true; 662 } 663 664 if (obj instanceof XYBoxAndWhiskerRenderer) { 665 XYBoxAndWhiskerRenderer renderer = (XYBoxAndWhiskerRenderer) obj; 666 boolean result = super.equals(obj); 667 result = result && (this.boxWidth == renderer.getBoxWidth()); 668 result = result && (this.paint.equals(renderer.getPaint())); 669 return result; 670 } 671 672 return false; 673 674 } 675 676 683 private void writeObject(ObjectOutputStream stream) throws IOException { 684 685 stream.defaultWriteObject(); 686 SerialUtilities.writePaint(this.paint, stream); 687 688 } 689 690 698 private void readObject(ObjectInputStream stream) throws IOException , ClassNotFoundException { 699 700 stream.defaultReadObject(); 701 this.paint = SerialUtilities.readPaint(stream); 702 703 } 704 705 712 public Object clone() throws CloneNotSupportedException { 713 return super.clone(); 714 } 715 716 } 717 | Popular Tags |