1:
74:
75: package ;
76:
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: import ;
94:
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: import ;
112:
113:
119: public class XYBoxAndWhiskerRenderer extends AbstractXYItemRenderer
120: implements XYItemRenderer,
121: Cloneable,
122: PublicCloneable,
123: Serializable {
124:
125:
126: private static final long serialVersionUID = -8020170108532232324L;
127:
128:
129: private double boxWidth;
130:
131:
132: private transient Paint boxPaint;
133:
134:
135: private boolean fillBox;
136:
137:
141: private transient Paint artifactPaint = Color.black;
142:
143:
146: public XYBoxAndWhiskerRenderer() {
147: this(-1.0);
148: }
149:
150:
158: public XYBoxAndWhiskerRenderer(double boxWidth) {
159: super();
160: this.boxWidth = boxWidth;
161: this.boxPaint = Color.green;
162: this.fillBox = true;
163: setBaseToolTipGenerator(new BoxAndWhiskerXYToolTipGenerator());
164: }
165:
166:
173: public double getBoxWidth() {
174: return this.boxWidth;
175: }
176:
177:
188: public void setBoxWidth(double width) {
189: if (width != this.boxWidth) {
190: this.boxWidth = width;
191: fireChangeEvent();
192: }
193: }
194:
195:
202: public Paint getBoxPaint() {
203: return this.boxPaint;
204: }
205:
206:
214: public void setBoxPaint(Paint paint) {
215: this.boxPaint = paint;
216: fireChangeEvent();
217: }
218:
219:
226: public boolean getFillBox() {
227: return this.fillBox;
228: }
229:
230:
238: public void setFillBox(boolean flag) {
239: this.fillBox = flag;
240: fireChangeEvent();
241: }
242:
243:
251: public Paint getArtifactPaint() {
252: return this.artifactPaint;
253: }
254:
255:
264: public void setArtifactPaint(Paint paint) {
265: if (paint == null) {
266: throw new IllegalArgumentException("Null 'paint' argument.");
267: }
268: this.artifactPaint = paint;
269: fireChangeEvent();
270: }
271:
272:
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:
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:
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:
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:
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:
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:
437: g2.setPaint(getArtifactPaint());
438: g2.draw(new Line2D.Double(yyMedian,
439: xx - width / 2, yyMedian, xx + width / 2));
440:
441:
442: if (yAverage != null) {
443: double aRadius = width / 4;
444:
445:
446: if ((yyAverage > (dataArea.getMinX() - aRadius))
447: && (yyAverage < (dataArea.getMaxX() + aRadius))) {
448: Ellipse2D.Double avgEllipse = new Ellipse2D.Double(
449: yyAverage - aRadius, xx - aRadius, aRadius * 2,
450: aRadius * 2);
451: g2.fill(avgEllipse);
452: g2.draw(avgEllipse);
453: }
454: }
455:
456:
457:
458:
459: if (entities != null && box.intersects(dataArea)) {
460: addEntity(entities, box, dataset, series, item, yyAverage, xx);
461: }
462:
463: }
464:
465:
482: public void drawVerticalItem(Graphics2D g2,
483: Rectangle2D dataArea,
484: PlotRenderingInfo info,
485: XYPlot plot,
486: ValueAxis domainAxis,
487: ValueAxis rangeAxis,
488: XYDataset dataset,
489: int series,
490: int item,
491: CrosshairState crosshairState,
492: int pass) {
493:
494:
495: EntityCollection entities = null;
496: if (info != null) {
497: entities = info.getOwner().getEntityCollection();
498: }
499:
500: BoxAndWhiskerXYDataset boxAndWhiskerData
501: = (BoxAndWhiskerXYDataset) dataset;
502:
503: Number x = boxAndWhiskerData.getX(series, item);
504: Number yMax = boxAndWhiskerData.getMaxRegularValue(series, item);
505: Number yMin = boxAndWhiskerData.getMinRegularValue(series, item);
506: Number yMedian = boxAndWhiskerData.getMedianValue(series, item);
507: Number yAverage = boxAndWhiskerData.getMeanValue(series, item);
508: Number yQ1Median = boxAndWhiskerData.getQ1Value(series, item);
509: Number yQ3Median = boxAndWhiskerData.getQ3Value(series, item);
510: List yOutliers = boxAndWhiskerData.getOutliers(series, item);
511:
512: double xx = domainAxis.valueToJava2D(x.doubleValue(), dataArea,
513: plot.getDomainAxisEdge());
514:
515: RectangleEdge location = plot.getRangeAxisEdge();
516: double yyMax = rangeAxis.valueToJava2D(yMax.doubleValue(), dataArea,
517: location);
518: double yyMin = rangeAxis.valueToJava2D(yMin.doubleValue(), dataArea,
519: location);
520: double yyMedian = rangeAxis.valueToJava2D(yMedian.doubleValue(),
521: dataArea, location);
522: double yyAverage = 0.0;
523: if (yAverage != null) {
524: yyAverage = rangeAxis.valueToJava2D(yAverage.doubleValue(),
525: dataArea, location);
526: }
527: double yyQ1Median = rangeAxis.valueToJava2D(yQ1Median.doubleValue(),
528: dataArea, location);
529: double yyQ3Median = rangeAxis.valueToJava2D(yQ3Median.doubleValue(),
530: dataArea, location);
531: double yyOutlier;
532:
533:
534: double exactBoxWidth = getBoxWidth();
535: double width = exactBoxWidth;
536: double dataAreaX = dataArea.getMaxX() - dataArea.getMinX();
537: double maxBoxPercent = 0.1;
538: double maxBoxWidth = dataAreaX * maxBoxPercent;
539: if (exactBoxWidth <= 0.0) {
540: int itemCount = boxAndWhiskerData.getItemCount(series);
541: exactBoxWidth = dataAreaX / itemCount * 4.5 / 7;
542: if (exactBoxWidth < 3) {
543: width = 3;
544: }
545: else if (exactBoxWidth > maxBoxWidth) {
546: width = maxBoxWidth;
547: }
548: else {
549: width = exactBoxWidth;
550: }
551: }
552:
553: Paint p = getBoxPaint();
554: if (p != null) {
555: g2.setPaint(p);
556: }
557: Stroke s = getItemStroke(series, item);
558:
559: g2.setStroke(s);
560:
561:
562: g2.draw(new Line2D.Double(xx, yyMax, xx, yyQ3Median));
563: g2.draw(new Line2D.Double(xx - width / 2, yyMax, xx + width / 2,
564: yyMax));
565:
566:
567: g2.draw(new Line2D.Double(xx, yyMin, xx, yyQ1Median));
568: g2.draw(new Line2D.Double(xx - width / 2, yyMin, xx + width / 2,
569: yyMin));
570:
571:
572: Shape box = null;
573: if (yyQ1Median > yyQ3Median) {
574: box = new Rectangle2D.Double(xx - width / 2, yyQ3Median, width,
575: yyQ1Median - yyQ3Median);
576: }
577: else {
578: box = new Rectangle2D.Double(xx - width / 2, yyQ1Median, width,
579: yyQ3Median - yyQ1Median);
580: }
581: if (this.fillBox) {
582: g2.fill(box);
583: }
584: g2.draw(box);
585:
586:
587: g2.setPaint(getArtifactPaint());
588: g2.draw(new Line2D.Double(xx - width / 2, yyMedian, xx + width / 2,
589: yyMedian));
590:
591: double aRadius = 0;
592: double oRadius = width / 3;
593:
594:
595: if (yAverage != null) {
596: aRadius = width / 4;
597:
598:
599: if ((yyAverage > (dataArea.getMinY() - aRadius))
600: && (yyAverage < (dataArea.getMaxY() + aRadius))) {
601: Ellipse2D.Double avgEllipse = new Ellipse2D.Double(xx - aRadius,
602: yyAverage - aRadius, aRadius * 2, aRadius * 2);
603: g2.fill(avgEllipse);
604: g2.draw(avgEllipse);
605: }
606: }
607:
608: List outliers = new ArrayList();
609: OutlierListCollection outlierListCollection
610: = new OutlierListCollection();
611:
612:
616:
617: for (int i = 0; i < yOutliers.size(); i++) {
618: double outlier = ((Number) yOutliers.get(i)).doubleValue();
619: if (outlier > boxAndWhiskerData.getMaxOutlier(series,
620: item).doubleValue()) {
621: outlierListCollection.setHighFarOut(true);
622: }
623: else if (outlier < boxAndWhiskerData.getMinOutlier(series,
624: item).doubleValue()) {
625: outlierListCollection.setLowFarOut(true);
626: }
627: else if (outlier > boxAndWhiskerData.getMaxRegularValue(series,
628: item).doubleValue()) {
629: yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea,
630: location);
631: outliers.add(new Outlier(xx, yyOutlier, oRadius));
632: }
633: else if (outlier < boxAndWhiskerData.getMinRegularValue(series,
634: item).doubleValue()) {
635: yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea,
636: location);
637: outliers.add(new Outlier(xx, yyOutlier, oRadius));
638: }
639: Collections.sort(outliers);
640: }
641:
642:
643:
644: for (Iterator iterator = outliers.iterator(); iterator.hasNext();) {
645: Outlier outlier = (Outlier) iterator.next();
646: outlierListCollection.add(outlier);
647: }
648:
649:
650: double maxAxisValue = rangeAxis.valueToJava2D(rangeAxis.getUpperBound(),
651: dataArea, location) + aRadius;
652: double minAxisValue = rangeAxis.valueToJava2D(rangeAxis.getLowerBound(),
653: dataArea, location) - aRadius;
654:
655:
656: for (Iterator iterator = outlierListCollection.iterator();
657: iterator.hasNext();) {
658: OutlierList list = (OutlierList) iterator.next();
659: Outlier outlier = list.getAveragedOutlier();
660: Point2D point = outlier.getPoint();
661:
662: if (list.isMultiple()) {
663: drawMultipleEllipse(point, width, oRadius, g2);
664: }
665: else {
666: drawEllipse(point, oRadius, g2);
667: }
668: }
669:
670:
671: if (outlierListCollection.isHighFarOut()) {
672: drawHighFarOut(aRadius, g2, xx, maxAxisValue);
673: }
674:
675: if (outlierListCollection.isLowFarOut()) {
676: drawLowFarOut(aRadius, g2, xx, minAxisValue);
677: }
678:
679:
680: if (entities != null && box.intersects(dataArea)) {
681: addEntity(entities, box, dataset, series, item, xx, yyAverage);
682: }
683:
684: }
685:
686:
693: protected void drawEllipse(Point2D point, double oRadius, Graphics2D g2) {
694: Ellipse2D.Double dot = new Ellipse2D.Double(point.getX() + oRadius / 2,
695: point.getY(), oRadius, oRadius);
696: g2.draw(dot);
697: }
698:
699:
707: protected void drawMultipleEllipse(Point2D point, double boxWidth,
708: double oRadius, Graphics2D g2) {
709:
710: Ellipse2D.Double dot1 = new Ellipse2D.Double(point.getX()
711: - (boxWidth / 2) + oRadius, point.getY(), oRadius, oRadius);
712: Ellipse2D.Double dot2 = new Ellipse2D.Double(point.getX()
713: + (boxWidth / 2), point.getY(), oRadius, oRadius);
714: g2.draw(dot1);
715: g2.draw(dot2);
716:
717: }
718:
719:
727: protected void drawHighFarOut(double aRadius, Graphics2D g2, double xx,
728: double m) {
729: double side = aRadius * 2;
730: g2.draw(new Line2D.Double(xx - side, m + side, xx + side, m + side));
731: g2.draw(new Line2D.Double(xx - side, m + side, xx, m));
732: g2.draw(new Line2D.Double(xx + side, m + side, xx, m));
733: }
734:
735:
743: protected void drawLowFarOut(double aRadius, Graphics2D g2, double xx,
744: double m) {
745: double side = aRadius * 2;
746: g2.draw(new Line2D.Double(xx - side, m - side, xx + side, m - side));
747: g2.draw(new Line2D.Double(xx - side, m - side, xx, m));
748: g2.draw(new Line2D.Double(xx + side, m - side, xx, m));
749: }
750:
751:
758: public boolean equals(Object obj) {
759: if (obj == this) {
760: return true;
761: }
762: if (!(obj instanceof XYBoxAndWhiskerRenderer)) {
763: return false;
764: }
765: if (!super.equals(obj)) {
766: return false;
767: }
768: XYBoxAndWhiskerRenderer that = (XYBoxAndWhiskerRenderer) obj;
769: if (this.boxWidth != that.getBoxWidth()) {
770: return false;
771: }
772: if (!PaintUtilities.equal(this.boxPaint, that.boxPaint)) {
773: return false;
774: }
775: if (!PaintUtilities.equal(this.artifactPaint, that.artifactPaint)) {
776: return false;
777: }
778: if (this.fillBox != that.fillBox) {
779: return false;
780: }
781: return true;
782:
783: }
784:
785:
792: private void writeObject(ObjectOutputStream stream) throws IOException {
793: stream.defaultWriteObject();
794: SerialUtilities.writePaint(this.boxPaint, stream);
795: SerialUtilities.writePaint(this.artifactPaint, stream);
796: }
797:
798:
806: private void readObject(ObjectInputStream stream)
807: throws IOException, ClassNotFoundException {
808:
809: stream.defaultReadObject();
810: this.boxPaint = SerialUtilities.readPaint(stream);
811: this.artifactPaint = SerialUtilities.readPaint(stream);
812: }
813:
814:
821: public Object clone() throws CloneNotSupportedException {
822: return super.clone();
823: }
824:
825: }