1:
73:
74: package ;
75:
76: import ;
77: import ;
78: import ;
79: import ;
80: import ;
81: import ;
82: import ;
83: import ;
84: import ;
85: import ;
86: import ;
87: import ;
88: import ;
89: import ;
90: import ;
91: import ;
92: import ;
93:
94: import ;
95: import ;
96: import ;
97: import ;
98: import ;
99: import ;
100: import ;
101: import ;
102: import ;
103: import ;
104: import ;
105: import ;
106: import ;
107: import ;
108: import ;
109: import ;
110: import ;
111:
112:
118: public class XYBoxAndWhiskerRenderer extends AbstractXYItemRenderer
119: implements XYItemRenderer,
120: Cloneable,
121: PublicCloneable,
122: Serializable {
123:
124:
125: private static final long serialVersionUID = -8020170108532232324L;
126:
127:
128: private double boxWidth;
129:
130:
131: private transient Paint boxPaint;
132:
133:
134: private boolean fillBox;
135:
136:
140: private transient Paint artifactPaint = Color.black;
141:
142:
145: public XYBoxAndWhiskerRenderer() {
146: this(-1.0);
147: }
148:
149:
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:
172: public double getBoxWidth() {
173: return this.boxWidth;
174: }
175:
176:
187: public void setBoxWidth(double width) {
188: if (width != this.boxWidth) {
189: this.boxWidth = width;
190: notifyListeners(new RendererChangeEvent(this));
191: }
192: }
193:
194:
201: public Paint getBoxPaint() {
202: return this.boxPaint;
203: }
204:
205:
213: public void setBoxPaint(Paint paint) {
214: this.boxPaint = paint;
215: notifyListeners(new RendererChangeEvent(this));
216: }
217:
218:
225: public boolean getFillBox() {
226: return this.fillBox;
227: }
228:
229:
237: public void setFillBox(boolean flag) {
238: this.fillBox = flag;
239: notifyListeners(new RendererChangeEvent(this));
240: }
241:
242:
250: public Paint getArtifactPaint() {
251: return this.artifactPaint;
252: }
253:
254:
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:
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:
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:
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:
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:
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:
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:
435: g2.setPaint(getArtifactPaint());
436: g2.draw(new Line2D.Double(yyMedian,
437: xx - width / 2, yyMedian, xx + width / 2));
438:
439:
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:
450:
451:
452: if (entities != null && box.intersects(dataArea)) {
453: addEntity(entities, box, dataset, series, item, yyAverage, xx);
454: }
455:
456: }
457:
458:
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:
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:
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:
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:
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:
580: g2.setPaint(getArtifactPaint());
581: g2.draw(new Line2D.Double(xx - width / 2, yyMedian, xx + width / 2,
582: yyMedian));
583:
584: double aRadius = 0;
585: double oRadius = width / 3;
586:
587:
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:
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:
631:
632: for (Iterator iterator = outliers.iterator(); iterator.hasNext();) {
633: Outlier outlier = (Outlier) iterator.next();
634: outlierListCollection.add(outlier);
635: }
636:
637:
638: double maxAxisValue = rangeAxis.valueToJava2D(rangeAxis.getUpperBound(),
639: dataArea, location) + aRadius;
640: double minAxisValue = rangeAxis.valueToJava2D(rangeAxis.getLowerBound(),
641: dataArea, location) - aRadius;
642:
643:
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:
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:
668: if (entities != null && box.intersects(dataArea)) {
669: addEntity(entities, box, dataset, series, item, xx, yyAverage);
670: }
671:
672: }
673:
674:
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:
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:
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:
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:
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:
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:
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:
809: public Object clone() throws CloneNotSupportedException {
810: return super.clone();
811: }
812:
813: }