1:
66:
67: package ;
68:
69: import ;
70: import ;
71: import ;
72: import ;
73: import ;
74: import ;
75: import ;
76: import ;
77: import ;
78: import ;
79: import ;
80: import ;
81: import ;
82: import ;
83: import ;
84: import ;
85: import ;
86:
87: import ;
88: import ;
89: import ;
90: import ;
91: import ;
92: import ;
93: import ;
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:
107:
112: public class BoxAndWhiskerRenderer extends AbstractCategoryItemRenderer
113: implements Cloneable, PublicCloneable,
114: Serializable {
115:
116:
117: private static final long serialVersionUID = 632027470694481177L;
118:
119:
120: private transient Paint artifactPaint;
121:
122:
123: private boolean fillBox;
124:
125:
126: private double itemMargin;
127:
128:
131: public BoxAndWhiskerRenderer() {
132: this.artifactPaint = Color.black;
133: this.fillBox = true;
134: this.itemMargin = 0.20;
135: }
136:
137:
145: public Paint getArtifactPaint() {
146: return this.artifactPaint;
147: }
148:
149:
157: public void setArtifactPaint(Paint paint) {
158: if (paint == null) {
159: throw new IllegalArgumentException("Null 'paint' argument.");
160: }
161: this.artifactPaint = paint;
162: notifyListeners(new RendererChangeEvent(this));
163: }
164:
165:
172: public boolean getFillBox() {
173: return this.fillBox;
174: }
175:
176:
184: public void setFillBox(boolean flag) {
185: this.fillBox = flag;
186: notifyListeners(new RendererChangeEvent(this));
187: }
188:
189:
197: public double getItemMargin() {
198: return this.itemMargin;
199: }
200:
201:
209: public void setItemMargin(double margin) {
210: this.itemMargin = margin;
211: notifyListeners(new RendererChangeEvent(this));
212: }
213:
214:
222: public LegendItem getLegendItem(int datasetIndex, int series) {
223:
224: CategoryPlot cp = getPlot();
225: if (cp == null) {
226: return null;
227: }
228:
229: CategoryDataset dataset;
230: dataset = cp.getDataset(datasetIndex);
231: String label = getLegendItemLabelGenerator().generateLabel(dataset,
232: series);
233: String description = label;
234: String toolTipText = null;
235: if (getLegendItemToolTipGenerator() != null) {
236: toolTipText = getLegendItemToolTipGenerator().generateLabel(
237: dataset, series);
238: }
239: String urlText = null;
240: if (getLegendItemURLGenerator() != null) {
241: urlText = getLegendItemURLGenerator().generateLabel(dataset,
242: series);
243: }
244: Shape shape = new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0);
245: Paint paint = getSeriesPaint(series);
246: Paint outlinePaint = getSeriesOutlinePaint(series);
247: Stroke outlineStroke = getSeriesOutlineStroke(series);
248:
249: return new LegendItem(label, description, toolTipText, urlText,
250: shape, paint, outlineStroke, outlinePaint);
251:
252: }
253:
254:
266: public CategoryItemRendererState initialise(Graphics2D g2,
267: Rectangle2D dataArea,
268: CategoryPlot plot,
269: int rendererIndex,
270: PlotRenderingInfo info) {
271:
272: CategoryItemRendererState state = super.initialise(g2, dataArea, plot,
273: rendererIndex, info);
274:
275:
276: CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
277: CategoryDataset dataset = plot.getDataset(rendererIndex);
278: if (dataset != null) {
279: int columns = dataset.getColumnCount();
280: int rows = dataset.getRowCount();
281: double space = 0.0;
282: PlotOrientation orientation = plot.getOrientation();
283: if (orientation == PlotOrientation.HORIZONTAL) {
284: space = dataArea.getHeight();
285: }
286: else if (orientation == PlotOrientation.VERTICAL) {
287: space = dataArea.getWidth();
288: }
289: double categoryMargin = 0.0;
290: double currentItemMargin = 0.0;
291: if (columns > 1) {
292: categoryMargin = domainAxis.getCategoryMargin();
293: }
294: if (rows > 1) {
295: currentItemMargin = getItemMargin();
296: }
297: double used = space * (1 - domainAxis.getLowerMargin()
298: - domainAxis.getUpperMargin()
299: - categoryMargin - currentItemMargin);
300: if ((rows * columns) > 0) {
301: state.setBarWidth(used / (dataset.getColumnCount()
302: * dataset.getRowCount()));
303: }
304: else {
305: state.setBarWidth(used);
306: }
307: }
308:
309: return state;
310:
311: }
312:
313:
327: public void drawItem(Graphics2D g2,
328: CategoryItemRendererState state,
329: Rectangle2D dataArea,
330: CategoryPlot plot,
331: CategoryAxis domainAxis,
332: ValueAxis rangeAxis,
333: CategoryDataset dataset,
334: int row,
335: int column,
336: int pass) {
337:
338: if (!(dataset instanceof BoxAndWhiskerCategoryDataset)) {
339: throw new IllegalArgumentException(
340: "BoxAndWhiskerRenderer.drawItem() : the data should be "
341: + "of type BoxAndWhiskerCategoryDataset only.");
342: }
343:
344: PlotOrientation orientation = plot.getOrientation();
345:
346: if (orientation == PlotOrientation.HORIZONTAL) {
347: drawHorizontalItem(g2, state, dataArea, plot, domainAxis,
348: rangeAxis, dataset, row, column);
349: }
350: else if (orientation == PlotOrientation.VERTICAL) {
351: drawVerticalItem(g2, state, dataArea, plot, domainAxis,
352: rangeAxis, dataset, row, column);
353: }
354:
355: }
356:
357:
372: public void drawHorizontalItem(Graphics2D g2,
373: CategoryItemRendererState state,
374: Rectangle2D dataArea,
375: CategoryPlot plot,
376: CategoryAxis domainAxis,
377: ValueAxis rangeAxis,
378: CategoryDataset dataset,
379: int row,
380: int column) {
381:
382: BoxAndWhiskerCategoryDataset bawDataset
383: = (BoxAndWhiskerCategoryDataset) dataset;
384:
385: double categoryEnd = domainAxis.getCategoryEnd(column,
386: getColumnCount(), dataArea, plot.getDomainAxisEdge());
387: double categoryStart = domainAxis.getCategoryStart(column,
388: getColumnCount(), dataArea, plot.getDomainAxisEdge());
389: double categoryWidth = Math.abs(categoryEnd - categoryStart);
390:
391: double yy = categoryStart;
392: int seriesCount = getRowCount();
393: int categoryCount = getColumnCount();
394:
395: if (seriesCount > 1) {
396: double seriesGap = dataArea.getWidth() * getItemMargin()
397: / (categoryCount * (seriesCount - 1));
398: double usedWidth = (state.getBarWidth() * seriesCount)
399: + (seriesGap * (seriesCount - 1));
400:
401:
402: double offset = (categoryWidth - usedWidth) / 2;
403: yy = yy + offset + (row * (state.getBarWidth() + seriesGap));
404: }
405: else {
406:
407:
408: double offset = (categoryWidth - state.getBarWidth()) / 2;
409: yy = yy + offset;
410: }
411:
412: Paint p = getItemPaint(row, column);
413: if (p != null) {
414: g2.setPaint(p);
415: }
416: Stroke s = getItemStroke(row, column);
417: g2.setStroke(s);
418:
419: RectangleEdge location = plot.getRangeAxisEdge();
420:
421: Number xQ1 = bawDataset.getQ1Value(row, column);
422: Number xQ3 = bawDataset.getQ3Value(row, column);
423: Number xMax = bawDataset.getMaxRegularValue(row, column);
424: Number xMin = bawDataset.getMinRegularValue(row, column);
425:
426: Shape box = null;
427: if (xQ1 != null && xQ3 != null && xMax != null && xMin != null) {
428:
429: double xxQ1 = rangeAxis.valueToJava2D(xQ1.doubleValue(), dataArea,
430: location);
431: double xxQ3 = rangeAxis.valueToJava2D(xQ3.doubleValue(), dataArea,
432: location);
433: double xxMax = rangeAxis.valueToJava2D(xMax.doubleValue(), dataArea,
434: location);
435: double xxMin = rangeAxis.valueToJava2D(xMin.doubleValue(), dataArea,
436: location);
437: double yymid = yy + state.getBarWidth() / 2.0;
438:
439:
440: g2.draw(new Line2D.Double(xxMax, yymid, xxQ3, yymid));
441: g2.draw(new Line2D.Double(xxMax, yy, xxMax,
442: yy + state.getBarWidth()));
443:
444:
445: g2.draw(new Line2D.Double(xxMin, yymid, xxQ1, yymid));
446: g2.draw(new Line2D.Double(xxMin, yy, xxMin,
447: yy + state.getBarWidth()));
448:
449:
450: box = new Rectangle2D.Double(Math.min(xxQ1, xxQ3), yy,
451: Math.abs(xxQ1 - xxQ3), state.getBarWidth());
452: if (this.fillBox) {
453: g2.fill(box);
454: }
455: g2.draw(box);
456:
457: }
458:
459: g2.setPaint(this.artifactPaint);
460: double aRadius = 0;
461:
462:
463: Number xMean = bawDataset.getMeanValue(row, column);
464: if (xMean != null) {
465: double xxMean = rangeAxis.valueToJava2D(xMean.doubleValue(),
466: dataArea, location);
467: aRadius = state.getBarWidth() / 4;
468: Ellipse2D.Double avgEllipse = new Ellipse2D.Double(xxMean
469: - aRadius, yy + aRadius, aRadius * 2, aRadius * 2);
470: g2.fill(avgEllipse);
471: g2.draw(avgEllipse);
472: }
473:
474:
475: Number xMedian = bawDataset.getMedianValue(row, column);
476: if (xMedian != null) {
477: double xxMedian = rangeAxis.valueToJava2D(xMedian.doubleValue(),
478: dataArea, location);
479: g2.draw(new Line2D.Double(xxMedian, yy, xxMedian,
480: yy + state.getBarWidth()));
481: }
482:
483:
484: if (state.getInfo() != null && box != null) {
485: EntityCollection entities = state.getEntityCollection();
486: if (entities != null) {
487: String tip = null;
488: CategoryToolTipGenerator tipster
489: = getToolTipGenerator(row, column);
490: if (tipster != null) {
491: tip = tipster.generateToolTip(dataset, row, column);
492: }
493: String url = null;
494: if (getItemURLGenerator(row, column) != null) {
495: url = getItemURLGenerator(row, column).generateURL(
496: dataset, row, column);
497: }
498: CategoryItemEntity entity = new CategoryItemEntity(box, tip,
499: url, dataset, row, dataset.getColumnKey(column),
500: column);
501: entities.add(entity);
502: }
503: }
504:
505: }
506:
507:
522: public void drawVerticalItem(Graphics2D g2,
523: CategoryItemRendererState state,
524: Rectangle2D dataArea,
525: CategoryPlot plot,
526: CategoryAxis domainAxis,
527: ValueAxis rangeAxis,
528: CategoryDataset dataset,
529: int row,
530: int column) {
531:
532: BoxAndWhiskerCategoryDataset bawDataset
533: = (BoxAndWhiskerCategoryDataset) dataset;
534:
535: double categoryEnd = domainAxis.getCategoryEnd(column,
536: getColumnCount(), dataArea, plot.getDomainAxisEdge());
537: double categoryStart = domainAxis.getCategoryStart(column,
538: getColumnCount(), dataArea, plot.getDomainAxisEdge());
539: double categoryWidth = categoryEnd - categoryStart;
540:
541: double xx = categoryStart;
542: int seriesCount = getRowCount();
543: int categoryCount = getColumnCount();
544:
545: if (seriesCount > 1) {
546: double seriesGap = dataArea.getWidth() * getItemMargin()
547: / (categoryCount * (seriesCount - 1));
548: double usedWidth = (state.getBarWidth() * seriesCount)
549: + (seriesGap * (seriesCount - 1));
550:
551:
552: double offset = (categoryWidth - usedWidth) / 2;
553: xx = xx + offset + (row * (state.getBarWidth() + seriesGap));
554: }
555: else {
556:
557:
558: double offset = (categoryWidth - state.getBarWidth()) / 2;
559: xx = xx + offset;
560: }
561:
562: double yyAverage = 0.0;
563: double yyOutlier;
564:
565: Paint p = getItemPaint(row, column);
566: if (p != null) {
567: g2.setPaint(p);
568: }
569: Stroke s = getItemStroke(row, column);
570: g2.setStroke(s);
571:
572: double aRadius = 0;
573:
574: RectangleEdge location = plot.getRangeAxisEdge();
575:
576: Number yQ1 = bawDataset.getQ1Value(row, column);
577: Number yQ3 = bawDataset.getQ3Value(row, column);
578: Number yMax = bawDataset.getMaxRegularValue(row, column);
579: Number yMin = bawDataset.getMinRegularValue(row, column);
580: Shape box = null;
581: if (yQ1 != null && yQ3 != null && yMax != null && yMin != null) {
582:
583: double yyQ1 = rangeAxis.valueToJava2D(yQ1.doubleValue(), dataArea,
584: location);
585: double yyQ3 = rangeAxis.valueToJava2D(yQ3.doubleValue(), dataArea,
586: location);
587: double yyMax = rangeAxis.valueToJava2D(yMax.doubleValue(),
588: dataArea, location);
589: double yyMin = rangeAxis.valueToJava2D(yMin.doubleValue(),
590: dataArea, location);
591: double xxmid = xx + state.getBarWidth() / 2.0;
592:
593:
594: g2.draw(new Line2D.Double(xxmid, yyMax, xxmid, yyQ3));
595: g2.draw(new Line2D.Double(xx, yyMax, xx + state.getBarWidth(),
596: yyMax));
597:
598:
599: g2.draw(new Line2D.Double(xxmid, yyMin, xxmid, yyQ1));
600: g2.draw(new Line2D.Double(xx, yyMin, xx + state.getBarWidth(),
601: yyMin));
602:
603:
604: box = new Rectangle2D.Double(xx, Math.min(yyQ1, yyQ3),
605: state.getBarWidth(), Math.abs(yyQ1 - yyQ3));
606: if (this.fillBox) {
607: g2.fill(box);
608: }
609: g2.draw(box);
610:
611: }
612:
613: g2.setPaint(this.artifactPaint);
614:
615:
616: Number yMean = bawDataset.getMeanValue(row, column);
617: if (yMean != null) {
618: yyAverage = rangeAxis.valueToJava2D(yMean.doubleValue(),
619: dataArea, location);
620: aRadius = state.getBarWidth() / 4;
621: Ellipse2D.Double avgEllipse = new Ellipse2D.Double(xx + aRadius,
622: yyAverage - aRadius, aRadius * 2, aRadius * 2);
623: g2.fill(avgEllipse);
624: g2.draw(avgEllipse);
625: }
626:
627:
628: Number yMedian = bawDataset.getMedianValue(row, column);
629: if (yMedian != null) {
630: double yyMedian = rangeAxis.valueToJava2D(yMedian.doubleValue(),
631: dataArea, location);
632: g2.draw(new Line2D.Double(xx, yyMedian, xx + state.getBarWidth(),
633: yyMedian));
634: }
635:
636:
637: double maxAxisValue = rangeAxis.valueToJava2D(
638: rangeAxis.getUpperBound(), dataArea, location) + aRadius;
639: double minAxisValue = rangeAxis.valueToJava2D(
640: rangeAxis.getLowerBound(), dataArea, location) - aRadius;
641:
642: g2.setPaint(p);
643:
644:
645: double oRadius = state.getBarWidth() / 3;
646: List outliers = new ArrayList();
647: OutlierListCollection outlierListCollection
648: = new OutlierListCollection();
649:
650:
651:
652:
653: List yOutliers = bawDataset.getOutliers(row, column);
654: if (yOutliers != null) {
655: for (int i = 0; i < yOutliers.size(); i++) {
656: double outlier = ((Number) yOutliers.get(i)).doubleValue();
657: Number minOutlier = bawDataset.getMinOutlier(row, column);
658: Number maxOutlier = bawDataset.getMaxOutlier(row, column);
659: Number minRegular = bawDataset.getMinRegularValue(row, column);
660: Number maxRegular = bawDataset.getMaxRegularValue(row, column);
661: if (outlier > maxOutlier.doubleValue()) {
662: outlierListCollection.setHighFarOut(true);
663: }
664: else if (outlier < minOutlier.doubleValue()) {
665: outlierListCollection.setLowFarOut(true);
666: }
667: else if (outlier > maxRegular.doubleValue()) {
668: yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea,
669: location);
670: outliers.add(new Outlier(xx + state.getBarWidth() / 2.0,
671: yyOutlier, oRadius));
672: }
673: else if (outlier < minRegular.doubleValue()) {
674: yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea,
675: location);
676: outliers.add(new Outlier(xx + state.getBarWidth() / 2.0,
677: yyOutlier, oRadius));
678: }
679: Collections.sort(outliers);
680: }
681:
682:
683:
684: for (Iterator iterator = outliers.iterator(); iterator.hasNext();) {
685: Outlier outlier = (Outlier) iterator.next();
686: outlierListCollection.add(outlier);
687: }
688:
689: for (Iterator iterator = outlierListCollection.iterator();
690: iterator.hasNext();) {
691: OutlierList list = (OutlierList) iterator.next();
692: Outlier outlier = list.getAveragedOutlier();
693: Point2D point = outlier.getPoint();
694:
695: if (list.isMultiple()) {
696: drawMultipleEllipse(point, state.getBarWidth(), oRadius,
697: g2);
698: }
699: else {
700: drawEllipse(point, oRadius, g2);
701: }
702: }
703:
704:
705: if (outlierListCollection.isHighFarOut()) {
706: drawHighFarOut(aRadius / 2.0, g2,
707: xx + state.getBarWidth() / 2.0, maxAxisValue);
708: }
709:
710: if (outlierListCollection.isLowFarOut()) {
711: drawLowFarOut(aRadius / 2.0, g2,
712: xx + state.getBarWidth() / 2.0, minAxisValue);
713: }
714: }
715:
716: if (state.getInfo() != null && box != null) {
717: EntityCollection entities = state.getEntityCollection();
718: if (entities != null) {
719: String tip = null;
720: CategoryToolTipGenerator tipster
721: = getToolTipGenerator(row, column);
722: if (tipster != null) {
723: tip = tipster.generateToolTip(dataset, row, column);
724: }
725: String url = null;
726: if (getItemURLGenerator(row, column) != null) {
727: url = getItemURLGenerator(row, column).generateURL(dataset,
728: row, column);
729: }
730: CategoryItemEntity entity = new CategoryItemEntity(box, tip,
731: url, dataset, row, dataset.getColumnKey(column),
732: column);
733: entities.add(entity);
734: }
735: }
736:
737: }
738:
739:
746: private void drawEllipse(Point2D point, double oRadius, Graphics2D g2) {
747: Ellipse2D dot = new Ellipse2D.Double(point.getX() + oRadius / 2,
748: point.getY(), oRadius, oRadius);
749: g2.draw(dot);
750: }
751:
752:
760: private void drawMultipleEllipse(Point2D point, double boxWidth,
761: double oRadius, Graphics2D g2) {
762:
763: Ellipse2D dot1 = new Ellipse2D.Double(point.getX() - (boxWidth / 2)
764: + oRadius, point.getY(), oRadius, oRadius);
765: Ellipse2D dot2 = new Ellipse2D.Double(point.getX() + (boxWidth / 2),
766: point.getY(), oRadius, oRadius);
767: g2.draw(dot1);
768: g2.draw(dot2);
769: }
770:
771:
779: private void drawHighFarOut(double aRadius, Graphics2D g2, double xx,
780: double m) {
781: double side = aRadius * 2;
782: g2.draw(new Line2D.Double(xx - side, m + side, xx + side, m + side));
783: g2.draw(new Line2D.Double(xx - side, m + side, xx, m));
784: g2.draw(new Line2D.Double(xx + side, m + side, xx, m));
785: }
786:
787:
795: private void drawLowFarOut(double aRadius, Graphics2D g2, double xx,
796: double m) {
797: double side = aRadius * 2;
798: g2.draw(new Line2D.Double(xx - side, m - side, xx + side, m - side));
799: g2.draw(new Line2D.Double(xx - side, m - side, xx, m));
800: g2.draw(new Line2D.Double(xx + side, m - side, xx, m));
801: }
802:
803:
810: public boolean equals(Object obj) {
811: if (obj == this) {
812: return true;
813: }
814: if (!(obj instanceof BoxAndWhiskerRenderer)) {
815: return false;
816: }
817: if (!super.equals(obj)) {
818: return false;
819: }
820: BoxAndWhiskerRenderer that = (BoxAndWhiskerRenderer) obj;
821: if (!PaintUtilities.equal(this.artifactPaint, that.artifactPaint)) {
822: return false;
823: }
824: if (!(this.fillBox == that.fillBox)) {
825: return false;
826: }
827: if (!(this.itemMargin == that.itemMargin)) {
828: return false;
829: }
830: return true;
831: }
832:
833:
840: private void writeObject(ObjectOutputStream stream) throws IOException {
841: stream.defaultWriteObject();
842: SerialUtilities.writePaint(this.artifactPaint, stream);
843: }
844:
845:
853: private void readObject(ObjectInputStream stream)
854: throws IOException, ClassNotFoundException {
855: stream.defaultReadObject();
856: this.artifactPaint = SerialUtilities.readPaint(stream);
857: }
858:
859: }