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