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:
119: public class BoxAndWhiskerRenderer extends AbstractCategoryItemRenderer
120: implements Cloneable, PublicCloneable, Serializable {
121:
122:
123: private static final long serialVersionUID = 632027470694481177L;
124:
125:
126: private transient Paint artifactPaint;
127:
128:
129: private boolean fillBox;
130:
131:
132: private double itemMargin;
133:
134:
138: private double maximumBarWidth;
139:
140:
143: public BoxAndWhiskerRenderer() {
144: this.artifactPaint = Color.black;
145: this.fillBox = true;
146: this.itemMargin = 0.20;
147: this.maximumBarWidth = 1.0;
148: }
149:
150:
158: public Paint getArtifactPaint() {
159: return this.artifactPaint;
160: }
161:
162:
170: public void setArtifactPaint(Paint paint) {
171: if (paint == null) {
172: throw new IllegalArgumentException("Null 'paint' argument.");
173: }
174: this.artifactPaint = paint;
175: fireChangeEvent();
176: }
177:
178:
185: public boolean getFillBox() {
186: return this.fillBox;
187: }
188:
189:
197: public void setFillBox(boolean flag) {
198: this.fillBox = flag;
199: fireChangeEvent();
200: }
201:
202:
210: public double getItemMargin() {
211: return this.itemMargin;
212: }
213:
214:
222: public void setItemMargin(double margin) {
223: this.itemMargin = margin;
224: fireChangeEvent();
225: }
226:
227:
237: public double getMaximumBarWidth() {
238: return this.maximumBarWidth;
239: }
240:
241:
252: public void setMaximumBarWidth(double percent) {
253: this.maximumBarWidth = percent;
254: fireChangeEvent();
255: }
256:
257:
265: public LegendItem getLegendItem(int datasetIndex, int series) {
266:
267: CategoryPlot cp = getPlot();
268: if (cp == null) {
269: return null;
270: }
271:
272:
273: if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) {
274: return null;
275: }
276:
277: CategoryDataset dataset = cp.getDataset(datasetIndex);
278: String label = getLegendItemLabelGenerator().generateLabel(dataset,
279: series);
280: String description = label;
281: String toolTipText = null;
282: if (getLegendItemToolTipGenerator() != null) {
283: toolTipText = getLegendItemToolTipGenerator().generateLabel(
284: dataset, series);
285: }
286: String urlText = null;
287: if (getLegendItemURLGenerator() != null) {
288: urlText = getLegendItemURLGenerator().generateLabel(dataset,
289: series);
290: }
291: Shape shape = new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0);
292: Paint paint = lookupSeriesPaint(series);
293: Paint outlinePaint = lookupSeriesOutlinePaint(series);
294: Stroke outlineStroke = lookupSeriesOutlineStroke(series);
295: LegendItem result = new LegendItem(label, description, toolTipText,
296: urlText, shape, paint, outlineStroke, outlinePaint);
297: result.setDataset(dataset);
298: result.setDatasetIndex(datasetIndex);
299: result.setSeriesKey(dataset.getRowKey(series));
300: result.setSeriesIndex(series);
301: return result;
302:
303: }
304:
305:
317: public CategoryItemRendererState initialise(Graphics2D g2,
318: Rectangle2D dataArea,
319: CategoryPlot plot,
320: int rendererIndex,
321: PlotRenderingInfo info) {
322:
323: CategoryItemRendererState state = super.initialise(g2, dataArea, plot,
324: rendererIndex, info);
325:
326:
327: CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
328: CategoryDataset dataset = plot.getDataset(rendererIndex);
329: if (dataset != null) {
330: int columns = dataset.getColumnCount();
331: int rows = dataset.getRowCount();
332: double space = 0.0;
333: PlotOrientation orientation = plot.getOrientation();
334: if (orientation == PlotOrientation.HORIZONTAL) {
335: space = dataArea.getHeight();
336: }
337: else if (orientation == PlotOrientation.VERTICAL) {
338: space = dataArea.getWidth();
339: }
340: double maxWidth = space * getMaximumBarWidth();
341: double categoryMargin = 0.0;
342: double currentItemMargin = 0.0;
343: if (columns > 1) {
344: categoryMargin = domainAxis.getCategoryMargin();
345: }
346: if (rows > 1) {
347: currentItemMargin = getItemMargin();
348: }
349: double used = space * (1 - domainAxis.getLowerMargin()
350: - domainAxis.getUpperMargin()
351: - categoryMargin - currentItemMargin);
352: if ((rows * columns) > 0) {
353: state.setBarWidth(Math.min(used / (dataset.getColumnCount()
354: * dataset.getRowCount()), maxWidth));
355: }
356: else {
357: state.setBarWidth(Math.min(used, maxWidth));
358: }
359: }
360:
361: return state;
362:
363: }
364:
365:
380: public void drawItem(Graphics2D g2,
381: CategoryItemRendererState state,
382: Rectangle2D dataArea,
383: CategoryPlot plot,
384: CategoryAxis domainAxis,
385: ValueAxis rangeAxis,
386: CategoryDataset dataset,
387: int row,
388: int column,
389: int pass) {
390:
391: if (!(dataset instanceof BoxAndWhiskerCategoryDataset)) {
392: throw new IllegalArgumentException(
393: "BoxAndWhiskerRenderer.drawItem() : the data should be "
394: + "of type BoxAndWhiskerCategoryDataset only.");
395: }
396:
397: PlotOrientation orientation = plot.getOrientation();
398:
399: if (orientation == PlotOrientation.HORIZONTAL) {
400: drawHorizontalItem(g2, state, dataArea, plot, domainAxis,
401: rangeAxis, dataset, row, column);
402: }
403: else if (orientation == PlotOrientation.VERTICAL) {
404: drawVerticalItem(g2, state, dataArea, plot, domainAxis,
405: rangeAxis, dataset, row, column);
406: }
407:
408: }
409:
410:
426: public void drawHorizontalItem(Graphics2D g2,
427: CategoryItemRendererState state,
428: Rectangle2D dataArea,
429: CategoryPlot plot,
430: CategoryAxis domainAxis,
431: ValueAxis rangeAxis,
432: CategoryDataset dataset,
433: int row,
434: int column) {
435:
436: BoxAndWhiskerCategoryDataset bawDataset
437: = (BoxAndWhiskerCategoryDataset) dataset;
438:
439: double categoryEnd = domainAxis.getCategoryEnd(column,
440: getColumnCount(), dataArea, plot.getDomainAxisEdge());
441: double categoryStart = domainAxis.getCategoryStart(column,
442: getColumnCount(), dataArea, plot.getDomainAxisEdge());
443: double categoryWidth = Math.abs(categoryEnd - categoryStart);
444:
445: double yy = categoryStart;
446: int seriesCount = getRowCount();
447: int categoryCount = getColumnCount();
448:
449: if (seriesCount > 1) {
450: double seriesGap = dataArea.getHeight() * getItemMargin()
451: / (categoryCount * (seriesCount - 1));
452: double usedWidth = (state.getBarWidth() * seriesCount)
453: + (seriesGap * (seriesCount - 1));
454:
455:
456: double offset = (categoryWidth - usedWidth) / 2;
457: yy = yy + offset + (row * (state.getBarWidth() + seriesGap));
458: }
459: else {
460:
461:
462: double offset = (categoryWidth - state.getBarWidth()) / 2;
463: yy = yy + offset;
464: }
465:
466: g2.setPaint(getItemPaint(row, column));
467: Stroke s = getItemStroke(row, column);
468: g2.setStroke(s);
469:
470: RectangleEdge location = plot.getRangeAxisEdge();
471:
472: Number xQ1 = bawDataset.getQ1Value(row, column);
473: Number xQ3 = bawDataset.getQ3Value(row, column);
474: Number xMax = bawDataset.getMaxRegularValue(row, column);
475: Number xMin = bawDataset.getMinRegularValue(row, column);
476:
477: Shape box = null;
478: if (xQ1 != null && xQ3 != null && xMax != null && xMin != null) {
479:
480: double xxQ1 = rangeAxis.valueToJava2D(xQ1.doubleValue(), dataArea,
481: location);
482: double xxQ3 = rangeAxis.valueToJava2D(xQ3.doubleValue(), dataArea,
483: location);
484: double xxMax = rangeAxis.valueToJava2D(xMax.doubleValue(), dataArea,
485: location);
486: double xxMin = rangeAxis.valueToJava2D(xMin.doubleValue(), dataArea,
487: location);
488: double yymid = yy + state.getBarWidth() / 2.0;
489:
490:
491: g2.draw(new Line2D.Double(xxMax, yymid, xxQ3, yymid));
492: g2.draw(new Line2D.Double(xxMax, yy, xxMax,
493: yy + state.getBarWidth()));
494:
495:
496: g2.draw(new Line2D.Double(xxMin, yymid, xxQ1, yymid));
497: g2.draw(new Line2D.Double(xxMin, yy, xxMin,
498: yy + state.getBarWidth()));
499:
500:
501: box = new Rectangle2D.Double(Math.min(xxQ1, xxQ3), yy,
502: Math.abs(xxQ1 - xxQ3), state.getBarWidth());
503: if (this.fillBox) {
504: g2.fill(box);
505: }
506: g2.setStroke(getItemOutlineStroke(row, column));
507: g2.setPaint(getItemOutlinePaint(row, column));
508: g2.draw(box);
509: }
510:
511: g2.setPaint(this.artifactPaint);
512: double aRadius = 0;
513:
514:
515: Number xMean = bawDataset.getMeanValue(row, column);
516: if (xMean != null) {
517: double xxMean = rangeAxis.valueToJava2D(xMean.doubleValue(),
518: dataArea, location);
519: aRadius = state.getBarWidth() / 4;
520:
521:
522: if ((xxMean > (dataArea.getMinX() - aRadius))
523: && (xxMean < (dataArea.getMaxX() + aRadius))) {
524: Ellipse2D.Double avgEllipse = new Ellipse2D.Double(xxMean
525: - aRadius, yy + aRadius, aRadius * 2, aRadius * 2);
526: g2.fill(avgEllipse);
527: g2.draw(avgEllipse);
528: }
529: }
530:
531:
532: Number xMedian = bawDataset.getMedianValue(row, column);
533: if (xMedian != null) {
534: double xxMedian = rangeAxis.valueToJava2D(xMedian.doubleValue(),
535: dataArea, location);
536: g2.draw(new Line2D.Double(xxMedian, yy, xxMedian,
537: yy + state.getBarWidth()));
538: }
539:
540:
541: if (state.getInfo() != null && box != null) {
542: EntityCollection entities = state.getEntityCollection();
543: if (entities != null) {
544: addItemEntity(entities, dataset, row, column, box);
545: }
546: }
547:
548: }
549:
550:
566: public void drawVerticalItem(Graphics2D g2,
567: CategoryItemRendererState state,
568: Rectangle2D dataArea,
569: CategoryPlot plot,
570: CategoryAxis domainAxis,
571: ValueAxis rangeAxis,
572: CategoryDataset dataset,
573: int row,
574: int column) {
575:
576: BoxAndWhiskerCategoryDataset bawDataset
577: = (BoxAndWhiskerCategoryDataset) dataset;
578:
579: double categoryEnd = domainAxis.getCategoryEnd(column,
580: getColumnCount(), dataArea, plot.getDomainAxisEdge());
581: double categoryStart = domainAxis.getCategoryStart(column,
582: getColumnCount(), dataArea, plot.getDomainAxisEdge());
583: double categoryWidth = categoryEnd - categoryStart;
584:
585: double xx = categoryStart;
586: int seriesCount = getRowCount();
587: int categoryCount = getColumnCount();
588:
589: if (seriesCount > 1) {
590: double seriesGap = dataArea.getWidth() * getItemMargin()
591: / (categoryCount * (seriesCount - 1));
592: double usedWidth = (state.getBarWidth() * seriesCount)
593: + (seriesGap * (seriesCount - 1));
594:
595:
596: double offset = (categoryWidth - usedWidth) / 2;
597: xx = xx + offset + (row * (state.getBarWidth() + seriesGap));
598: }
599: else {
600:
601:
602: double offset = (categoryWidth - state.getBarWidth()) / 2;
603: xx = xx + offset;
604: }
605:
606: double yyAverage = 0.0;
607: double yyOutlier;
608:
609: Paint itemPaint = getItemPaint(row, column);
610: g2.setPaint(itemPaint);
611: Stroke s = getItemStroke(row, column);
612: g2.setStroke(s);
613:
614: double aRadius = 0;
615:
616: RectangleEdge location = plot.getRangeAxisEdge();
617:
618: Number yQ1 = bawDataset.getQ1Value(row, column);
619: Number yQ3 = bawDataset.getQ3Value(row, column);
620: Number yMax = bawDataset.getMaxRegularValue(row, column);
621: Number yMin = bawDataset.getMinRegularValue(row, column);
622: Shape box = null;
623: if (yQ1 != null && yQ3 != null && yMax != null && yMin != null) {
624:
625: double yyQ1 = rangeAxis.valueToJava2D(yQ1.doubleValue(), dataArea,
626: location);
627: double yyQ3 = rangeAxis.valueToJava2D(yQ3.doubleValue(), dataArea,
628: location);
629: double yyMax = rangeAxis.valueToJava2D(yMax.doubleValue(),
630: dataArea, location);
631: double yyMin = rangeAxis.valueToJava2D(yMin.doubleValue(),
632: dataArea, location);
633: double xxmid = xx + state.getBarWidth() / 2.0;
634:
635:
636: g2.draw(new Line2D.Double(xxmid, yyMax, xxmid, yyQ3));
637: g2.draw(new Line2D.Double(xx, yyMax, xx + state.getBarWidth(),
638: yyMax));
639:
640:
641: g2.draw(new Line2D.Double(xxmid, yyMin, xxmid, yyQ1));
642: g2.draw(new Line2D.Double(xx, yyMin, xx + state.getBarWidth(),
643: yyMin));
644:
645:
646: box = new Rectangle2D.Double(xx, Math.min(yyQ1, yyQ3),
647: state.getBarWidth(), Math.abs(yyQ1 - yyQ3));
648: if (this.fillBox) {
649: g2.fill(box);
650: }
651: g2.setStroke(getItemOutlineStroke(row, column));
652: g2.setPaint(getItemOutlinePaint(row, column));
653: g2.draw(box);
654: }
655:
656: g2.setPaint(this.artifactPaint);
657:
658:
659: Number yMean = bawDataset.getMeanValue(row, column);
660: if (yMean != null) {
661: yyAverage = rangeAxis.valueToJava2D(yMean.doubleValue(),
662: dataArea, location);
663: aRadius = state.getBarWidth() / 4;
664:
665:
666: if ((yyAverage > (dataArea.getMinY() - aRadius))
667: && (yyAverage < (dataArea.getMaxY() + aRadius))) {
668: Ellipse2D.Double avgEllipse = new Ellipse2D.Double(xx + aRadius,
669: yyAverage - aRadius, aRadius * 2, aRadius * 2);
670: g2.fill(avgEllipse);
671: g2.draw(avgEllipse);
672: }
673: }
674:
675:
676: Number yMedian = bawDataset.getMedianValue(row, column);
677: if (yMedian != null) {
678: double yyMedian = rangeAxis.valueToJava2D(yMedian.doubleValue(),
679: dataArea, location);
680: g2.draw(new Line2D.Double(xx, yyMedian, xx + state.getBarWidth(),
681: yyMedian));
682: }
683:
684:
685: double maxAxisValue = rangeAxis.valueToJava2D(
686: rangeAxis.getUpperBound(), dataArea, location) + aRadius;
687: double minAxisValue = rangeAxis.valueToJava2D(
688: rangeAxis.getLowerBound(), dataArea, location) - aRadius;
689:
690: g2.setPaint(itemPaint);
691:
692:
693: double oRadius = state.getBarWidth() / 3;
694: List outliers = new ArrayList();
695: OutlierListCollection outlierListCollection
696: = new OutlierListCollection();
697:
698:
699:
700:
701: List yOutliers = bawDataset.getOutliers(row, column);
702: if (yOutliers != null) {
703: for (int i = 0; i < yOutliers.size(); i++) {
704: double outlier = ((Number) yOutliers.get(i)).doubleValue();
705: Number minOutlier = bawDataset.getMinOutlier(row, column);
706: Number maxOutlier = bawDataset.getMaxOutlier(row, column);
707: Number minRegular = bawDataset.getMinRegularValue(row, column);
708: Number maxRegular = bawDataset.getMaxRegularValue(row, column);
709: if (outlier > maxOutlier.doubleValue()) {
710: outlierListCollection.setHighFarOut(true);
711: }
712: else if (outlier < minOutlier.doubleValue()) {
713: outlierListCollection.setLowFarOut(true);
714: }
715: else if (outlier > maxRegular.doubleValue()) {
716: yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea,
717: location);
718: outliers.add(new Outlier(xx + state.getBarWidth() / 2.0,
719: yyOutlier, oRadius));
720: }
721: else if (outlier < minRegular.doubleValue()) {
722: yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea,
723: location);
724: outliers.add(new Outlier(xx + state.getBarWidth() / 2.0,
725: yyOutlier, oRadius));
726: }
727: Collections.sort(outliers);
728: }
729:
730:
731:
732: for (Iterator iterator = outliers.iterator(); iterator.hasNext();) {
733: Outlier outlier = (Outlier) iterator.next();
734: outlierListCollection.add(outlier);
735: }
736:
737: for (Iterator iterator = outlierListCollection.iterator();
738: iterator.hasNext();) {
739: OutlierList list = (OutlierList) iterator.next();
740: Outlier outlier = list.getAveragedOutlier();
741: Point2D point = outlier.getPoint();
742:
743: if (list.isMultiple()) {
744: drawMultipleEllipse(point, state.getBarWidth(), oRadius,
745: g2);
746: }
747: else {
748: drawEllipse(point, oRadius, g2);
749: }
750: }
751:
752:
753: if (outlierListCollection.isHighFarOut()) {
754: drawHighFarOut(aRadius / 2.0, g2,
755: xx + state.getBarWidth() / 2.0, maxAxisValue);
756: }
757:
758: if (outlierListCollection.isLowFarOut()) {
759: drawLowFarOut(aRadius / 2.0, g2,
760: xx + state.getBarWidth() / 2.0, minAxisValue);
761: }
762: }
763:
764: if (state.getInfo() != null && box != null) {
765: EntityCollection entities = state.getEntityCollection();
766: if (entities != null) {
767: addItemEntity(entities, dataset, row, column, box);
768: }
769: }
770:
771: }
772:
773:
780: private void drawEllipse(Point2D point, double oRadius, Graphics2D g2) {
781: Ellipse2D dot = new Ellipse2D.Double(point.getX() + oRadius / 2,
782: point.getY(), oRadius, oRadius);
783: g2.draw(dot);
784: }
785:
786:
794: private void drawMultipleEllipse(Point2D point, double boxWidth,
795: double oRadius, Graphics2D g2) {
796:
797: Ellipse2D dot1 = new Ellipse2D.Double(point.getX() - (boxWidth / 2)
798: + oRadius, point.getY(), oRadius, oRadius);
799: Ellipse2D dot2 = new Ellipse2D.Double(point.getX() + (boxWidth / 2),
800: point.getY(), oRadius, oRadius);
801: g2.draw(dot1);
802: g2.draw(dot2);
803: }
804:
805:
813: private void drawHighFarOut(double aRadius, Graphics2D g2, double xx,
814: double m) {
815: double side = aRadius * 2;
816: g2.draw(new Line2D.Double(xx - side, m + side, xx + side, m + side));
817: g2.draw(new Line2D.Double(xx - side, m + side, xx, m));
818: g2.draw(new Line2D.Double(xx + side, m + side, xx, m));
819: }
820:
821:
829: private void drawLowFarOut(double aRadius, Graphics2D g2, double xx,
830: double m) {
831: double side = aRadius * 2;
832: g2.draw(new Line2D.Double(xx - side, m - side, xx + side, m - side));
833: g2.draw(new Line2D.Double(xx - side, m - side, xx, m));
834: g2.draw(new Line2D.Double(xx + side, m - side, xx, m));
835: }
836:
837:
844: public boolean equals(Object obj) {
845: if (obj == this) {
846: return true;
847: }
848: if (!(obj instanceof BoxAndWhiskerRenderer)) {
849: return false;
850: }
851: if (!super.equals(obj)) {
852: return false;
853: }
854: BoxAndWhiskerRenderer that = (BoxAndWhiskerRenderer) obj;
855: if (!PaintUtilities.equal(this.artifactPaint, that.artifactPaint)) {
856: return false;
857: }
858: if (this.fillBox != that.fillBox) {
859: return false;
860: }
861: if (this.itemMargin != that.itemMargin) {
862: return false;
863: }
864: if (this.maximumBarWidth != that.maximumBarWidth) {
865: return false;
866: }
867: return true;
868: }
869:
870:
877: private void writeObject(ObjectOutputStream stream) throws IOException {
878: stream.defaultWriteObject();
879: SerialUtilities.writePaint(this.artifactPaint, stream);
880: }
881:
882:
890: private void readObject(ObjectInputStream stream)
891: throws IOException, ClassNotFoundException {
892: stream.defaultReadObject();
893: this.artifactPaint = SerialUtilities.readPaint(stream);
894: }
895:
896: }