001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * ---------------------------- 028 * XYBoxAndWhiskerRenderer.java 029 * ---------------------------- 030 * (C) Copyright 2003, 2004, 2007, by David Browning and Contributors. 031 * 032 * Original Author: David Browning (for Australian Institute of Marine 033 * Science); 034 * Contributor(s): David Gilbert (for Object Refinery Limited); 035 * 036 * Changes 037 * ------- 038 * 05-Aug-2003 : Version 1, contributed by David Browning. Based on code in the 039 * CandlestickRenderer class. Additional modifications by David 040 * Gilbert to make the code work with 0.9.10 changes (DG); 041 * 08-Aug-2003 : Updated some of the Javadoc 042 * Allowed BoxAndwhiskerDataset Average value to be null - the 043 * average value is an AIMS requirement 044 * Allow the outlier and farout coefficients to be set - though 045 * at the moment this only affects the calculation of farouts. 046 * Added artifactPaint variable and setter/getter 047 * 12-Aug-2003 Rewrote code to sort out and process outliers to take 048 * advantage of changes in DefaultBoxAndWhiskerDataset 049 * Added a limit of 10% for width of box should no width be 050 * specified...maybe this should be setable??? 051 * 20-Aug-2003 : Implemented Cloneable and PublicCloneable (DG); 052 * 08-Sep-2003 : Changed ValueAxis API (DG); 053 * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG); 054 * 25-Feb-2004 : Replaced CrosshairInfo with CrosshairState (DG); 055 * 23-Apr-2004 : Added fillBox attribute, extended equals() method and fixed 056 * serialization issue (DG); 057 * 29-Apr-2004 : Fixed problem with drawing upper and lower shadows - bug id 058 * 944011 (DG); 059 * 15-Jul-2004 : Switched getX() with getXValue() and getY() with 060 * getYValue() (DG); 061 * 01-Oct-2004 : Renamed 'paint' --> 'boxPaint' to avoid conflict with 062 * inherited attribute (DG); 063 * 10-Jun-2005 : Updated equals() to handle GradientPaint (DG); 064 * 06-Oct-2005 : Removed setPaint() call in drawItem(), it is causing a 065 * loop (DG); 066 * ------------- JFREECHART 1.0.x --------------------------------------------- 067 * 02-Feb-2007 : Removed author tags from all over JFreeChart sources (DG); 068 * 05-Feb-2007 : Added event notifications and fixed drawing for horizontal 069 * plot orientation (DG); 070 * 13-Jun-2007 : Replaced deprecated method call (DG); 071 * 072 */ 073 074package org.jfree.chart.renderer.xy; 075 076import java.awt.Color; 077import java.awt.Graphics2D; 078import java.awt.Paint; 079import java.awt.Shape; 080import java.awt.Stroke; 081import java.awt.geom.Ellipse2D; 082import java.awt.geom.Line2D; 083import java.awt.geom.Point2D; 084import java.awt.geom.Rectangle2D; 085import java.io.IOException; 086import java.io.ObjectInputStream; 087import java.io.ObjectOutputStream; 088import java.io.Serializable; 089import java.util.ArrayList; 090import java.util.Collections; 091import java.util.Iterator; 092import java.util.List; 093 094import org.jfree.chart.axis.ValueAxis; 095import org.jfree.chart.entity.EntityCollection; 096import org.jfree.chart.event.RendererChangeEvent; 097import org.jfree.chart.labels.BoxAndWhiskerXYToolTipGenerator; 098import org.jfree.chart.plot.CrosshairState; 099import org.jfree.chart.plot.PlotOrientation; 100import org.jfree.chart.plot.PlotRenderingInfo; 101import org.jfree.chart.plot.XYPlot; 102import org.jfree.chart.renderer.Outlier; 103import org.jfree.chart.renderer.OutlierList; 104import org.jfree.chart.renderer.OutlierListCollection; 105import org.jfree.data.statistics.BoxAndWhiskerXYDataset; 106import org.jfree.data.xy.XYDataset; 107import org.jfree.io.SerialUtilities; 108import org.jfree.ui.RectangleEdge; 109import org.jfree.util.PaintUtilities; 110import org.jfree.util.PublicCloneable; 111 112/** 113 * A renderer that draws box-and-whisker items on an {@link XYPlot}. This 114 * renderer requires a {@link BoxAndWhiskerXYDataset}). 115 * <P> 116 * This renderer does not include any code to calculate the crosshair point. 117 */ 118public class XYBoxAndWhiskerRenderer extends AbstractXYItemRenderer 119 implements XYItemRenderer, 120 Cloneable, 121 PublicCloneable, 122 Serializable { 123 124 /** For serialization. */ 125 private static final long serialVersionUID = -8020170108532232324L; 126 127 /** The box width. */ 128 private double boxWidth; 129 130 /** The paint used to fill the box. */ 131 private transient Paint boxPaint; 132 133 /** A flag that controls whether or not the box is filled. */ 134 private boolean fillBox; 135 136 /** 137 * The paint used to draw various artifacts such as outliers, farout 138 * symbol, average ellipse and median line. 139 */ 140 private transient Paint artifactPaint = Color.black; 141 142 /** 143 * Creates a new renderer for box and whisker charts. 144 */ 145 public XYBoxAndWhiskerRenderer() { 146 this(-1.0); 147 } 148 149 /** 150 * Creates a new renderer for box and whisker charts. 151 * <P> 152 * Use -1 for the box width if you prefer the width to be calculated 153 * automatically. 154 * 155 * @param boxWidth the box width. 156 */ 157 public XYBoxAndWhiskerRenderer(double boxWidth) { 158 super(); 159 this.boxWidth = boxWidth; 160 this.boxPaint = Color.green; 161 this.fillBox = true; 162 setBaseToolTipGenerator(new BoxAndWhiskerXYToolTipGenerator()); 163 } 164 165 /** 166 * Returns the width of each box. 167 * 168 * @return The box width. 169 * 170 * @see #setBoxWidth(double) 171 */ 172 public double getBoxWidth() { 173 return this.boxWidth; 174 } 175 176 /** 177 * Sets the box width and sends a {@link RendererChangeEvent} to all 178 * registered listeners. 179 * <P> 180 * If you set the width to a negative value, the renderer will calculate 181 * the box width automatically based on the space available on the chart. 182 * 183 * @param width the width. 184 * 185 * @see #getBoxWidth() 186 */ 187 public void setBoxWidth(double width) { 188 if (width != this.boxWidth) { 189 this.boxWidth = width; 190 notifyListeners(new RendererChangeEvent(this)); 191 } 192 } 193 194 /** 195 * Returns the paint used to fill boxes. 196 * 197 * @return The paint (possibly <code>null</code>). 198 * 199 * @see #setBoxPaint(Paint) 200 */ 201 public Paint getBoxPaint() { 202 return this.boxPaint; 203 } 204 205 /** 206 * Sets the paint used to fill boxes and sends a {@link RendererChangeEvent} 207 * to all registered listeners. 208 * 209 * @param paint the paint (<code>null</code> permitted). 210 * 211 * @see #getBoxPaint() 212 */ 213 public void setBoxPaint(Paint paint) { 214 this.boxPaint = paint; 215 notifyListeners(new RendererChangeEvent(this)); 216 } 217 218 /** 219 * Returns the flag that controls whether or not the box is filled. 220 * 221 * @return A boolean. 222 * 223 * @see #setFillBox(boolean) 224 */ 225 public boolean getFillBox() { 226 return this.fillBox; 227 } 228 229 /** 230 * Sets the flag that controls whether or not the box is filled and sends a 231 * {@link RendererChangeEvent} to all registered listeners. 232 * 233 * @param flag the flag. 234 * 235 * @see #setFillBox(boolean) 236 */ 237 public void setFillBox(boolean flag) { 238 this.fillBox = flag; 239 notifyListeners(new RendererChangeEvent(this)); 240 } 241 242 /** 243 * Returns the paint used to paint the various artifacts such as outliers, 244 * farout symbol, median line and the averages ellipse. 245 * 246 * @return The paint (never <code>null</code>). 247 * 248 * @see #setArtifactPaint(Paint) 249 */ 250 public Paint getArtifactPaint() { 251 return this.artifactPaint; 252 } 253 254 /** 255 * Sets the paint used to paint the various artifacts such as outliers, 256 * farout symbol, median line and the averages ellipse. 257 * 258 * @param paint the paint (<code>null</code> not permitted). 259 * 260 * @see #getArtifactPaint() 261 */ 262 public void setArtifactPaint(Paint paint) { 263 if (paint == null) { 264 throw new IllegalArgumentException("Null 'paint' argument."); 265 } 266 this.artifactPaint = paint; 267 notifyListeners(new RendererChangeEvent(this)); 268 } 269 270 /** 271 * Draws the visual representation of a single data item. 272 * 273 * @param g2 the graphics device. 274 * @param state the renderer state. 275 * @param dataArea the area within which the plot is being drawn. 276 * @param info collects info about the drawing. 277 * @param plot the plot (can be used to obtain standard color 278 * information etc). 279 * @param domainAxis the domain axis. 280 * @param rangeAxis the range axis. 281 * @param dataset the dataset. 282 * @param series the series index (zero-based). 283 * @param item the item index (zero-based). 284 * @param crosshairState crosshair information for the plot 285 * (<code>null</code> permitted). 286 * @param pass the pass index. 287 */ 288 public void drawItem(Graphics2D g2, 289 XYItemRendererState state, 290 Rectangle2D dataArea, 291 PlotRenderingInfo info, 292 XYPlot plot, 293 ValueAxis domainAxis, 294 ValueAxis rangeAxis, 295 XYDataset dataset, 296 int series, 297 int item, 298 CrosshairState crosshairState, 299 int pass) { 300 301 PlotOrientation orientation = plot.getOrientation(); 302 303 if (orientation == PlotOrientation.HORIZONTAL) { 304 drawHorizontalItem(g2, dataArea, info, plot, domainAxis, rangeAxis, 305 dataset, series, item, crosshairState, pass); 306 } 307 else if (orientation == PlotOrientation.VERTICAL) { 308 drawVerticalItem(g2, dataArea, info, plot, domainAxis, rangeAxis, 309 dataset, series, item, crosshairState, pass); 310 } 311 312 } 313 314 /** 315 * Draws the visual representation of a single data item. 316 * 317 * @param g2 the graphics device. 318 * @param dataArea the area within which the plot is being drawn. 319 * @param info collects info about the drawing. 320 * @param plot the plot (can be used to obtain standard color 321 * information etc). 322 * @param domainAxis the domain axis. 323 * @param rangeAxis the range axis. 324 * @param dataset the dataset. 325 * @param series the series index (zero-based). 326 * @param item the item index (zero-based). 327 * @param crosshairState crosshair information for the plot 328 * (<code>null</code> permitted). 329 * @param pass the pass index. 330 */ 331 public void drawHorizontalItem(Graphics2D g2, 332 Rectangle2D dataArea, 333 PlotRenderingInfo info, 334 XYPlot plot, 335 ValueAxis domainAxis, 336 ValueAxis rangeAxis, 337 XYDataset dataset, 338 int series, 339 int item, 340 CrosshairState crosshairState, 341 int pass) { 342 343 // setup for collecting optional entity info... 344 EntityCollection entities = null; 345 if (info != null) { 346 entities = info.getOwner().getEntityCollection(); 347 } 348 349 BoxAndWhiskerXYDataset boxAndWhiskerData 350 = (BoxAndWhiskerXYDataset) dataset; 351 352 Number x = boxAndWhiskerData.getX(series, item); 353 Number yMax = boxAndWhiskerData.getMaxRegularValue(series, item); 354 Number yMin = boxAndWhiskerData.getMinRegularValue(series, item); 355 Number yMedian = boxAndWhiskerData.getMedianValue(series, item); 356 Number yAverage = boxAndWhiskerData.getMeanValue(series, item); 357 Number yQ1Median = boxAndWhiskerData.getQ1Value(series, item); 358 Number yQ3Median = boxAndWhiskerData.getQ3Value(series, item); 359 360 double xx = domainAxis.valueToJava2D(x.doubleValue(), dataArea, 361 plot.getDomainAxisEdge()); 362 363 RectangleEdge location = plot.getRangeAxisEdge(); 364 double yyMax = rangeAxis.valueToJava2D(yMax.doubleValue(), dataArea, 365 location); 366 double yyMin = rangeAxis.valueToJava2D(yMin.doubleValue(), dataArea, 367 location); 368 double yyMedian = rangeAxis.valueToJava2D(yMedian.doubleValue(), 369 dataArea, location); 370 double yyAverage = 0.0; 371 if (yAverage != null) { 372 yyAverage = rangeAxis.valueToJava2D(yAverage.doubleValue(), 373 dataArea, location); 374 } 375 double yyQ1Median = rangeAxis.valueToJava2D(yQ1Median.doubleValue(), 376 dataArea, location); 377 double yyQ3Median = rangeAxis.valueToJava2D(yQ3Median.doubleValue(), 378 dataArea, location); 379 380 double exactBoxWidth = getBoxWidth(); 381 double width = exactBoxWidth; 382 double dataAreaX = dataArea.getHeight(); 383 double maxBoxPercent = 0.1; 384 double maxBoxWidth = dataAreaX * maxBoxPercent; 385 if (exactBoxWidth <= 0.0) { 386 int itemCount = boxAndWhiskerData.getItemCount(series); 387 exactBoxWidth = dataAreaX / itemCount * 4.5 / 7; 388 if (exactBoxWidth < 3) { 389 width = 3; 390 } 391 else if (exactBoxWidth > maxBoxWidth) { 392 width = maxBoxWidth; 393 } 394 else { 395 width = exactBoxWidth; 396 } 397 } 398 399 Paint p = getBoxPaint(); 400 if (p != null) { 401 g2.setPaint(p); 402 } 403 Stroke s = getItemStroke(series, item); 404 g2.setStroke(s); 405 406 // draw the upper shadow 407 g2.draw(new Line2D.Double(yyMax, xx, yyQ3Median, xx)); 408 g2.draw(new Line2D.Double(yyMax, xx - width / 2, yyMax, 409 xx + width / 2)); 410 411 // draw the lower shadow 412 g2.draw(new Line2D.Double(yyMin, xx, yyQ1Median, xx)); 413 g2.draw(new Line2D.Double(yyMin, xx - width / 2, yyMin, 414 xx + width / 2)); 415 416 // draw the body 417 Shape box = null; 418 if (yyQ1Median < yyQ3Median) { 419 box = new Rectangle2D.Double(yyQ1Median, xx - width / 2, 420 yyQ3Median - yyQ1Median, width); 421 } 422 else { 423 box = new Rectangle2D.Double(yyQ3Median, xx - width / 2, 424 yyQ1Median - yyQ3Median, width); 425 } 426 if (getBoxPaint() != null) { 427 g2.setPaint(getBoxPaint()); 428 } 429 if (this.fillBox) { 430 g2.fill(box); 431 } 432 g2.draw(box); 433 434 // draw median 435 g2.setPaint(getArtifactPaint()); 436 g2.draw(new Line2D.Double(yyMedian, 437 xx - width / 2, yyMedian, xx + width / 2)); 438 439 // draw average - SPECIAL AIMS REQUIREMENT 440 if (yAverage != null) { 441 double aRadius = width / 4; 442 Ellipse2D.Double avgEllipse = new Ellipse2D.Double( 443 yyAverage - aRadius, xx - aRadius, aRadius * 2, 444 aRadius * 2); 445 g2.fill(avgEllipse); 446 g2.draw(avgEllipse); 447 } 448 449 // FIXME: draw outliers 450 451 // add an entity for the item... 452 if (entities != null && box.intersects(dataArea)) { 453 addEntity(entities, box, dataset, series, item, yyAverage, xx); 454 } 455 456 } 457 458 /** 459 * Draws the visual representation of a single data item. 460 * 461 * @param g2 the graphics device. 462 * @param dataArea the area within which the plot is being drawn. 463 * @param info collects info about the drawing. 464 * @param plot the plot (can be used to obtain standard color 465 * information etc). 466 * @param domainAxis the domain axis. 467 * @param rangeAxis the range axis. 468 * @param dataset the dataset. 469 * @param series the series index (zero-based). 470 * @param item the item index (zero-based). 471 * @param crosshairState crosshair information for the plot 472 * (<code>null</code> permitted). 473 * @param pass the pass index. 474 */ 475 public void drawVerticalItem(Graphics2D g2, 476 Rectangle2D dataArea, 477 PlotRenderingInfo info, 478 XYPlot plot, 479 ValueAxis domainAxis, 480 ValueAxis rangeAxis, 481 XYDataset dataset, 482 int series, 483 int item, 484 CrosshairState crosshairState, 485 int pass) { 486 487 // setup for collecting optional entity info... 488 EntityCollection entities = null; 489 if (info != null) { 490 entities = info.getOwner().getEntityCollection(); 491 } 492 493 BoxAndWhiskerXYDataset boxAndWhiskerData 494 = (BoxAndWhiskerXYDataset) dataset; 495 496 Number x = boxAndWhiskerData.getX(series, item); 497 Number yMax = boxAndWhiskerData.getMaxRegularValue(series, item); 498 Number yMin = boxAndWhiskerData.getMinRegularValue(series, item); 499 Number yMedian = boxAndWhiskerData.getMedianValue(series, item); 500 Number yAverage = boxAndWhiskerData.getMeanValue(series, item); 501 Number yQ1Median = boxAndWhiskerData.getQ1Value(series, item); 502 Number yQ3Median = boxAndWhiskerData.getQ3Value(series, item); 503 List yOutliers = boxAndWhiskerData.getOutliers(series, item); 504 505 double xx = domainAxis.valueToJava2D(x.doubleValue(), dataArea, 506 plot.getDomainAxisEdge()); 507 508 RectangleEdge location = plot.getRangeAxisEdge(); 509 double yyMax = rangeAxis.valueToJava2D(yMax.doubleValue(), dataArea, 510 location); 511 double yyMin = rangeAxis.valueToJava2D(yMin.doubleValue(), dataArea, 512 location); 513 double yyMedian = rangeAxis.valueToJava2D(yMedian.doubleValue(), 514 dataArea, location); 515 double yyAverage = 0.0; 516 if (yAverage != null) { 517 yyAverage = rangeAxis.valueToJava2D(yAverage.doubleValue(), 518 dataArea, location); 519 } 520 double yyQ1Median = rangeAxis.valueToJava2D(yQ1Median.doubleValue(), 521 dataArea, location); 522 double yyQ3Median = rangeAxis.valueToJava2D(yQ3Median.doubleValue(), 523 dataArea, location); 524 double yyOutlier; 525 526 527 double exactBoxWidth = getBoxWidth(); 528 double width = exactBoxWidth; 529 double dataAreaX = dataArea.getMaxX() - dataArea.getMinX(); 530 double maxBoxPercent = 0.1; 531 double maxBoxWidth = dataAreaX * maxBoxPercent; 532 if (exactBoxWidth <= 0.0) { 533 int itemCount = boxAndWhiskerData.getItemCount(series); 534 exactBoxWidth = dataAreaX / itemCount * 4.5 / 7; 535 if (exactBoxWidth < 3) { 536 width = 3; 537 } 538 else if (exactBoxWidth > maxBoxWidth) { 539 width = maxBoxWidth; 540 } 541 else { 542 width = exactBoxWidth; 543 } 544 } 545 546 Paint p = getBoxPaint(); 547 if (p != null) { 548 g2.setPaint(p); 549 } 550 Stroke s = getItemStroke(series, item); 551 552 g2.setStroke(s); 553 554 // draw the upper shadow 555 g2.draw(new Line2D.Double(xx, yyMax, xx, yyQ3Median)); 556 g2.draw(new Line2D.Double(xx - width / 2, yyMax, xx + width / 2, 557 yyMax)); 558 559 // draw the lower shadow 560 g2.draw(new Line2D.Double(xx, yyMin, xx, yyQ1Median)); 561 g2.draw(new Line2D.Double(xx - width / 2, yyMin, xx + width / 2, 562 yyMin)); 563 564 // draw the body 565 Shape box = null; 566 if (yyQ1Median > yyQ3Median) { 567 box = new Rectangle2D.Double(xx - width / 2, yyQ3Median, width, 568 yyQ1Median - yyQ3Median); 569 } 570 else { 571 box = new Rectangle2D.Double(xx - width / 2, yyQ1Median, width, 572 yyQ3Median - yyQ1Median); 573 } 574 if (this.fillBox) { 575 g2.fill(box); 576 } 577 g2.draw(box); 578 579 // draw median 580 g2.setPaint(getArtifactPaint()); 581 g2.draw(new Line2D.Double(xx - width / 2, yyMedian, xx + width / 2, 582 yyMedian)); 583 584 double aRadius = 0; // average radius 585 double oRadius = width / 3; // outlier radius 586 587 // draw average - SPECIAL AIMS REQUIREMENT 588 if (yAverage != null) { 589 aRadius = width / 4; 590 Ellipse2D.Double avgEllipse = new Ellipse2D.Double(xx - aRadius, 591 yyAverage - aRadius, aRadius * 2, aRadius * 2); 592 g2.fill(avgEllipse); 593 g2.draw(avgEllipse); 594 } 595 596 List outliers = new ArrayList(); 597 OutlierListCollection outlierListCollection 598 = new OutlierListCollection(); 599 600 /* From outlier array sort out which are outliers and put these into 601 * an arraylist. If there are any farouts, set the flag on the 602 * OutlierListCollection 603 */ 604 605 for (int i = 0; i < yOutliers.size(); i++) { 606 double outlier = ((Number) yOutliers.get(i)).doubleValue(); 607 if (outlier > boxAndWhiskerData.getMaxOutlier(series, 608 item).doubleValue()) { 609 outlierListCollection.setHighFarOut(true); 610 } 611 else if (outlier < boxAndWhiskerData.getMinOutlier(series, 612 item).doubleValue()) { 613 outlierListCollection.setLowFarOut(true); 614 } 615 else if (outlier > boxAndWhiskerData.getMaxRegularValue(series, 616 item).doubleValue()) { 617 yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea, 618 location); 619 outliers.add(new Outlier(xx, yyOutlier, oRadius)); 620 } 621 else if (outlier < boxAndWhiskerData.getMinRegularValue(series, 622 item).doubleValue()) { 623 yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea, 624 location); 625 outliers.add(new Outlier(xx, yyOutlier, oRadius)); 626 } 627 Collections.sort(outliers); 628 } 629 630 // Process outliers. Each outlier is either added to the appropriate 631 // outlier list or a new outlier list is made 632 for (Iterator iterator = outliers.iterator(); iterator.hasNext();) { 633 Outlier outlier = (Outlier) iterator.next(); 634 outlierListCollection.add(outlier); 635 } 636 637 // draw yOutliers 638 double maxAxisValue = rangeAxis.valueToJava2D(rangeAxis.getUpperBound(), 639 dataArea, location) + aRadius; 640 double minAxisValue = rangeAxis.valueToJava2D(rangeAxis.getLowerBound(), 641 dataArea, location) - aRadius; 642 643 // draw outliers 644 for (Iterator iterator = outlierListCollection.iterator(); 645 iterator.hasNext();) { 646 OutlierList list = (OutlierList) iterator.next(); 647 Outlier outlier = list.getAveragedOutlier(); 648 Point2D point = outlier.getPoint(); 649 650 if (list.isMultiple()) { 651 drawMultipleEllipse(point, width, oRadius, g2); 652 } 653 else { 654 drawEllipse(point, oRadius, g2); 655 } 656 } 657 658 // draw farout 659 if (outlierListCollection.isHighFarOut()) { 660 drawHighFarOut(aRadius, g2, xx, maxAxisValue); 661 } 662 663 if (outlierListCollection.isLowFarOut()) { 664 drawLowFarOut(aRadius, g2, xx, minAxisValue); 665 } 666 667 // add an entity for the item... 668 if (entities != null && box.intersects(dataArea)) { 669 addEntity(entities, box, dataset, series, item, xx, yyAverage); 670 } 671 672 } 673 674 /** 675 * Draws an ellipse to represent an outlier. 676 * 677 * @param point the location. 678 * @param oRadius the radius. 679 * @param g2 the graphics device. 680 */ 681 protected void drawEllipse(Point2D point, double oRadius, Graphics2D g2) { 682 Ellipse2D.Double dot = new Ellipse2D.Double(point.getX() + oRadius / 2, 683 point.getY(), oRadius, oRadius); 684 g2.draw(dot); 685 } 686 687 /** 688 * Draws two ellipses to represent overlapping outliers. 689 * 690 * @param point the location. 691 * @param boxWidth the box width. 692 * @param oRadius the radius. 693 * @param g2 the graphics device. 694 */ 695 protected void drawMultipleEllipse(Point2D point, double boxWidth, 696 double oRadius, Graphics2D g2) { 697 698 Ellipse2D.Double dot1 = new Ellipse2D.Double(point.getX() 699 - (boxWidth / 2) + oRadius, point.getY(), oRadius, oRadius); 700 Ellipse2D.Double dot2 = new Ellipse2D.Double(point.getX() 701 + (boxWidth / 2), point.getY(), oRadius, oRadius); 702 g2.draw(dot1); 703 g2.draw(dot2); 704 705 } 706 707 /** 708 * Draws a triangle to indicate the presence of far out values. 709 * 710 * @param aRadius the radius. 711 * @param g2 the graphics device. 712 * @param xx the x value. 713 * @param m the max y value. 714 */ 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 /** 724 * Draws a triangle to indicate the presence of far out values. 725 * 726 * @param aRadius the radius. 727 * @param g2 the graphics device. 728 * @param xx the x value. 729 * @param m the min y value. 730 */ 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 /** 740 * Tests this renderer for equality with another object. 741 * 742 * @param obj the object (<code>null</code> permitted). 743 * 744 * @return <code>true</code> or <code>false</code>. 745 */ 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 /** 774 * Provides serialization support. 775 * 776 * @param stream the output stream. 777 * 778 * @throws IOException if there is an I/O error. 779 */ 780 private void writeObject(ObjectOutputStream stream) throws IOException { 781 stream.defaultWriteObject(); 782 SerialUtilities.writePaint(this.boxPaint, stream); 783 SerialUtilities.writePaint(this.artifactPaint, stream); 784 } 785 786 /** 787 * Provides serialization support. 788 * 789 * @param stream the input stream. 790 * 791 * @throws IOException if there is an I/O error. 792 * @throws ClassNotFoundException if there is a classpath problem. 793 */ 794 private void readObject(ObjectInputStream stream) 795 throws IOException, ClassNotFoundException { 796 797 stream.defaultReadObject(); 798 this.boxPaint = SerialUtilities.readPaint(stream); 799 this.artifactPaint = SerialUtilities.readPaint(stream); 800 } 801 802 /** 803 * Returns a clone of the renderer. 804 * 805 * @return A clone. 806 * 807 * @throws CloneNotSupportedException if the renderer cannot be cloned. 808 */ 809 public Object clone() throws CloneNotSupportedException { 810 return super.clone(); 811 } 812 813}