1 45 46 package org.jfree.chart.renderer; 47 48 import java.awt.Color ; 49 import java.awt.Graphics2D ; 50 import java.awt.Paint ; 51 import java.awt.Shape ; 52 import java.awt.Stroke ; 53 import java.awt.geom.Ellipse2D ; 54 import java.awt.geom.Line2D ; 55 import java.awt.geom.Point2D ; 56 import java.awt.geom.Rectangle2D ; 57 import java.util.ArrayList ; 58 import java.util.Collections ; 59 import java.util.Iterator ; 60 import java.util.List ; 61 62 import org.jfree.chart.axis.CategoryAxis; 63 import org.jfree.chart.axis.ValueAxis; 64 import org.jfree.chart.plot.CategoryPlot; 65 import org.jfree.chart.plot.PlotOrientation; 66 import org.jfree.chart.plot.PlotRenderingInfo; 67 import org.jfree.data.CategoryDataset; 68 import org.jfree.data.statistics.BoxAndWhiskerCategoryDataset; 69 import org.jfree.ui.RectangleEdge; 70 71 74 public class BoxAndWhiskerRenderer extends AbstractCategoryItemRenderer { 75 76 77 private Paint artifactPaint; 78 79 80 private double boxWidth; 81 82 83 private double itemMargin; 84 85 88 public BoxAndWhiskerRenderer () { 89 this.artifactPaint = Color.black; 90 this.boxWidth = 0.0; 91 this.itemMargin = 0.20; 92 } 93 94 99 public Paint getArtifactPaint() { 100 return artifactPaint; 101 } 102 103 108 public void setArtifactPaint(Paint paint) { 109 this.artifactPaint = paint; 110 } 111 112 117 public double getBoxWidth() { 118 return boxWidth; 119 } 120 121 126 public void setBoxWidth(double width) { 127 this.boxWidth = width; 128 } 129 130 136 public double getItemMargin() { 137 return itemMargin; 138 } 139 140 145 public void setItemMargin(double margin) { 146 this.itemMargin = margin; 147 } 148 149 162 public CategoryItemRendererState initialise(Graphics2D g2, 163 Rectangle2D dataArea, 164 CategoryPlot plot, 165 Integer index, 166 PlotRenderingInfo info) { 167 168 CategoryItemRendererState state = super.initialise(g2, dataArea, plot, index, info); 169 170 CategoryAxis domainAxis = getDomainAxis(plot, index); 172 173 CategoryDataset dataset = getDataset(plot, index); 174 if (dataset != null) { 175 int columns = dataset.getColumnCount(); 176 int rows = dataset.getRowCount(); 177 double space = 0.0; 178 PlotOrientation orientation = plot.getOrientation(); 179 if (orientation == PlotOrientation.HORIZONTAL) { 180 space = dataArea.getHeight(); 181 } 182 else if (orientation == PlotOrientation.VERTICAL) { 183 space = dataArea.getWidth(); 184 } 185 double categoryMargin = 0.0; 186 double currentItemMargin = 0.0; 187 if (columns > 1) { 188 categoryMargin = domainAxis.getCategoryMargin(); 189 } 190 if (rows > 1) { 191 currentItemMargin = getItemMargin(); 192 } 193 double used = space * (1 - domainAxis.getLowerMargin() - domainAxis.getUpperMargin() 194 - categoryMargin - currentItemMargin); 195 if ((rows * columns) > 0) { 196 setBoxWidth(used / (dataset.getColumnCount() * dataset.getRowCount())); 197 } 198 else { 199 setBoxWidth(used); 200 } 201 } 202 203 return state; 204 205 } 206 207 220 public void drawItem(Graphics2D g2, 221 CategoryItemRendererState state, 222 Rectangle2D dataArea, 223 CategoryPlot plot, 224 CategoryAxis domainAxis, 225 ValueAxis rangeAxis, 226 CategoryDataset dataset, 227 int row, 228 int column) { 229 230 if (!(dataset instanceof BoxAndWhiskerCategoryDataset)) { 231 throw new IllegalArgumentException ("BoxAndWhiskerRenderer.drawItem()" 232 + " : the data should be of type BoxAndWhiskerCategoryDataset only."); 233 } 234 235 PlotOrientation orientation = plot.getOrientation(); 236 237 if (orientation == PlotOrientation.HORIZONTAL) { 238 drawHorizontalItem(g2, dataArea, plot, domainAxis, rangeAxis, dataset, row, column); 239 } 240 else if (orientation == PlotOrientation.VERTICAL) { 241 drawVerticalItem(g2, dataArea, plot, domainAxis, rangeAxis, dataset, row, column); 242 } 243 244 } 245 246 259 public void drawHorizontalItem(Graphics2D g2, 260 Rectangle2D dataArea, 261 CategoryPlot plot, 262 CategoryAxis domainAxis, 263 ValueAxis rangeAxis, 264 CategoryDataset dataset, 265 int row, 266 int column) { 267 268 BoxAndWhiskerCategoryDataset bawDataset = (BoxAndWhiskerCategoryDataset) dataset; 269 270 double categoryEnd = domainAxis.getCategoryEnd( 271 column, getColumnCount(), dataArea, plot.getDomainAxisEdge()); 272 double categoryStart = domainAxis.getCategoryStart( 273 column, getColumnCount(), dataArea, plot.getDomainAxisEdge()); 274 double categoryWidth = Math.abs(categoryEnd - categoryStart); 275 276 double yy = categoryStart; 277 int seriesCount = getRowCount(); 278 int categoryCount = getColumnCount(); 279 280 if (seriesCount > 1) { 281 double seriesGap = dataArea.getWidth() * getItemMargin() 282 / (categoryCount * (seriesCount - 1)); 283 double usedWidth = (getBoxWidth() * seriesCount) + (seriesGap * (seriesCount - 1)); 284 double offset = (categoryWidth - usedWidth) / 2; 286 yy = yy + offset + (row * (getBoxWidth() + seriesGap)); 287 } 288 else { 289 double offset = (categoryWidth - boxWidth) / 2; 291 yy = yy + offset; 292 } 293 294 Paint p = this.getItemPaint(row, column); 295 if (p != null) { 296 g2.setPaint(p); 297 } 298 Stroke s = getItemStroke(row, column); 299 g2.setStroke(s); 300 301 RectangleEdge location = plot.getRangeAxisEdge(); 302 303 Number xQ1 = bawDataset.getQ1Value(row, column); 304 Number xQ3 = bawDataset.getQ3Value(row, column); 305 Number xMax = bawDataset.getMaxRegularValue(row, column); 306 Number xMin = bawDataset.getMinRegularValue(row, column); 307 308 if (xQ1 != null && xQ3 != null && xMax != null && xMin != null) { 309 310 double xxQ1 = rangeAxis.translateValueToJava2D(xQ1.doubleValue(), dataArea, location); 311 double xxQ3 = rangeAxis.translateValueToJava2D(xQ3.doubleValue(), dataArea, location); 312 double xxMax = rangeAxis.translateValueToJava2D(xMax.doubleValue(), dataArea, location); 313 double xxMin = rangeAxis.translateValueToJava2D(xMin.doubleValue(), dataArea, location); 314 if ((xxMax > xxQ1) && (xxMax > xxQ3)) { g2.draw(new Line2D.Double (xxMax, yy + this.boxWidth / 2, 317 Math.max(xxQ1, xxQ3), yy + this.boxWidth / 2)); 318 g2.draw(new Line2D.Double (xxMax, yy, 319 xxMax, yy + this.boxWidth)); 320 } 321 322 if ((xxMin < xxQ1) && (xxMin < xxQ3)) { 324 g2.draw(new Line2D.Double (xxMin, yy + this.boxWidth / 2, 325 Math.min(xxQ1, xxQ3), yy + this.boxWidth / 2)); 326 g2.draw(new Line2D.Double (xxMin, yy, 327 xxMin, yy + this.boxWidth)); 328 } 329 330 Shape body = new Rectangle2D.Double (Math.min(xxQ1, xxQ3), yy, 332 Math.abs(xxQ1 - xxQ3), this.boxWidth); 333 g2.fill(body); 334 g2.draw(body); 335 336 } 337 338 g2.setPaint(artifactPaint); 339 double aRadius = 0; 341 Number xMean = bawDataset.getMeanValue(row, column); 343 if (xMean != null) { 344 double xxMean = rangeAxis.translateValueToJava2D(xMean.doubleValue(), dataArea, location); 345 aRadius = this.boxWidth / 4; 346 Ellipse2D.Double avgEllipse = new Ellipse2D.Double (xxMean - aRadius, yy + aRadius, 347 aRadius * 2, aRadius * 2); 348 g2.fill(avgEllipse); 349 g2.draw(avgEllipse); 350 } 351 352 Number xMedian = bawDataset.getMedianValue(row, column); 354 if (xMedian != null) { 355 double xxMedian = rangeAxis.translateValueToJava2D( 356 xMedian.doubleValue(), 357 dataArea, 358 location); 359 g2.draw(new Line2D.Double (xxMedian, yy, xxMedian, yy + this.boxWidth)); 360 } 361 362 } 363 364 377 public void drawVerticalItem(Graphics2D g2, 378 Rectangle2D dataArea, 379 CategoryPlot plot, 380 CategoryAxis domainAxis, 381 ValueAxis rangeAxis, 382 CategoryDataset dataset, 383 int row, 384 int column) { 385 386 392 BoxAndWhiskerCategoryDataset bawDataset = (BoxAndWhiskerCategoryDataset) dataset; 393 394 double categoryEnd = domainAxis.getCategoryEnd( 395 column, getColumnCount(), dataArea, plot.getDomainAxisEdge()); 396 double categoryStart = domainAxis.getCategoryStart( 397 column, getColumnCount(), dataArea, plot.getDomainAxisEdge()); 398 double categoryWidth = categoryEnd - categoryStart; 399 400 double xx = categoryStart; 401 int seriesCount = getRowCount(); 402 int categoryCount = getColumnCount(); 403 404 if (seriesCount > 1) { 405 double seriesGap = dataArea.getWidth() * getItemMargin() 406 / (categoryCount * (seriesCount - 1)); 407 double usedWidth = (getBoxWidth() * seriesCount) + (seriesGap * (seriesCount - 1)); 408 double offset = (categoryWidth - usedWidth) / 2; 410 xx = xx + offset + (row * (getBoxWidth() + seriesGap)); 411 } 412 else { 413 double offset = (categoryWidth - boxWidth) / 2; 415 xx = xx + offset; 416 } 417 418 double yyAverage = 0.0; 419 double yyOutlier; 420 421 Paint p = this.getItemPaint(row, column); 422 if (p != null) { 423 g2.setPaint(p); 424 } 425 Stroke s = getItemStroke(row, column); 426 g2.setStroke(s); 427 428 double aRadius = 0; 430 RectangleEdge location = plot.getRangeAxisEdge(); 431 432 Number yQ1 = bawDataset.getQ1Value(row, column); 433 Number yQ3 = bawDataset.getQ3Value(row, column); 434 Number yMax = bawDataset.getMaxRegularValue(row, column); 435 Number yMin = bawDataset.getMinRegularValue(row, column); 436 if (yQ1 != null && yQ3 != null && yMax != null && yMin != null) { 437 438 double yyQ1 = rangeAxis.translateValueToJava2D(yQ1.doubleValue(), dataArea, location); 440 double yyQ3 = rangeAxis.translateValueToJava2D(yQ3.doubleValue(), dataArea, location); 441 double yyMax = rangeAxis.translateValueToJava2D(yMax.doubleValue(), dataArea, location); 442 double yyMin = rangeAxis.translateValueToJava2D(yMin.doubleValue(), dataArea, location); 443 if ((yyMax < yyQ1) && (yyMax < yyQ3)) { g2.draw(new Line2D.Double (xx + this.boxWidth / 2, yyMax, 445 xx + this.boxWidth / 2, Math.max(yyQ1, yyQ3))); 446 g2.draw(new Line2D.Double (xx, yyMax, 447 xx + this.boxWidth, yyMax)); 448 } 449 450 if ((yyMin > yyQ1) && (yyMin > yyQ3)) { 452 g2.draw(new Line2D.Double (xx + this.boxWidth / 2, yyMin, 453 xx + this.boxWidth / 2, Math.min(yyQ1, yyQ3))); 454 g2.draw(new Line2D.Double (xx, yyMin, 455 xx + this.boxWidth, yyMin)); 456 } 457 458 Shape body = new Rectangle2D.Double (xx, Math.min(yyQ1, yyQ3), 460 this.boxWidth, Math.abs(yyQ1 - yyQ3)); 461 g2.fill(body); 462 g2.draw(body); 463 464 } 465 466 g2.setPaint(artifactPaint); 467 468 Number yMean = bawDataset.getMeanValue(row, column); 470 if (yMean != null) { 471 yyAverage = rangeAxis.translateValueToJava2D(yMean.doubleValue(), dataArea, location); 472 aRadius = this.boxWidth / 4; 473 Ellipse2D.Double avgEllipse = new Ellipse2D.Double (xx + aRadius, yyAverage - aRadius, 474 aRadius * 2, aRadius * 2); 475 g2.fill(avgEllipse); 476 g2.draw(avgEllipse); 477 } 478 479 Number yMedian = bawDataset.getMedianValue(row, column); 481 if (yMedian != null) { 482 double yyMedian = rangeAxis.translateValueToJava2D(yMedian.doubleValue(), 483 dataArea, location); 484 g2.draw(new Line2D.Double (xx, yyMedian, xx + this.boxWidth, yyMedian)); 485 } 486 487 double maxAxisValue = rangeAxis.translateValueToJava2D(rangeAxis.getUpperBound(), 489 dataArea, location) + aRadius; 490 double minAxisValue = rangeAxis.translateValueToJava2D(rangeAxis.getLowerBound(), 491 dataArea, location) - aRadius; 492 493 g2.setPaint(p); 494 495 double oRadius = this.boxWidth / 3; List outliers = new ArrayList (); 498 OutlierListCollection outlierListCollection = new OutlierListCollection(); 499 500 List yOutliers = bawDataset.getOutliers(row, column); 503 for (int i = 0; i < yOutliers.size(); i++) { 504 double outlier = ((Number ) yOutliers.get(i)).doubleValue(); 505 if (outlier > bawDataset.getMaxOutlier(row, column).doubleValue()) { 506 outlierListCollection.setHighFarOut(true); 507 } 508 else if (outlier < bawDataset.getMinOutlier(row, column).doubleValue()) { 509 outlierListCollection.setLowFarOut(true); 510 } 511 else if (outlier > bawDataset.getMaxRegularValue(row, column).doubleValue()) { 512 yyOutlier = rangeAxis.translateValueToJava2D(outlier, dataArea, location); 513 outliers.add(new Outlier(xx + this.boxWidth / 2.0, yyOutlier, oRadius)); 514 } 515 else if (outlier < bawDataset.getMinRegularValue(row, column).doubleValue()) { 516 yyOutlier = rangeAxis.translateValueToJava2D(outlier, dataArea, location); 517 outliers.add(new Outlier(xx + this.boxWidth / 2.0, yyOutlier, oRadius)); 518 } 519 Collections.sort(outliers); 520 } 521 522 for (Iterator iterator = outliers.iterator(); iterator.hasNext();) { 525 Outlier outlier = (Outlier) iterator.next(); 526 outlierListCollection.add(outlier); 527 } 528 529 for (Iterator iterator = outlierListCollection.iterator(); iterator.hasNext();) { 530 OutlierList list = (OutlierList) iterator.next(); 531 Outlier outlier = list.getAveragedOutlier(); 532 Point2D point = outlier.getPoint(); 533 534 if (list.isMultiple()) { 535 drawMultipleEllipse(point, this.boxWidth, oRadius, g2); 536 } 537 else { 538 drawEllipse(point, oRadius, g2); 539 } 540 } 541 542 if (outlierListCollection.isHighFarOut()) { 544 drawHighFarOut(aRadius / 2.0, g2, xx + this.boxWidth / 2.0, maxAxisValue); 545 } 546 547 if (outlierListCollection.isLowFarOut()) { 548 drawLowFarOut(aRadius / 2.0, g2, xx + this.boxWidth / 2.0, minAxisValue); 549 } 550 551 565 } 566 567 574 private void drawEllipse(Point2D point, double oRadius, Graphics2D g2) { 575 Ellipse2D dot = new Ellipse2D.Double (point.getX() + oRadius / 2, point.getY(), 576 oRadius, oRadius); 577 g2.draw(dot); 578 } 579 580 588 private void drawMultipleEllipse(Point2D point, double boxWidth, double oRadius, 589 Graphics2D g2) { 590 591 Ellipse2D dot1 = new Ellipse2D.Double (point.getX() - (boxWidth / 2) + oRadius, point.getY(), 592 oRadius, oRadius); 593 Ellipse2D dot2 = new Ellipse2D.Double (point.getX() + (boxWidth / 2), point.getY(), 594 oRadius, oRadius); 595 g2.draw(dot1); 596 g2.draw(dot2); 597 } 598 599 607 private void drawHighFarOut(double aRadius, Graphics2D g2, double xx, double m) { 608 double side = aRadius * 2; 609 g2.draw(new Line2D.Double (xx - side, m + side, xx + side, m + side)); 610 g2.draw(new Line2D.Double (xx - side, m + side, xx, m)); 611 g2.draw(new Line2D.Double (xx + side, m + side, xx, m)); 612 } 613 614 622 private void drawLowFarOut(double aRadius, Graphics2D g2, double xx, double m) { 623 double side = aRadius * 2; 624 g2.draw(new Line2D.Double (xx - side, m - side, xx + side, m - side)); 625 g2.draw(new Line2D.Double (xx - side, m - side, xx, m)); 626 g2.draw(new Line2D.Double (xx + side, m - side, xx, m)); 627 } 628 } 629 | Popular Tags |