1 65 66 package org.jfree.chart.renderer.category; 67 68 import java.awt.Color ; 69 import java.awt.Graphics2D ; 70 import java.awt.Paint ; 71 import java.awt.Shape ; 72 import java.awt.Stroke ; 73 import java.awt.geom.Ellipse2D ; 74 import java.awt.geom.Line2D ; 75 import java.awt.geom.Point2D ; 76 import java.awt.geom.Rectangle2D ; 77 import java.io.IOException ; 78 import java.io.ObjectInputStream ; 79 import java.io.ObjectOutputStream ; 80 import java.io.Serializable ; 81 import java.util.ArrayList ; 82 import java.util.Collections ; 83 import java.util.Iterator ; 84 import java.util.List ; 85 86 import org.jfree.chart.LegendItem; 87 import org.jfree.chart.axis.CategoryAxis; 88 import org.jfree.chart.axis.ValueAxis; 89 import org.jfree.chart.entity.CategoryItemEntity; 90 import org.jfree.chart.entity.EntityCollection; 91 import org.jfree.chart.event.RendererChangeEvent; 92 import org.jfree.chart.labels.CategoryToolTipGenerator; 93 import org.jfree.chart.plot.CategoryPlot; 94 import org.jfree.chart.plot.PlotOrientation; 95 import org.jfree.chart.plot.PlotRenderingInfo; 96 import org.jfree.chart.renderer.Outlier; 97 import org.jfree.chart.renderer.OutlierList; 98 import org.jfree.chart.renderer.OutlierListCollection; 99 import org.jfree.data.category.CategoryDataset; 100 import org.jfree.data.statistics.BoxAndWhiskerCategoryDataset; 101 import org.jfree.io.SerialUtilities; 102 import org.jfree.ui.RectangleEdge; 103 import org.jfree.util.PaintUtilities; 104 import org.jfree.util.PublicCloneable; 105 106 111 public class BoxAndWhiskerRenderer extends AbstractCategoryItemRenderer 112 implements Cloneable , PublicCloneable, 113 Serializable { 114 115 116 private static final long serialVersionUID = 632027470694481177L; 117 118 119 private transient Paint artifactPaint; 120 121 122 private boolean fillBox; 123 124 125 private double itemMargin; 126 127 130 public BoxAndWhiskerRenderer() { 131 this.artifactPaint = Color.black; 132 this.fillBox = true; 133 this.itemMargin = 0.20; 134 } 135 136 143 public Paint getArtifactPaint() { 144 return this.artifactPaint; 145 } 146 147 154 public void setArtifactPaint(Paint paint) { 155 this.artifactPaint = paint; 156 } 157 158 165 public boolean getFillBox() { 166 return this.fillBox; 167 } 168 169 177 public void setFillBox(boolean flag) { 178 this.fillBox = flag; 179 notifyListeners(new RendererChangeEvent(this)); 180 } 181 182 190 public double getItemMargin() { 191 return this.itemMargin; 192 } 193 194 201 public void setItemMargin(double margin) { 202 this.itemMargin = margin; 203 } 204 205 213 public LegendItem getLegendItem(int datasetIndex, int series) { 214 215 CategoryPlot cp = getPlot(); 216 if (cp == null) { 217 return null; 218 } 219 220 CategoryDataset dataset; 221 dataset = cp.getDataset(datasetIndex); 222 String label = getLegendItemLabelGenerator().generateLabel(dataset, 223 series); 224 String description = label; 225 String toolTipText = null; 226 if (getLegendItemToolTipGenerator() != null) { 227 toolTipText = getLegendItemToolTipGenerator().generateLabel( 228 dataset, series); 229 } 230 String urlText = null; 231 if (getLegendItemURLGenerator() != null) { 232 urlText = getLegendItemURLGenerator().generateLabel(dataset, 233 series); 234 } 235 Shape shape = new Rectangle2D.Double (-4.0, -4.0, 8.0, 8.0); 236 Paint paint = getSeriesPaint(series); 237 Paint outlinePaint = getSeriesOutlinePaint(series); 238 Stroke outlineStroke = getSeriesOutlineStroke(series); 239 240 return new LegendItem(label, description, toolTipText, urlText, 241 shape, paint, outlineStroke, outlinePaint); 242 243 } 244 245 257 public CategoryItemRendererState initialise(Graphics2D g2, 258 Rectangle2D dataArea, 259 CategoryPlot plot, 260 int rendererIndex, 261 PlotRenderingInfo info) { 262 263 CategoryItemRendererState state = super.initialise(g2, dataArea, plot, 264 rendererIndex, info); 265 266 CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex); 268 CategoryDataset dataset = plot.getDataset(rendererIndex); 269 if (dataset != null) { 270 int columns = dataset.getColumnCount(); 271 int rows = dataset.getRowCount(); 272 double space = 0.0; 273 PlotOrientation orientation = plot.getOrientation(); 274 if (orientation == PlotOrientation.HORIZONTAL) { 275 space = dataArea.getHeight(); 276 } 277 else if (orientation == PlotOrientation.VERTICAL) { 278 space = dataArea.getWidth(); 279 } 280 double categoryMargin = 0.0; 281 double currentItemMargin = 0.0; 282 if (columns > 1) { 283 categoryMargin = domainAxis.getCategoryMargin(); 284 } 285 if (rows > 1) { 286 currentItemMargin = getItemMargin(); 287 } 288 double used = space * (1 - domainAxis.getLowerMargin() 289 - domainAxis.getUpperMargin() 290 - categoryMargin - currentItemMargin); 291 if ((rows * columns) > 0) { 292 state.setBarWidth(used / (dataset.getColumnCount() 293 * dataset.getRowCount())); 294 } 295 else { 296 state.setBarWidth(used); 297 } 298 } 299 300 return state; 301 302 } 303 304 318 public void drawItem(Graphics2D g2, 319 CategoryItemRendererState state, 320 Rectangle2D dataArea, 321 CategoryPlot plot, 322 CategoryAxis domainAxis, 323 ValueAxis rangeAxis, 324 CategoryDataset dataset, 325 int row, 326 int column, 327 int pass) { 328 329 if (!(dataset instanceof BoxAndWhiskerCategoryDataset)) { 330 throw new IllegalArgumentException ( 331 "BoxAndWhiskerRenderer.drawItem() : the data should be " 332 + "of type BoxAndWhiskerCategoryDataset only." 333 ); 334 } 335 336 PlotOrientation orientation = plot.getOrientation(); 337 338 if (orientation == PlotOrientation.HORIZONTAL) { 339 drawHorizontalItem(g2, state, dataArea, plot, domainAxis, 340 rangeAxis, dataset, row, column); 341 } 342 else if (orientation == PlotOrientation.VERTICAL) { 343 drawVerticalItem(g2, state, dataArea, plot, domainAxis, 344 rangeAxis, dataset, row, column); 345 } 346 347 } 348 349 364 public void drawHorizontalItem(Graphics2D g2, 365 CategoryItemRendererState state, 366 Rectangle2D dataArea, 367 CategoryPlot plot, 368 CategoryAxis domainAxis, 369 ValueAxis rangeAxis, 370 CategoryDataset dataset, 371 int row, 372 int column) { 373 374 BoxAndWhiskerCategoryDataset bawDataset 375 = (BoxAndWhiskerCategoryDataset) dataset; 376 377 double categoryEnd = domainAxis.getCategoryEnd(column, 378 getColumnCount(), dataArea, plot.getDomainAxisEdge()); 379 double categoryStart = domainAxis.getCategoryStart(column, 380 getColumnCount(), dataArea, plot.getDomainAxisEdge()); 381 double categoryWidth = Math.abs(categoryEnd - categoryStart); 382 383 double yy = categoryStart; 384 int seriesCount = getRowCount(); 385 int categoryCount = getColumnCount(); 386 387 if (seriesCount > 1) { 388 double seriesGap = dataArea.getWidth() * getItemMargin() 389 / (categoryCount * (seriesCount - 1)); 390 double usedWidth = (state.getBarWidth() * seriesCount) 391 + (seriesGap * (seriesCount - 1)); 392 double offset = (categoryWidth - usedWidth) / 2; 395 yy = yy + offset + (row * (state.getBarWidth() + seriesGap)); 396 } 397 else { 398 double offset = (categoryWidth - state.getBarWidth()) / 2; 401 yy = yy + offset; 402 } 403 404 Paint p = getItemPaint(row, column); 405 if (p != null) { 406 g2.setPaint(p); 407 } 408 Stroke s = getItemStroke(row, column); 409 g2.setStroke(s); 410 411 RectangleEdge location = plot.getRangeAxisEdge(); 412 413 Number xQ1 = bawDataset.getQ1Value(row, column); 414 Number xQ3 = bawDataset.getQ3Value(row, column); 415 Number xMax = bawDataset.getMaxRegularValue(row, column); 416 Number xMin = bawDataset.getMinRegularValue(row, column); 417 418 Shape box = null; 419 if (xQ1 != null && xQ3 != null && xMax != null && xMin != null) { 420 421 double xxQ1 = rangeAxis.valueToJava2D(xQ1.doubleValue(), dataArea, 422 location); 423 double xxQ3 = rangeAxis.valueToJava2D(xQ3.doubleValue(), dataArea, 424 location); 425 double xxMax = rangeAxis.valueToJava2D(xMax.doubleValue(), dataArea, 426 location); 427 double xxMin = rangeAxis.valueToJava2D(xMin.doubleValue(), dataArea, 428 location); 429 double yymid = yy + state.getBarWidth() / 2.0; 430 431 g2.draw(new Line2D.Double (xxMax, yymid, xxQ3, yymid)); 433 g2.draw(new Line2D.Double (xxMax, yy, xxMax, 434 yy + state.getBarWidth())); 435 436 g2.draw(new Line2D.Double (xxMin, yymid, xxQ1, yymid)); 438 g2.draw(new Line2D.Double (xxMin, yy, xxMin, 439 yy + state.getBarWidth())); 440 441 box = new Rectangle2D.Double (Math.min(xxQ1, xxQ3), yy, 443 Math.abs(xxQ1 - xxQ3), state.getBarWidth()); 444 if (this.fillBox) { 445 g2.fill(box); 446 } 447 g2.draw(box); 448 449 } 450 451 g2.setPaint(this.artifactPaint); 452 double aRadius = 0; 454 Number xMean = bawDataset.getMeanValue(row, column); 456 if (xMean != null) { 457 double xxMean = rangeAxis.valueToJava2D(xMean.doubleValue(), 458 dataArea, location); 459 aRadius = state.getBarWidth() / 4; 460 Ellipse2D.Double avgEllipse = new Ellipse2D.Double (xxMean 461 - aRadius, yy + aRadius, aRadius * 2, aRadius * 2); 462 g2.fill(avgEllipse); 463 g2.draw(avgEllipse); 464 } 465 466 Number xMedian = bawDataset.getMedianValue(row, column); 468 if (xMedian != null) { 469 double xxMedian = rangeAxis.valueToJava2D(xMedian.doubleValue(), 470 dataArea, location); 471 g2.draw(new Line2D.Double (xxMedian, yy, xxMedian, 472 yy + state.getBarWidth())); 473 } 474 475 if (state.getInfo() != null && box != null) { 477 EntityCollection entities = state.getEntityCollection(); 478 if (entities != null) { 479 String tip = null; 480 CategoryToolTipGenerator tipster 481 = getToolTipGenerator(row, column); 482 if (tipster != null) { 483 tip = tipster.generateToolTip(dataset, row, column); 484 } 485 String url = null; 486 if (getItemURLGenerator(row, column) != null) { 487 url = getItemURLGenerator(row, column).generateURL( 488 dataset, row, column); 489 } 490 CategoryItemEntity entity = new CategoryItemEntity(box, tip, 491 url, dataset, row, dataset.getColumnKey(column), 492 column); 493 entities.add(entity); 494 } 495 } 496 497 } 498 499 514 public void drawVerticalItem(Graphics2D g2, 515 CategoryItemRendererState state, 516 Rectangle2D dataArea, 517 CategoryPlot plot, 518 CategoryAxis domainAxis, 519 ValueAxis rangeAxis, 520 CategoryDataset dataset, 521 int row, 522 int column) { 523 524 BoxAndWhiskerCategoryDataset bawDataset 525 = (BoxAndWhiskerCategoryDataset) dataset; 526 527 double categoryEnd = domainAxis.getCategoryEnd(column, 528 getColumnCount(), dataArea, plot.getDomainAxisEdge()); 529 double categoryStart = domainAxis.getCategoryStart(column, 530 getColumnCount(), dataArea, plot.getDomainAxisEdge()); 531 double categoryWidth = categoryEnd - categoryStart; 532 533 double xx = categoryStart; 534 int seriesCount = getRowCount(); 535 int categoryCount = getColumnCount(); 536 537 if (seriesCount > 1) { 538 double seriesGap = dataArea.getWidth() * getItemMargin() 539 / (categoryCount * (seriesCount - 1)); 540 double usedWidth = (state.getBarWidth() * seriesCount) 541 + (seriesGap * (seriesCount - 1)); 542 double offset = (categoryWidth - usedWidth) / 2; 545 xx = xx + offset + (row * (state.getBarWidth() + seriesGap)); 546 } 547 else { 548 double offset = (categoryWidth - state.getBarWidth()) / 2; 551 xx = xx + offset; 552 } 553 554 double yyAverage = 0.0; 555 double yyOutlier; 556 557 Paint p = getItemPaint(row, column); 558 if (p != null) { 559 g2.setPaint(p); 560 } 561 Stroke s = getItemStroke(row, column); 562 g2.setStroke(s); 563 564 double aRadius = 0; 566 RectangleEdge location = plot.getRangeAxisEdge(); 567 568 Number yQ1 = bawDataset.getQ1Value(row, column); 569 Number yQ3 = bawDataset.getQ3Value(row, column); 570 Number yMax = bawDataset.getMaxRegularValue(row, column); 571 Number yMin = bawDataset.getMinRegularValue(row, column); 572 Shape box = null; 573 if (yQ1 != null && yQ3 != null && yMax != null && yMin != null) { 574 575 double yyQ1 = rangeAxis.valueToJava2D(yQ1.doubleValue(), dataArea, 576 location); 577 double yyQ3 = rangeAxis.valueToJava2D(yQ3.doubleValue(), dataArea, 578 location); 579 double yyMax = rangeAxis.valueToJava2D(yMax.doubleValue(), 580 dataArea, location); 581 double yyMin = rangeAxis.valueToJava2D(yMin.doubleValue(), 582 dataArea, location); 583 double xxmid = xx + state.getBarWidth() / 2.0; 584 585 g2.draw(new Line2D.Double (xxmid, yyMax, xxmid, yyQ3)); 587 g2.draw(new Line2D.Double (xx, yyMax, xx + state.getBarWidth(), 588 yyMax)); 589 590 g2.draw(new Line2D.Double (xxmid, yyMin, xxmid, yyQ1)); 592 g2.draw(new Line2D.Double (xx, yyMin, xx + state.getBarWidth(), 593 yyMin)); 594 595 box = new Rectangle2D.Double (xx, Math.min(yyQ1, yyQ3), 597 state.getBarWidth(), Math.abs(yyQ1 - yyQ3)); 598 if (this.fillBox) { 599 g2.fill(box); 600 } 601 g2.draw(box); 602 603 } 604 605 g2.setPaint(this.artifactPaint); 606 607 Number yMean = bawDataset.getMeanValue(row, column); 609 if (yMean != null) { 610 yyAverage = rangeAxis.valueToJava2D(yMean.doubleValue(), 611 dataArea, location); 612 aRadius = state.getBarWidth() / 4; 613 Ellipse2D.Double avgEllipse = new Ellipse2D.Double (xx + aRadius, 614 yyAverage - aRadius, aRadius * 2, aRadius * 2); 615 g2.fill(avgEllipse); 616 g2.draw(avgEllipse); 617 } 618 619 Number yMedian = bawDataset.getMedianValue(row, column); 621 if (yMedian != null) { 622 double yyMedian = rangeAxis.valueToJava2D(yMedian.doubleValue(), 623 dataArea, location); 624 g2.draw(new Line2D.Double (xx, yyMedian, xx + state.getBarWidth(), 625 yyMedian)); 626 } 627 628 double maxAxisValue = rangeAxis.valueToJava2D( 630 rangeAxis.getUpperBound(), dataArea, location) + aRadius; 631 double minAxisValue = rangeAxis.valueToJava2D( 632 rangeAxis.getLowerBound(), dataArea, location) - aRadius; 633 634 g2.setPaint(p); 635 636 double oRadius = state.getBarWidth() / 3; List outliers = new ArrayList (); 639 OutlierListCollection outlierListCollection 640 = new OutlierListCollection(); 641 642 List yOutliers = bawDataset.getOutliers(row, column); 646 if (yOutliers != null) { 647 for (int i = 0; i < yOutliers.size(); i++) { 648 double outlier = ((Number ) yOutliers.get(i)).doubleValue(); 649 Number minOutlier = bawDataset.getMinOutlier(row, column); 650 Number maxOutlier = bawDataset.getMaxOutlier(row, column); 651 Number minRegular = bawDataset.getMinRegularValue(row, column); 652 Number maxRegular = bawDataset.getMaxRegularValue(row, column); 653 if (outlier > maxOutlier.doubleValue()) { 654 outlierListCollection.setHighFarOut(true); 655 } 656 else if (outlier < minOutlier.doubleValue()) { 657 outlierListCollection.setLowFarOut(true); 658 } 659 else if (outlier > maxRegular.doubleValue()) { 660 yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea, 661 location); 662 outliers.add(new Outlier(xx + state.getBarWidth() / 2.0, 663 yyOutlier, oRadius)); 664 } 665 else if (outlier < minRegular.doubleValue()) { 666 yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea, 667 location); 668 outliers.add(new Outlier(xx + state.getBarWidth() / 2.0, 669 yyOutlier, oRadius)); 670 } 671 Collections.sort(outliers); 672 } 673 674 for (Iterator iterator = outliers.iterator(); iterator.hasNext();) { 677 Outlier outlier = (Outlier) iterator.next(); 678 outlierListCollection.add(outlier); 679 } 680 681 for (Iterator iterator = outlierListCollection.iterator(); 682 iterator.hasNext();) { 683 OutlierList list = (OutlierList) iterator.next(); 684 Outlier outlier = list.getAveragedOutlier(); 685 Point2D point = outlier.getPoint(); 686 687 if (list.isMultiple()) { 688 drawMultipleEllipse(point, state.getBarWidth(), oRadius, 689 g2); 690 } 691 else { 692 drawEllipse(point, oRadius, g2); 693 } 694 } 695 696 if (outlierListCollection.isHighFarOut()) { 698 drawHighFarOut(aRadius / 2.0, g2, 699 xx + state.getBarWidth() / 2.0, maxAxisValue); 700 } 701 702 if (outlierListCollection.isLowFarOut()) { 703 drawLowFarOut(aRadius / 2.0, g2, 704 xx + state.getBarWidth() / 2.0, minAxisValue); 705 } 706 } 707 if (state.getInfo() != null && box != null) { 709 EntityCollection entities = state.getEntityCollection(); 710 if (entities != null) { 711 String tip = null; 712 CategoryToolTipGenerator tipster 713 = getToolTipGenerator(row, column); 714 if (tipster != null) { 715 tip = tipster.generateToolTip(dataset, row, column); 716 } 717 String url = null; 718 if (getItemURLGenerator(row, column) != null) { 719 url = getItemURLGenerator(row, column).generateURL(dataset, 720 row, column); 721 } 722 CategoryItemEntity entity = new CategoryItemEntity(box, tip, 723 url, dataset, row, dataset.getColumnKey(column), 724 column); 725 entities.add(entity); 726 } 727 } 728 729 } 730 731 738 private void drawEllipse(Point2D point, double oRadius, Graphics2D g2) { 739 Ellipse2D dot = new Ellipse2D.Double (point.getX() + oRadius / 2, 740 point.getY(), oRadius, oRadius); 741 g2.draw(dot); 742 } 743 744 752 private void drawMultipleEllipse(Point2D point, double boxWidth, 753 double oRadius, Graphics2D g2) { 754 755 Ellipse2D dot1 = new Ellipse2D.Double (point.getX() - (boxWidth / 2) 756 + oRadius, point.getY(), oRadius, oRadius); 757 Ellipse2D dot2 = new Ellipse2D.Double (point.getX() + (boxWidth / 2), 758 point.getY(), oRadius, oRadius); 759 g2.draw(dot1); 760 g2.draw(dot2); 761 } 762 763 771 private void drawHighFarOut(double aRadius, Graphics2D g2, double xx, 772 double m) { 773 double side = aRadius * 2; 774 g2.draw(new Line2D.Double (xx - side, m + side, xx + side, m + side)); 775 g2.draw(new Line2D.Double (xx - side, m + side, xx, m)); 776 g2.draw(new Line2D.Double (xx + side, m + side, xx, m)); 777 } 778 779 787 private void drawLowFarOut(double aRadius, Graphics2D g2, double xx, 788 double m) { 789 double side = aRadius * 2; 790 g2.draw(new Line2D.Double (xx - side, m - side, xx + side, m - side)); 791 g2.draw(new Line2D.Double (xx - side, m - side, xx, m)); 792 g2.draw(new Line2D.Double (xx + side, m - side, xx, m)); 793 } 794 795 802 public boolean equals(Object obj) { 803 if (obj == this) { 804 return true; 805 } 806 if (!(obj instanceof BoxAndWhiskerRenderer)) { 807 return false; 808 } 809 if (!super.equals(obj)) { 810 return false; 811 } 812 BoxAndWhiskerRenderer that = (BoxAndWhiskerRenderer) obj; 813 if (!PaintUtilities.equal(this.artifactPaint, that.artifactPaint)) { 814 return false; 815 } 816 if (!(this.fillBox == that.fillBox)) { 817 return false; 818 } 819 if (!(this.itemMargin == that.itemMargin)) { 820 return false; 821 } 822 return true; 823 } 824 825 832 private void writeObject(ObjectOutputStream stream) throws IOException { 833 stream.defaultWriteObject(); 834 SerialUtilities.writePaint(this.artifactPaint, stream); 835 } 836 837 845 private void readObject(ObjectInputStream stream) 846 throws IOException , ClassNotFoundException { 847 stream.defaultReadObject(); 848 this.artifactPaint = SerialUtilities.readPaint(stream); 849 } 850 851 } 852 | Popular Tags |