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
074 package org.jfree.chart.renderer.xy;
075
076 import java.awt.Color;
077 import java.awt.Graphics2D;
078 import java.awt.Paint;
079 import java.awt.Shape;
080 import java.awt.Stroke;
081 import java.awt.geom.Ellipse2D;
082 import java.awt.geom.Line2D;
083 import java.awt.geom.Point2D;
084 import java.awt.geom.Rectangle2D;
085 import java.io.IOException;
086 import java.io.ObjectInputStream;
087 import java.io.ObjectOutputStream;
088 import java.io.Serializable;
089 import java.util.ArrayList;
090 import java.util.Collections;
091 import java.util.Iterator;
092 import java.util.List;
093
094 import org.jfree.chart.axis.ValueAxis;
095 import org.jfree.chart.entity.EntityCollection;
096 import org.jfree.chart.event.RendererChangeEvent;
097 import org.jfree.chart.labels.BoxAndWhiskerXYToolTipGenerator;
098 import org.jfree.chart.plot.CrosshairState;
099 import org.jfree.chart.plot.PlotOrientation;
100 import org.jfree.chart.plot.PlotRenderingInfo;
101 import org.jfree.chart.plot.XYPlot;
102 import org.jfree.chart.renderer.Outlier;
103 import org.jfree.chart.renderer.OutlierList;
104 import org.jfree.chart.renderer.OutlierListCollection;
105 import org.jfree.data.statistics.BoxAndWhiskerXYDataset;
106 import org.jfree.data.xy.XYDataset;
107 import org.jfree.io.SerialUtilities;
108 import org.jfree.ui.RectangleEdge;
109 import org.jfree.util.PaintUtilities;
110 import 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 */
118 public 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 fireChangeEvent();
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 fireChangeEvent();
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 fireChangeEvent();
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, and sends a
257 * {@link RendererChangeEvent} to all registered listeners.
258 *
259 * @param paint the paint (<code>null</code> not permitted).
260 *
261 * @see #getArtifactPaint()
262 */
263 public void setArtifactPaint(Paint paint) {
264 if (paint == null) {
265 throw new IllegalArgumentException("Null 'paint' argument.");
266 }
267 this.artifactPaint = paint;
268 fireChangeEvent();
269 }
270
271 /**
272 * Draws the visual representation of a single data item.
273 *
274 * @param g2 the graphics device.
275 * @param state the renderer state.
276 * @param dataArea the area within which the plot is being drawn.
277 * @param info collects info about the drawing.
278 * @param plot the plot (can be used to obtain standard color
279 * information etc).
280 * @param domainAxis the domain axis.
281 * @param rangeAxis the range axis.
282 * @param dataset the dataset.
283 * @param series the series index (zero-based).
284 * @param item the item index (zero-based).
285 * @param crosshairState crosshair information for the plot
286 * (<code>null</code> permitted).
287 * @param pass the pass index.
288 */
289 public void drawItem(Graphics2D g2,
290 XYItemRendererState state,
291 Rectangle2D dataArea,
292 PlotRenderingInfo info,
293 XYPlot plot,
294 ValueAxis domainAxis,
295 ValueAxis rangeAxis,
296 XYDataset dataset,
297 int series,
298 int item,
299 CrosshairState crosshairState,
300 int pass) {
301
302 PlotOrientation orientation = plot.getOrientation();
303
304 if (orientation == PlotOrientation.HORIZONTAL) {
305 drawHorizontalItem(g2, dataArea, info, plot, domainAxis, rangeAxis,
306 dataset, series, item, crosshairState, pass);
307 }
308 else if (orientation == PlotOrientation.VERTICAL) {
309 drawVerticalItem(g2, dataArea, info, plot, domainAxis, rangeAxis,
310 dataset, series, item, crosshairState, pass);
311 }
312
313 }
314
315 /**
316 * Draws the visual representation of a single data item.
317 *
318 * @param g2 the graphics device.
319 * @param dataArea the area within which the plot is being drawn.
320 * @param info collects info about the drawing.
321 * @param plot the plot (can be used to obtain standard color
322 * information etc).
323 * @param domainAxis the domain axis.
324 * @param rangeAxis the range axis.
325 * @param dataset the dataset.
326 * @param series the series index (zero-based).
327 * @param item the item index (zero-based).
328 * @param crosshairState crosshair information for the plot
329 * (<code>null</code> permitted).
330 * @param pass the pass index.
331 */
332 public void drawHorizontalItem(Graphics2D g2,
333 Rectangle2D dataArea,
334 PlotRenderingInfo info,
335 XYPlot plot,
336 ValueAxis domainAxis,
337 ValueAxis rangeAxis,
338 XYDataset dataset,
339 int series,
340 int item,
341 CrosshairState crosshairState,
342 int pass) {
343
344 // setup for collecting optional entity info...
345 EntityCollection entities = null;
346 if (info != null) {
347 entities = info.getOwner().getEntityCollection();
348 }
349
350 BoxAndWhiskerXYDataset boxAndWhiskerData
351 = (BoxAndWhiskerXYDataset) dataset;
352
353 Number x = boxAndWhiskerData.getX(series, item);
354 Number yMax = boxAndWhiskerData.getMaxRegularValue(series, item);
355 Number yMin = boxAndWhiskerData.getMinRegularValue(series, item);
356 Number yMedian = boxAndWhiskerData.getMedianValue(series, item);
357 Number yAverage = boxAndWhiskerData.getMeanValue(series, item);
358 Number yQ1Median = boxAndWhiskerData.getQ1Value(series, item);
359 Number yQ3Median = boxAndWhiskerData.getQ3Value(series, item);
360
361 double xx = domainAxis.valueToJava2D(x.doubleValue(), dataArea,
362 plot.getDomainAxisEdge());
363
364 RectangleEdge location = plot.getRangeAxisEdge();
365 double yyMax = rangeAxis.valueToJava2D(yMax.doubleValue(), dataArea,
366 location);
367 double yyMin = rangeAxis.valueToJava2D(yMin.doubleValue(), dataArea,
368 location);
369 double yyMedian = rangeAxis.valueToJava2D(yMedian.doubleValue(),
370 dataArea, location);
371 double yyAverage = 0.0;
372 if (yAverage != null) {
373 yyAverage = rangeAxis.valueToJava2D(yAverage.doubleValue(),
374 dataArea, location);
375 }
376 double yyQ1Median = rangeAxis.valueToJava2D(yQ1Median.doubleValue(),
377 dataArea, location);
378 double yyQ3Median = rangeAxis.valueToJava2D(yQ3Median.doubleValue(),
379 dataArea, location);
380
381 double exactBoxWidth = getBoxWidth();
382 double width = exactBoxWidth;
383 double dataAreaX = dataArea.getHeight();
384 double maxBoxPercent = 0.1;
385 double maxBoxWidth = dataAreaX * maxBoxPercent;
386 if (exactBoxWidth <= 0.0) {
387 int itemCount = boxAndWhiskerData.getItemCount(series);
388 exactBoxWidth = dataAreaX / itemCount * 4.5 / 7;
389 if (exactBoxWidth < 3) {
390 width = 3;
391 }
392 else if (exactBoxWidth > maxBoxWidth) {
393 width = maxBoxWidth;
394 }
395 else {
396 width = exactBoxWidth;
397 }
398 }
399
400 Paint p = getBoxPaint();
401 if (p != null) {
402 g2.setPaint(p);
403 }
404 Stroke s = getItemStroke(series, item);
405 g2.setStroke(s);
406
407 // draw the upper shadow
408 g2.draw(new Line2D.Double(yyMax, xx, yyQ3Median, xx));
409 g2.draw(new Line2D.Double(yyMax, xx - width / 2, yyMax,
410 xx + width / 2));
411
412 // draw the lower shadow
413 g2.draw(new Line2D.Double(yyMin, xx, yyQ1Median, xx));
414 g2.draw(new Line2D.Double(yyMin, xx - width / 2, yyMin,
415 xx + width / 2));
416
417 // draw the body
418 Shape box = null;
419 if (yyQ1Median < yyQ3Median) {
420 box = new Rectangle2D.Double(yyQ1Median, xx - width / 2,
421 yyQ3Median - yyQ1Median, width);
422 }
423 else {
424 box = new Rectangle2D.Double(yyQ3Median, xx - width / 2,
425 yyQ1Median - yyQ3Median, width);
426 }
427 if (getBoxPaint() != null) {
428 g2.setPaint(getBoxPaint());
429 }
430 if (this.fillBox) {
431 g2.fill(box);
432 }
433 g2.draw(box);
434
435 // draw median
436 g2.setPaint(getArtifactPaint());
437 g2.draw(new Line2D.Double(yyMedian,
438 xx - width / 2, yyMedian, xx + width / 2));
439
440 // draw average - SPECIAL AIMS REQUIREMENT
441 if (yAverage != null) {
442 double aRadius = width / 4;
443 Ellipse2D.Double avgEllipse = new Ellipse2D.Double(
444 yyAverage - aRadius, xx - aRadius, aRadius * 2,
445 aRadius * 2);
446 g2.fill(avgEllipse);
447 g2.draw(avgEllipse);
448 }
449
450 // FIXME: draw outliers
451
452 // add an entity for the item...
453 if (entities != null && box.intersects(dataArea)) {
454 addEntity(entities, box, dataset, series, item, yyAverage, xx);
455 }
456
457 }
458
459 /**
460 * Draws the visual representation of a single data item.
461 *
462 * @param g2 the graphics device.
463 * @param dataArea the area within which the plot is being drawn.
464 * @param info collects info about the drawing.
465 * @param plot the plot (can be used to obtain standard color
466 * information etc).
467 * @param domainAxis the domain axis.
468 * @param rangeAxis the range axis.
469 * @param dataset the dataset.
470 * @param series the series index (zero-based).
471 * @param item the item index (zero-based).
472 * @param crosshairState crosshair information for the plot
473 * (<code>null</code> permitted).
474 * @param pass the pass index.
475 */
476 public void drawVerticalItem(Graphics2D g2,
477 Rectangle2D dataArea,
478 PlotRenderingInfo info,
479 XYPlot plot,
480 ValueAxis domainAxis,
481 ValueAxis rangeAxis,
482 XYDataset dataset,
483 int series,
484 int item,
485 CrosshairState crosshairState,
486 int pass) {
487
488 // setup for collecting optional entity info...
489 EntityCollection entities = null;
490 if (info != null) {
491 entities = info.getOwner().getEntityCollection();
492 }
493
494 BoxAndWhiskerXYDataset boxAndWhiskerData
495 = (BoxAndWhiskerXYDataset) dataset;
496
497 Number x = boxAndWhiskerData.getX(series, item);
498 Number yMax = boxAndWhiskerData.getMaxRegularValue(series, item);
499 Number yMin = boxAndWhiskerData.getMinRegularValue(series, item);
500 Number yMedian = boxAndWhiskerData.getMedianValue(series, item);
501 Number yAverage = boxAndWhiskerData.getMeanValue(series, item);
502 Number yQ1Median = boxAndWhiskerData.getQ1Value(series, item);
503 Number yQ3Median = boxAndWhiskerData.getQ3Value(series, item);
504 List yOutliers = boxAndWhiskerData.getOutliers(series, item);
505
506 double xx = domainAxis.valueToJava2D(x.doubleValue(), dataArea,
507 plot.getDomainAxisEdge());
508
509 RectangleEdge location = plot.getRangeAxisEdge();
510 double yyMax = rangeAxis.valueToJava2D(yMax.doubleValue(), dataArea,
511 location);
512 double yyMin = rangeAxis.valueToJava2D(yMin.doubleValue(), dataArea,
513 location);
514 double yyMedian = rangeAxis.valueToJava2D(yMedian.doubleValue(),
515 dataArea, location);
516 double yyAverage = 0.0;
517 if (yAverage != null) {
518 yyAverage = rangeAxis.valueToJava2D(yAverage.doubleValue(),
519 dataArea, location);
520 }
521 double yyQ1Median = rangeAxis.valueToJava2D(yQ1Median.doubleValue(),
522 dataArea, location);
523 double yyQ3Median = rangeAxis.valueToJava2D(yQ3Median.doubleValue(),
524 dataArea, location);
525 double yyOutlier;
526
527
528 double exactBoxWidth = getBoxWidth();
529 double width = exactBoxWidth;
530 double dataAreaX = dataArea.getMaxX() - dataArea.getMinX();
531 double maxBoxPercent = 0.1;
532 double maxBoxWidth = dataAreaX * maxBoxPercent;
533 if (exactBoxWidth <= 0.0) {
534 int itemCount = boxAndWhiskerData.getItemCount(series);
535 exactBoxWidth = dataAreaX / itemCount * 4.5 / 7;
536 if (exactBoxWidth < 3) {
537 width = 3;
538 }
539 else if (exactBoxWidth > maxBoxWidth) {
540 width = maxBoxWidth;
541 }
542 else {
543 width = exactBoxWidth;
544 }
545 }
546
547 Paint p = getBoxPaint();
548 if (p != null) {
549 g2.setPaint(p);
550 }
551 Stroke s = getItemStroke(series, item);
552
553 g2.setStroke(s);
554
555 // draw the upper shadow
556 g2.draw(new Line2D.Double(xx, yyMax, xx, yyQ3Median));
557 g2.draw(new Line2D.Double(xx - width / 2, yyMax, xx + width / 2,
558 yyMax));
559
560 // draw the lower shadow
561 g2.draw(new Line2D.Double(xx, yyMin, xx, yyQ1Median));
562 g2.draw(new Line2D.Double(xx - width / 2, yyMin, xx + width / 2,
563 yyMin));
564
565 // draw the body
566 Shape box = null;
567 if (yyQ1Median > yyQ3Median) {
568 box = new Rectangle2D.Double(xx - width / 2, yyQ3Median, width,
569 yyQ1Median - yyQ3Median);
570 }
571 else {
572 box = new Rectangle2D.Double(xx - width / 2, yyQ1Median, width,
573 yyQ3Median - yyQ1Median);
574 }
575 if (this.fillBox) {
576 g2.fill(box);
577 }
578 g2.draw(box);
579
580 // draw median
581 g2.setPaint(getArtifactPaint());
582 g2.draw(new Line2D.Double(xx - width / 2, yyMedian, xx + width / 2,
583 yyMedian));
584
585 double aRadius = 0; // average radius
586 double oRadius = width / 3; // outlier radius
587
588 // draw average - SPECIAL AIMS REQUIREMENT
589 if (yAverage != null) {
590 aRadius = width / 4;
591 Ellipse2D.Double avgEllipse = new Ellipse2D.Double(xx - aRadius,
592 yyAverage - aRadius, aRadius * 2, aRadius * 2);
593 g2.fill(avgEllipse);
594 g2.draw(avgEllipse);
595 }
596
597 List outliers = new ArrayList();
598 OutlierListCollection outlierListCollection
599 = new OutlierListCollection();
600
601 /* From outlier array sort out which are outliers and put these into
602 * an arraylist. If there are any farouts, set the flag on the
603 * OutlierListCollection
604 */
605
606 for (int i = 0; i < yOutliers.size(); i++) {
607 double outlier = ((Number) yOutliers.get(i)).doubleValue();
608 if (outlier > boxAndWhiskerData.getMaxOutlier(series,
609 item).doubleValue()) {
610 outlierListCollection.setHighFarOut(true);
611 }
612 else if (outlier < boxAndWhiskerData.getMinOutlier(series,
613 item).doubleValue()) {
614 outlierListCollection.setLowFarOut(true);
615 }
616 else if (outlier > boxAndWhiskerData.getMaxRegularValue(series,
617 item).doubleValue()) {
618 yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea,
619 location);
620 outliers.add(new Outlier(xx, yyOutlier, oRadius));
621 }
622 else if (outlier < boxAndWhiskerData.getMinRegularValue(series,
623 item).doubleValue()) {
624 yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea,
625 location);
626 outliers.add(new Outlier(xx, yyOutlier, oRadius));
627 }
628 Collections.sort(outliers);
629 }
630
631 // Process outliers. Each outlier is either added to the appropriate
632 // outlier list or a new outlier list is made
633 for (Iterator iterator = outliers.iterator(); iterator.hasNext();) {
634 Outlier outlier = (Outlier) iterator.next();
635 outlierListCollection.add(outlier);
636 }
637
638 // draw yOutliers
639 double maxAxisValue = rangeAxis.valueToJava2D(rangeAxis.getUpperBound(),
640 dataArea, location) + aRadius;
641 double minAxisValue = rangeAxis.valueToJava2D(rangeAxis.getLowerBound(),
642 dataArea, location) - aRadius;
643
644 // draw outliers
645 for (Iterator iterator = outlierListCollection.iterator();
646 iterator.hasNext();) {
647 OutlierList list = (OutlierList) iterator.next();
648 Outlier outlier = list.getAveragedOutlier();
649 Point2D point = outlier.getPoint();
650
651 if (list.isMultiple()) {
652 drawMultipleEllipse(point, width, oRadius, g2);
653 }
654 else {
655 drawEllipse(point, oRadius, g2);
656 }
657 }
658
659 // draw farout
660 if (outlierListCollection.isHighFarOut()) {
661 drawHighFarOut(aRadius, g2, xx, maxAxisValue);
662 }
663
664 if (outlierListCollection.isLowFarOut()) {
665 drawLowFarOut(aRadius, g2, xx, minAxisValue);
666 }
667
668 // add an entity for the item...
669 if (entities != null && box.intersects(dataArea)) {
670 addEntity(entities, box, dataset, series, item, xx, yyAverage);
671 }
672
673 }
674
675 /**
676 * Draws an ellipse to represent an outlier.
677 *
678 * @param point the location.
679 * @param oRadius the radius.
680 * @param g2 the graphics device.
681 */
682 protected void drawEllipse(Point2D point, double oRadius, Graphics2D g2) {
683 Ellipse2D.Double dot = new Ellipse2D.Double(point.getX() + oRadius / 2,
684 point.getY(), oRadius, oRadius);
685 g2.draw(dot);
686 }
687
688 /**
689 * Draws two ellipses to represent overlapping outliers.
690 *
691 * @param point the location.
692 * @param boxWidth the box width.
693 * @param oRadius the radius.
694 * @param g2 the graphics device.
695 */
696 protected void drawMultipleEllipse(Point2D point, double boxWidth,
697 double oRadius, Graphics2D g2) {
698
699 Ellipse2D.Double dot1 = new Ellipse2D.Double(point.getX()
700 - (boxWidth / 2) + oRadius, point.getY(), oRadius, oRadius);
701 Ellipse2D.Double dot2 = new Ellipse2D.Double(point.getX()
702 + (boxWidth / 2), point.getY(), oRadius, oRadius);
703 g2.draw(dot1);
704 g2.draw(dot2);
705
706 }
707
708 /**
709 * Draws a triangle to indicate the presence of far out values.
710 *
711 * @param aRadius the radius.
712 * @param g2 the graphics device.
713 * @param xx the x value.
714 * @param m the max y value.
715 */
716 protected void drawHighFarOut(double aRadius, Graphics2D g2, double xx,
717 double m) {
718 double side = aRadius * 2;
719 g2.draw(new Line2D.Double(xx - side, m + side, xx + side, m + side));
720 g2.draw(new Line2D.Double(xx - side, m + side, xx, m));
721 g2.draw(new Line2D.Double(xx + side, m + side, xx, m));
722 }
723
724 /**
725 * Draws a triangle to indicate the presence of far out values.
726 *
727 * @param aRadius the radius.
728 * @param g2 the graphics device.
729 * @param xx the x value.
730 * @param m the min y value.
731 */
732 protected void drawLowFarOut(double aRadius, Graphics2D g2, double xx,
733 double m) {
734 double side = aRadius * 2;
735 g2.draw(new Line2D.Double(xx - side, m - side, xx + side, m - side));
736 g2.draw(new Line2D.Double(xx - side, m - side, xx, m));
737 g2.draw(new Line2D.Double(xx + side, m - side, xx, m));
738 }
739
740 /**
741 * Tests this renderer for equality with another object.
742 *
743 * @param obj the object (<code>null</code> permitted).
744 *
745 * @return <code>true</code> or <code>false</code>.
746 */
747 public boolean equals(Object obj) {
748 if (obj == this) {
749 return true;
750 }
751 if (!(obj instanceof XYBoxAndWhiskerRenderer)) {
752 return false;
753 }
754 if (!super.equals(obj)) {
755 return false;
756 }
757 XYBoxAndWhiskerRenderer that = (XYBoxAndWhiskerRenderer) obj;
758 if (this.boxWidth != that.getBoxWidth()) {
759 return false;
760 }
761 if (!PaintUtilities.equal(this.boxPaint, that.boxPaint)) {
762 return false;
763 }
764 if (!PaintUtilities.equal(this.artifactPaint, that.artifactPaint)) {
765 return false;
766 }
767 if (this.fillBox != that.fillBox) {
768 return false;
769 }
770 return true;
771
772 }
773
774 /**
775 * Provides serialization support.
776 *
777 * @param stream the output stream.
778 *
779 * @throws IOException if there is an I/O error.
780 */
781 private void writeObject(ObjectOutputStream stream) throws IOException {
782 stream.defaultWriteObject();
783 SerialUtilities.writePaint(this.boxPaint, stream);
784 SerialUtilities.writePaint(this.artifactPaint, stream);
785 }
786
787 /**
788 * Provides serialization support.
789 *
790 * @param stream the input stream.
791 *
792 * @throws IOException if there is an I/O error.
793 * @throws ClassNotFoundException if there is a classpath problem.
794 */
795 private void readObject(ObjectInputStream stream)
796 throws IOException, ClassNotFoundException {
797
798 stream.defaultReadObject();
799 this.boxPaint = SerialUtilities.readPaint(stream);
800 this.artifactPaint = SerialUtilities.readPaint(stream);
801 }
802
803 /**
804 * Returns a clone of the renderer.
805 *
806 * @return A clone.
807 *
808 * @throws CloneNotSupportedException if the renderer cannot be cloned.
809 */
810 public Object clone() throws CloneNotSupportedException {
811 return super.clone();
812 }
813
814 }