1:
75:
76: package ;
77:
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: import ;
95:
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: import ;
113:
114:
120: public class XYBoxAndWhiskerRenderer extends AbstractXYItemRenderer
121: implements XYItemRenderer,
122: Cloneable,
123: PublicCloneable,
124: Serializable {
125:
126:
127: private static final long serialVersionUID = -8020170108532232324L;
128:
129:
130: private double boxWidth;
131:
132:
133: private transient Paint boxPaint;
134:
135:
136: private boolean fillBox;
137:
138:
142: private transient Paint artifactPaint = Color.black;
143:
144:
147: public XYBoxAndWhiskerRenderer() {
148: this(-1.0);
149: }
150:
151:
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:
174: public double getBoxWidth() {
175: return this.boxWidth;
176: }
177:
178:
189: public void setBoxWidth(double width) {
190: if (width != this.boxWidth) {
191: this.boxWidth = width;
192: fireChangeEvent();
193: }
194: }
195:
196:
203: public Paint getBoxPaint() {
204: return this.boxPaint;
205: }
206:
207:
215: public void setBoxPaint(Paint paint) {
216: this.boxPaint = paint;
217: fireChangeEvent();
218: }
219:
220:
227: public boolean getFillBox() {
228: return this.fillBox;
229: }
230:
231:
239: public void setFillBox(boolean flag) {
240: this.fillBox = flag;
241: fireChangeEvent();
242: }
243:
244:
252: public Paint getArtifactPaint() {
253: return this.artifactPaint;
254: }
255:
256:
265: public void setArtifactPaint(Paint paint) {
266: if (paint == null) {
267: throw new IllegalArgumentException("Null 'paint' argument.");
268: }
269: this.artifactPaint = paint;
270: fireChangeEvent();
271: }
272:
273:
285: protected Paint lookupBoxPaint(int series, int item) {
286: Paint p = getBoxPaint();
287: if (p != null) {
288: return p;
289: }
290: else {
291:
292:
293: return getItemPaint(series, item);
294: }
295: }
296:
297:
316: public void drawItem(Graphics2D g2,
317: XYItemRendererState state,
318: Rectangle2D dataArea,
319: PlotRenderingInfo info,
320: XYPlot plot,
321: ValueAxis domainAxis,
322: ValueAxis rangeAxis,
323: XYDataset dataset,
324: int series,
325: int item,
326: CrosshairState crosshairState,
327: int pass) {
328:
329: PlotOrientation orientation = plot.getOrientation();
330:
331: if (orientation == PlotOrientation.HORIZONTAL) {
332: drawHorizontalItem(g2, dataArea, info, plot, domainAxis, rangeAxis,
333: dataset, series, item, crosshairState, pass);
334: }
335: else if (orientation == PlotOrientation.VERTICAL) {
336: drawVerticalItem(g2, dataArea, info, plot, domainAxis, rangeAxis,
337: dataset, series, item, crosshairState, pass);
338: }
339:
340: }
341:
342:
360: public void drawHorizontalItem(Graphics2D g2,
361: Rectangle2D dataArea,
362: PlotRenderingInfo info,
363: XYPlot plot,
364: ValueAxis domainAxis,
365: ValueAxis rangeAxis,
366: XYDataset dataset,
367: int series,
368: int item,
369: CrosshairState crosshairState,
370: int pass) {
371:
372:
373: EntityCollection entities = null;
374: if (info != null) {
375: entities = info.getOwner().getEntityCollection();
376: }
377:
378: BoxAndWhiskerXYDataset boxAndWhiskerData
379: = (BoxAndWhiskerXYDataset) dataset;
380:
381: Number x = boxAndWhiskerData.getX(series, item);
382: Number yMax = boxAndWhiskerData.getMaxRegularValue(series, item);
383: Number yMin = boxAndWhiskerData.getMinRegularValue(series, item);
384: Number yMedian = boxAndWhiskerData.getMedianValue(series, item);
385: Number yAverage = boxAndWhiskerData.getMeanValue(series, item);
386: Number yQ1Median = boxAndWhiskerData.getQ1Value(series, item);
387: Number yQ3Median = boxAndWhiskerData.getQ3Value(series, item);
388:
389: double xx = domainAxis.valueToJava2D(x.doubleValue(), dataArea,
390: plot.getDomainAxisEdge());
391:
392: RectangleEdge location = plot.getRangeAxisEdge();
393: double yyMax = rangeAxis.valueToJava2D(yMax.doubleValue(), dataArea,
394: location);
395: double yyMin = rangeAxis.valueToJava2D(yMin.doubleValue(), dataArea,
396: location);
397: double yyMedian = rangeAxis.valueToJava2D(yMedian.doubleValue(),
398: dataArea, location);
399: double yyAverage = 0.0;
400: if (yAverage != null) {
401: yyAverage = rangeAxis.valueToJava2D(yAverage.doubleValue(),
402: dataArea, location);
403: }
404: double yyQ1Median = rangeAxis.valueToJava2D(yQ1Median.doubleValue(),
405: dataArea, location);
406: double yyQ3Median = rangeAxis.valueToJava2D(yQ3Median.doubleValue(),
407: dataArea, location);
408:
409: double exactBoxWidth = getBoxWidth();
410: double width = exactBoxWidth;
411: double dataAreaX = dataArea.getHeight();
412: double maxBoxPercent = 0.1;
413: double maxBoxWidth = dataAreaX * maxBoxPercent;
414: if (exactBoxWidth <= 0.0) {
415: int itemCount = boxAndWhiskerData.getItemCount(series);
416: exactBoxWidth = dataAreaX / itemCount * 4.5 / 7;
417: if (exactBoxWidth < 3) {
418: width = 3;
419: }
420: else if (exactBoxWidth > maxBoxWidth) {
421: width = maxBoxWidth;
422: }
423: else {
424: width = exactBoxWidth;
425: }
426: }
427:
428: g2.setPaint(getItemPaint(series, item));
429: Stroke s = getItemStroke(series, item);
430: g2.setStroke(s);
431:
432:
433: g2.draw(new Line2D.Double(yyMax, xx, yyQ3Median, xx));
434: g2.draw(new Line2D.Double(yyMax, xx - width / 2, yyMax,
435: xx + width / 2));
436:
437:
438: g2.draw(new Line2D.Double(yyMin, xx, yyQ1Median, xx));
439: g2.draw(new Line2D.Double(yyMin, xx - width / 2, yyMin,
440: xx + width / 2));
441:
442:
443: Shape box = null;
444: if (yyQ1Median < yyQ3Median) {
445: box = new Rectangle2D.Double(yyQ1Median, xx - width / 2,
446: yyQ3Median - yyQ1Median, width);
447: }
448: else {
449: box = new Rectangle2D.Double(yyQ3Median, xx - width / 2,
450: yyQ1Median - yyQ3Median, width);
451: }
452: if (this.fillBox) {
453: g2.setPaint(lookupBoxPaint(series, item));
454: g2.fill(box);
455: }
456: g2.setStroke(getItemOutlineStroke(series, item));
457: g2.setPaint(getItemOutlinePaint(series, item));
458: g2.draw(box);
459:
460:
461: g2.setPaint(getArtifactPaint());
462: g2.draw(new Line2D.Double(yyMedian,
463: xx - width / 2, yyMedian, xx + width / 2));
464:
465:
466: if (yAverage != null) {
467: double aRadius = width / 4;
468:
469:
470: if ((yyAverage > (dataArea.getMinX() - aRadius))
471: && (yyAverage < (dataArea.getMaxX() + aRadius))) {
472: Ellipse2D.Double avgEllipse = new Ellipse2D.Double(
473: yyAverage - aRadius, xx - aRadius, aRadius * 2,
474: aRadius * 2);
475: g2.fill(avgEllipse);
476: g2.draw(avgEllipse);
477: }
478: }
479:
480:
481:
482:
483: if (entities != null && box.intersects(dataArea)) {
484: addEntity(entities, box, dataset, series, item, yyAverage, xx);
485: }
486:
487: }
488:
489:
507: public void drawVerticalItem(Graphics2D g2,
508: Rectangle2D dataArea,
509: PlotRenderingInfo info,
510: XYPlot plot,
511: ValueAxis domainAxis,
512: ValueAxis rangeAxis,
513: XYDataset dataset,
514: int series,
515: int item,
516: CrosshairState crosshairState,
517: int pass) {
518:
519:
520: EntityCollection entities = null;
521: if (info != null) {
522: entities = info.getOwner().getEntityCollection();
523: }
524:
525: BoxAndWhiskerXYDataset boxAndWhiskerData
526: = (BoxAndWhiskerXYDataset) dataset;
527:
528: Number x = boxAndWhiskerData.getX(series, item);
529: Number yMax = boxAndWhiskerData.getMaxRegularValue(series, item);
530: Number yMin = boxAndWhiskerData.getMinRegularValue(series, item);
531: Number yMedian = boxAndWhiskerData.getMedianValue(series, item);
532: Number yAverage = boxAndWhiskerData.getMeanValue(series, item);
533: Number yQ1Median = boxAndWhiskerData.getQ1Value(series, item);
534: Number yQ3Median = boxAndWhiskerData.getQ3Value(series, item);
535: List yOutliers = boxAndWhiskerData.getOutliers(series, item);
536:
537: double xx = domainAxis.valueToJava2D(x.doubleValue(), dataArea,
538: plot.getDomainAxisEdge());
539:
540: RectangleEdge location = plot.getRangeAxisEdge();
541: double yyMax = rangeAxis.valueToJava2D(yMax.doubleValue(), dataArea,
542: location);
543: double yyMin = rangeAxis.valueToJava2D(yMin.doubleValue(), dataArea,
544: location);
545: double yyMedian = rangeAxis.valueToJava2D(yMedian.doubleValue(),
546: dataArea, location);
547: double yyAverage = 0.0;
548: if (yAverage != null) {
549: yyAverage = rangeAxis.valueToJava2D(yAverage.doubleValue(),
550: dataArea, location);
551: }
552: double yyQ1Median = rangeAxis.valueToJava2D(yQ1Median.doubleValue(),
553: dataArea, location);
554: double yyQ3Median = rangeAxis.valueToJava2D(yQ3Median.doubleValue(),
555: dataArea, location);
556: double yyOutlier;
557:
558:
559: double exactBoxWidth = getBoxWidth();
560: double width = exactBoxWidth;
561: double dataAreaX = dataArea.getMaxX() - dataArea.getMinX();
562: double maxBoxPercent = 0.1;
563: double maxBoxWidth = dataAreaX * maxBoxPercent;
564: if (exactBoxWidth <= 0.0) {
565: int itemCount = boxAndWhiskerData.getItemCount(series);
566: exactBoxWidth = dataAreaX / itemCount * 4.5 / 7;
567: if (exactBoxWidth < 3) {
568: width = 3;
569: }
570: else if (exactBoxWidth > maxBoxWidth) {
571: width = maxBoxWidth;
572: }
573: else {
574: width = exactBoxWidth;
575: }
576: }
577:
578: g2.setPaint(getItemPaint(series, item));
579: Stroke s = getItemStroke(series, item);
580: g2.setStroke(s);
581:
582:
583: g2.draw(new Line2D.Double(xx, yyMax, xx, yyQ3Median));
584: g2.draw(new Line2D.Double(xx - width / 2, yyMax, xx + width / 2,
585: yyMax));
586:
587:
588: g2.draw(new Line2D.Double(xx, yyMin, xx, yyQ1Median));
589: g2.draw(new Line2D.Double(xx - width / 2, yyMin, xx + width / 2,
590: yyMin));
591:
592:
593: Shape box = null;
594: if (yyQ1Median > yyQ3Median) {
595: box = new Rectangle2D.Double(xx - width / 2, yyQ3Median, width,
596: yyQ1Median - yyQ3Median);
597: }
598: else {
599: box = new Rectangle2D.Double(xx - width / 2, yyQ1Median, width,
600: yyQ3Median - yyQ1Median);
601: }
602: if (this.fillBox) {
603: g2.setPaint(lookupBoxPaint(series, item));
604: g2.fill(box);
605: }
606: g2.setStroke(getItemOutlineStroke(series, item));
607: g2.setPaint(getItemOutlinePaint(series, item));
608: g2.draw(box);
609:
610:
611: g2.setPaint(getArtifactPaint());
612: g2.draw(new Line2D.Double(xx - width / 2, yyMedian, xx + width / 2,
613: yyMedian));
614:
615: double aRadius = 0;
616: double oRadius = width / 3;
617:
618:
619: if (yAverage != null) {
620: aRadius = width / 4;
621:
622:
623: if ((yyAverage > (dataArea.getMinY() - aRadius))
624: && (yyAverage < (dataArea.getMaxY() + aRadius))) {
625: Ellipse2D.Double avgEllipse = new Ellipse2D.Double(xx - aRadius,
626: yyAverage - aRadius, aRadius * 2, aRadius * 2);
627: g2.fill(avgEllipse);
628: g2.draw(avgEllipse);
629: }
630: }
631:
632: List outliers = new ArrayList();
633: OutlierListCollection outlierListCollection
634: = new OutlierListCollection();
635:
636:
640:
641: for (int i = 0; i < yOutliers.size(); i++) {
642: double outlier = ((Number) yOutliers.get(i)).doubleValue();
643: if (outlier > boxAndWhiskerData.getMaxOutlier(series,
644: item).doubleValue()) {
645: outlierListCollection.setHighFarOut(true);
646: }
647: else if (outlier < boxAndWhiskerData.getMinOutlier(series,
648: item).doubleValue()) {
649: outlierListCollection.setLowFarOut(true);
650: }
651: else if (outlier > boxAndWhiskerData.getMaxRegularValue(series,
652: item).doubleValue()) {
653: yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea,
654: location);
655: outliers.add(new Outlier(xx, yyOutlier, oRadius));
656: }
657: else if (outlier < boxAndWhiskerData.getMinRegularValue(series,
658: item).doubleValue()) {
659: yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea,
660: location);
661: outliers.add(new Outlier(xx, yyOutlier, oRadius));
662: }
663: Collections.sort(outliers);
664: }
665:
666:
667:
668: for (Iterator iterator = outliers.iterator(); iterator.hasNext();) {
669: Outlier outlier = (Outlier) iterator.next();
670: outlierListCollection.add(outlier);
671: }
672:
673:
674: double maxAxisValue = rangeAxis.valueToJava2D(rangeAxis.getUpperBound(),
675: dataArea, location) + aRadius;
676: double minAxisValue = rangeAxis.valueToJava2D(rangeAxis.getLowerBound(),
677: dataArea, location) - aRadius;
678:
679:
680: for (Iterator iterator = outlierListCollection.iterator();
681: iterator.hasNext();) {
682: OutlierList list = (OutlierList) iterator.next();
683: Outlier outlier = list.getAveragedOutlier();
684: Point2D point = outlier.getPoint();
685:
686: if (list.isMultiple()) {
687: drawMultipleEllipse(point, width, oRadius, g2);
688: }
689: else {
690: drawEllipse(point, oRadius, g2);
691: }
692: }
693:
694:
695: if (outlierListCollection.isHighFarOut()) {
696: drawHighFarOut(aRadius, g2, xx, maxAxisValue);
697: }
698:
699: if (outlierListCollection.isLowFarOut()) {
700: drawLowFarOut(aRadius, g2, xx, minAxisValue);
701: }
702:
703:
704: if (entities != null && box.intersects(dataArea)) {
705: addEntity(entities, box, dataset, series, item, xx, yyAverage);
706: }
707:
708: }
709:
710:
717: protected void drawEllipse(Point2D point, double oRadius, Graphics2D g2) {
718: Ellipse2D.Double dot = new Ellipse2D.Double(point.getX() + oRadius / 2,
719: point.getY(), oRadius, oRadius);
720: g2.draw(dot);
721: }
722:
723:
731: protected void drawMultipleEllipse(Point2D point, double boxWidth,
732: double oRadius, Graphics2D g2) {
733:
734: Ellipse2D.Double dot1 = new Ellipse2D.Double(point.getX()
735: - (boxWidth / 2) + oRadius, point.getY(), oRadius, oRadius);
736: Ellipse2D.Double dot2 = new Ellipse2D.Double(point.getX()
737: + (boxWidth / 2), point.getY(), oRadius, oRadius);
738: g2.draw(dot1);
739: g2.draw(dot2);
740:
741: }
742:
743:
751: protected void drawHighFarOut(double aRadius, Graphics2D g2, double xx,
752: double m) {
753: double side = aRadius * 2;
754: g2.draw(new Line2D.Double(xx - side, m + side, xx + side, m + side));
755: g2.draw(new Line2D.Double(xx - side, m + side, xx, m));
756: g2.draw(new Line2D.Double(xx + side, m + side, xx, m));
757: }
758:
759:
767: protected void drawLowFarOut(double aRadius, Graphics2D g2, double xx,
768: double m) {
769: double side = aRadius * 2;
770: g2.draw(new Line2D.Double(xx - side, m - side, xx + side, m - side));
771: g2.draw(new Line2D.Double(xx - side, m - side, xx, m));
772: g2.draw(new Line2D.Double(xx + side, m - side, xx, m));
773: }
774:
775:
782: public boolean equals(Object obj) {
783: if (obj == this) {
784: return true;
785: }
786: if (!(obj instanceof XYBoxAndWhiskerRenderer)) {
787: return false;
788: }
789: if (!super.equals(obj)) {
790: return false;
791: }
792: XYBoxAndWhiskerRenderer that = (XYBoxAndWhiskerRenderer) obj;
793: if (this.boxWidth != that.getBoxWidth()) {
794: return false;
795: }
796: if (!PaintUtilities.equal(this.boxPaint, that.boxPaint)) {
797: return false;
798: }
799: if (!PaintUtilities.equal(this.artifactPaint, that.artifactPaint)) {
800: return false;
801: }
802: if (this.fillBox != that.fillBox) {
803: return false;
804: }
805: return true;
806:
807: }
808:
809:
816: private void writeObject(ObjectOutputStream stream) throws IOException {
817: stream.defaultWriteObject();
818: SerialUtilities.writePaint(this.boxPaint, stream);
819: SerialUtilities.writePaint(this.artifactPaint, stream);
820: }
821:
822:
830: private void readObject(ObjectInputStream stream)
831: throws IOException, ClassNotFoundException {
832:
833: stream.defaultReadObject();
834: this.boxPaint = SerialUtilities.readPaint(stream);
835: this.artifactPaint = SerialUtilities.readPaint(stream);
836: }
837:
838:
845: public Object clone() throws CloneNotSupportedException {
846: return super.clone();
847: }
848:
849: }