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: import ;
113: import ;
114:
115:
121: public class XYBoxAndWhiskerRenderer extends AbstractXYItemRenderer
122: implements XYItemRenderer,
123: Cloneable,
124: PublicCloneable,
125: Serializable {
126:
127:
128: private static final long serialVersionUID = -8020170108532232324L;
129:
130:
131: private double boxWidth;
132:
133:
134: private transient Paint boxPaint;
135:
136:
137: private boolean fillBox;
138:
139:
143: private transient Paint artifactPaint = Color.black;
144:
145:
148: public XYBoxAndWhiskerRenderer() {
149: this(-1.0);
150: }
151:
152:
160: public XYBoxAndWhiskerRenderer(double boxWidth) {
161: super();
162: this.boxWidth = boxWidth;
163: this.boxPaint = Color.green;
164: this.fillBox = true;
165: setToolTipGenerator(new BoxAndWhiskerXYToolTipGenerator());
166: }
167:
168:
173: public double getBoxWidth() {
174: return this.boxWidth;
175: }
176:
177:
186: public void setBoxWidth(double width) {
187: if (width != this.boxWidth) {
188: this.boxWidth = width;
189: notifyListeners(new RendererChangeEvent(this));
190: }
191: }
192:
193:
198: public Paint getBoxPaint() {
199: return this.boxPaint;
200: }
201:
202:
208: public void setBoxPaint(Paint paint) {
209: this.boxPaint = paint;
210: notifyListeners(new RendererChangeEvent(this));
211: }
212:
213:
218: public boolean getFillBox() {
219: return this.fillBox;
220: }
221:
222:
228: public void setFillBox(boolean flag) {
229: this.fillBox = flag;
230: notifyListeners(new RendererChangeEvent(this));
231: }
232:
233:
239: public Paint getArtifactPaint() {
240: return this.artifactPaint;
241: }
242:
243:
249: public void setArtifactPaint(Paint artifactPaint) {
250: if (artifactPaint == null) {
251: throw new IllegalArgumentException(
252: "Null 'artifactPaint' argument.");
253: }
254: this.artifactPaint = artifactPaint;
255: notifyListeners(new RendererChangeEvent(this));
256: }
257:
258:
276: public void drawItem(Graphics2D g2,
277: XYItemRendererState state,
278: Rectangle2D dataArea,
279: PlotRenderingInfo info,
280: XYPlot plot,
281: ValueAxis domainAxis,
282: ValueAxis rangeAxis,
283: XYDataset dataset,
284: int series,
285: int item,
286: CrosshairState crosshairState,
287: int pass) {
288:
289: PlotOrientation orientation = plot.getOrientation();
290:
291: if (orientation == PlotOrientation.HORIZONTAL) {
292: drawHorizontalItem(g2, dataArea, info, plot, domainAxis, rangeAxis,
293: dataset, series, item, crosshairState, pass);
294: }
295: else if (orientation == PlotOrientation.VERTICAL) {
296: drawVerticalItem(g2, dataArea, info, plot, domainAxis, rangeAxis,
297: dataset, series, item, crosshairState, pass);
298: }
299:
300: }
301:
302:
319: public void drawHorizontalItem(Graphics2D g2,
320: Rectangle2D dataArea,
321: PlotRenderingInfo info,
322: XYPlot plot,
323: ValueAxis domainAxis,
324: ValueAxis rangeAxis,
325: XYDataset dataset,
326: int series,
327: int item,
328: CrosshairState crosshairState,
329: int pass) {
330:
331:
332: EntityCollection entities = null;
333: if (info != null) {
334: entities = info.getOwner().getEntityCollection();
335: }
336:
337: BoxAndWhiskerXYDataset boxAndWhiskerData
338: = (BoxAndWhiskerXYDataset) dataset;
339:
340: Number x = boxAndWhiskerData.getX(series, item);
341: Number yMax = boxAndWhiskerData.getMaxRegularValue(series, item);
342: Number yMin = boxAndWhiskerData.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(), dataArea,
353: location);
354: double yyMin = rangeAxis.valueToJava2D(yMin.doubleValue(), dataArea,
355: location);
356: double yyMedian = rangeAxis.valueToJava2D(yMedian.doubleValue(),
357: 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.doubleValue(),
364: dataArea, location);
365: double yyQ3Median = rangeAxis.valueToJava2D(yQ3Median.doubleValue(),
366: 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: }
379: else if (exactBoxWidth > maxBoxWidth) {
380: width = maxBoxWidth;
381: }
382: else {
383: width = exactBoxWidth;
384: }
385: }
386:
387: Paint p = getBoxPaint();
388: if (p != null) {
389: g2.setPaint(p);
390: }
391: Stroke s = getItemStroke(series, item);
392: g2.setStroke(s);
393:
394:
395: g2.draw(new Line2D.Double(yyMax, xx, yyQ3Median, xx));
396: g2.draw(new Line2D.Double(yyMax, xx - width / 2, yyMax,
397: xx + width / 2));
398:
399:
400: g2.draw(new Line2D.Double(yyMin, xx, yyQ1Median, xx));
401: g2.draw(new Line2D.Double(yyMin, xx - width / 2, yyMin,
402: xx + width / 2));
403:
404:
405: Shape box = null;
406: if (yyQ1Median < yyQ3Median) {
407: box = new Rectangle2D.Double(yyQ1Median, xx - width / 2,
408: yyQ3Median - yyQ1Median, width);
409: }
410: else {
411: box = new Rectangle2D.Double(yyQ3Median, xx - width / 2,
412: yyQ1Median - yyQ3Median, width);
413: }
414: if (getBoxPaint() != null) {
415: g2.setPaint(getBoxPaint());
416: }
417: if (this.fillBox) {
418: g2.fill(box);
419: }
420: g2.draw(box);
421:
422:
423: g2.setPaint(getArtifactPaint());
424: g2.draw(new Line2D.Double(yyMedian,
425: xx - width / 2, yyMedian, xx + width / 2));
426:
427:
428: if (yAverage != null) {
429: double aRadius = width / 4;
430: Ellipse2D.Double avgEllipse = new Ellipse2D.Double(
431: yyAverage - aRadius, xx - aRadius, aRadius * 2,
432: aRadius * 2);
433: g2.fill(avgEllipse);
434: g2.draw(avgEllipse);
435: }
436:
437:
438:
439:
440: if (entities != null) {
441: String tip = null;
442: XYToolTipGenerator generator = getToolTipGenerator(series, item);
443: if (generator != null) {
444: tip = generator.generateToolTip(dataset, series, item);
445: }
446: String url = null;
447: if (getURLGenerator() != null) {
448: url = getURLGenerator().generateURL(dataset, series, item);
449: }
450: XYItemEntity entity = new XYItemEntity(box, dataset, series, item,
451: tip, url);
452: entities.add(entity);
453: }
454:
455: }
456:
457:
474: public void drawVerticalItem(Graphics2D g2,
475: Rectangle2D dataArea,
476: PlotRenderingInfo info,
477: XYPlot plot,
478: ValueAxis domainAxis,
479: ValueAxis rangeAxis,
480: XYDataset dataset,
481: int series,
482: int item,
483: CrosshairState crosshairState,
484: int pass) {
485:
486:
487: EntityCollection entities = null;
488: if (info != null) {
489: entities = info.getOwner().getEntityCollection();
490: }
491:
492: BoxAndWhiskerXYDataset boxAndWhiskerData
493: = (BoxAndWhiskerXYDataset) dataset;
494:
495: Number x = boxAndWhiskerData.getX(series, item);
496: Number yMax = boxAndWhiskerData.getMaxRegularValue(series, item);
497: Number yMin = boxAndWhiskerData.getMinRegularValue(series, item);
498: Number yMedian = boxAndWhiskerData.getMedianValue(series, item);
499: Number yAverage = boxAndWhiskerData.getMeanValue(series, item);
500: Number yQ1Median = boxAndWhiskerData.getQ1Value(series, item);
501: Number yQ3Median = boxAndWhiskerData.getQ3Value(series, item);
502: List yOutliers = boxAndWhiskerData.getOutliers(series, item);
503:
504: double xx = domainAxis.valueToJava2D(x.doubleValue(), dataArea,
505: plot.getDomainAxisEdge());
506:
507: RectangleEdge location = plot.getRangeAxisEdge();
508: double yyMax = rangeAxis.valueToJava2D(yMax.doubleValue(), dataArea,
509: location);
510: double yyMin = rangeAxis.valueToJava2D(yMin.doubleValue(), dataArea,
511: location);
512: double yyMedian = rangeAxis.valueToJava2D(yMedian.doubleValue(),
513: dataArea, location);
514: double yyAverage = 0.0;
515: if (yAverage != null) {
516: yyAverage = rangeAxis.valueToJava2D(yAverage.doubleValue(),
517: dataArea, location);
518: }
519: double yyQ1Median = rangeAxis.valueToJava2D(yQ1Median.doubleValue(),
520: dataArea, location);
521: double yyQ3Median = rangeAxis.valueToJava2D(yQ3Median.doubleValue(),
522: dataArea, location);
523: double yyOutlier;
524:
525:
526: double exactBoxWidth = getBoxWidth();
527: double width = exactBoxWidth;
528: double dataAreaX = dataArea.getMaxX() - dataArea.getMinX();
529: double maxBoxPercent = 0.1;
530: double maxBoxWidth = dataAreaX * maxBoxPercent;
531: if (exactBoxWidth <= 0.0) {
532: int itemCount = boxAndWhiskerData.getItemCount(series);
533: exactBoxWidth = dataAreaX / itemCount * 4.5 / 7;
534: if (exactBoxWidth < 3) {
535: width = 3;
536: }
537: else if (exactBoxWidth > maxBoxWidth) {
538: width = maxBoxWidth;
539: }
540: else {
541: width = exactBoxWidth;
542: }
543: }
544:
545: Paint p = getBoxPaint();
546: if (p != null) {
547: g2.setPaint(p);
548: }
549: Stroke s = getItemStroke(series, item);
550:
551: g2.setStroke(s);
552:
553:
554: g2.draw(new Line2D.Double(xx, yyMax, xx, yyQ3Median));
555: g2.draw(new Line2D.Double(xx - width / 2, yyMax, xx + width / 2,
556: yyMax));
557:
558:
559: g2.draw(new Line2D.Double(xx, yyMin, xx, yyQ1Median));
560: g2.draw(new Line2D.Double(xx - width / 2, yyMin, xx + width / 2,
561: yyMin));
562:
563:
564: Shape box = null;
565: if (yyQ1Median > yyQ3Median) {
566: box = new Rectangle2D.Double(xx - width / 2, yyQ3Median, width,
567: yyQ1Median - yyQ3Median);
568: }
569: else {
570: box = new Rectangle2D.Double(xx - width / 2, yyQ1Median, width,
571: yyQ3Median - yyQ1Median);
572: }
573: if (this.fillBox) {
574: g2.fill(box);
575: }
576: g2.draw(box);
577:
578:
579: g2.setPaint(getArtifactPaint());
580: g2.draw(new Line2D.Double(xx - width / 2, yyMedian, xx + width / 2,
581: yyMedian));
582:
583: double aRadius = 0;
584: double oRadius = width / 3;
585:
586:
587: if (yAverage != null) {
588: aRadius = width / 4;
589: Ellipse2D.Double avgEllipse = new Ellipse2D.Double(xx - aRadius,
590: yyAverage - aRadius, aRadius * 2, aRadius * 2);
591: g2.fill(avgEllipse);
592: g2.draw(avgEllipse);
593: }
594:
595: List outliers = new ArrayList();
596: OutlierListCollection outlierListCollection
597: = new OutlierListCollection();
598:
599:
603:
604: for (int i = 0; i < yOutliers.size(); i++) {
605: double outlier = ((Number) yOutliers.get(i)).doubleValue();
606: if (outlier > boxAndWhiskerData.getMaxOutlier(series,
607: item).doubleValue()) {
608: outlierListCollection.setHighFarOut(true);
609: }
610: else if (outlier < boxAndWhiskerData.getMinOutlier(series,
611: item).doubleValue()) {
612: outlierListCollection.setLowFarOut(true);
613: }
614: else if (outlier > boxAndWhiskerData.getMaxRegularValue(series,
615: item).doubleValue()) {
616: yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea,
617: location);
618: outliers.add(new Outlier(xx, yyOutlier, oRadius));
619: }
620: else if (outlier < boxAndWhiskerData.getMinRegularValue(series,
621: item).doubleValue()) {
622: yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea,
623: location);
624: outliers.add(new Outlier(xx, yyOutlier, oRadius));
625: }
626: Collections.sort(outliers);
627: }
628:
629:
630:
631: for (Iterator iterator = outliers.iterator(); iterator.hasNext();) {
632: Outlier outlier = (Outlier) iterator.next();
633: outlierListCollection.add(outlier);
634: }
635:
636:
637: double maxAxisValue = rangeAxis.valueToJava2D(
638: rangeAxis.getUpperBound(), dataArea, location
639: ) + aRadius;
640: double minAxisValue = rangeAxis.valueToJava2D(
641: rangeAxis.getLowerBound(), dataArea, location
642: ) - aRadius;
643:
644:
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:
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:
669: if (entities != null) {
670: String tip = null;
671: XYToolTipGenerator generator = getToolTipGenerator(series, item);
672: if (generator != null) {
673: tip = generator.generateToolTip(dataset, series, item);
674: }
675: String url = null;
676: if (getURLGenerator() != null) {
677: url = getURLGenerator().generateURL(dataset, series, item);
678: }
679: XYItemEntity entity = new XYItemEntity(box, dataset, series, item,
680: tip, url);
681: entities.add(entity);
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: }