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