1:
69:
70: package ;
71:
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: import ;
87: import ;
88: import ;
89:
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: import ;
107: import ;
108: import ;
109:
110:
115: public class BoxAndWhiskerRenderer extends AbstractCategoryItemRenderer
116: implements Cloneable, PublicCloneable,
117: Serializable {
118:
119:
120: private static final long serialVersionUID = 632027470694481177L;
121:
122:
123: private transient Paint artifactPaint;
124:
125:
126: private boolean fillBox;
127:
128:
129: private double itemMargin;
130:
131:
134: public BoxAndWhiskerRenderer() {
135: this.artifactPaint = Color.black;
136: this.fillBox = true;
137: this.itemMargin = 0.20;
138: }
139:
140:
148: public Paint getArtifactPaint() {
149: return this.artifactPaint;
150: }
151:
152:
160: public void setArtifactPaint(Paint paint) {
161: if (paint == null) {
162: throw new IllegalArgumentException("Null 'paint' argument.");
163: }
164: this.artifactPaint = paint;
165: fireChangeEvent();
166: }
167:
168:
175: public boolean getFillBox() {
176: return this.fillBox;
177: }
178:
179:
187: public void setFillBox(boolean flag) {
188: this.fillBox = flag;
189: fireChangeEvent();
190: }
191:
192:
200: public double getItemMargin() {
201: return this.itemMargin;
202: }
203:
204:
212: public void setItemMargin(double margin) {
213: this.itemMargin = margin;
214: fireChangeEvent();
215: }
216:
217:
225: public LegendItem getLegendItem(int datasetIndex, int series) {
226:
227: CategoryPlot cp = getPlot();
228: if (cp == null) {
229: return null;
230: }
231:
232:
233: if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) {
234: return null;
235: }
236:
237: CategoryDataset dataset = cp.getDataset(datasetIndex);
238: String label = getLegendItemLabelGenerator().generateLabel(dataset,
239: series);
240: String description = label;
241: String toolTipText = null;
242: if (getLegendItemToolTipGenerator() != null) {
243: toolTipText = getLegendItemToolTipGenerator().generateLabel(
244: dataset, series);
245: }
246: String urlText = null;
247: if (getLegendItemURLGenerator() != null) {
248: urlText = getLegendItemURLGenerator().generateLabel(dataset,
249: series);
250: }
251: Shape shape = new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0);
252: Paint paint = lookupSeriesPaint(series);
253: Paint outlinePaint = lookupSeriesOutlinePaint(series);
254: Stroke outlineStroke = lookupSeriesOutlineStroke(series);
255: LegendItem result = new LegendItem(label, description, toolTipText,
256: urlText, shape, paint, outlineStroke, outlinePaint);
257: result.setDataset(dataset);
258: result.setDatasetIndex(datasetIndex);
259: result.setSeriesKey(dataset.getRowKey(series));
260: result.setSeriesIndex(series);
261: return result;
262:
263: }
264:
265:
277: public CategoryItemRendererState initialise(Graphics2D g2,
278: Rectangle2D dataArea,
279: CategoryPlot plot,
280: int rendererIndex,
281: PlotRenderingInfo info) {
282:
283: CategoryItemRendererState state = super.initialise(g2, dataArea, plot,
284: rendererIndex, info);
285:
286:
287: CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
288: CategoryDataset dataset = plot.getDataset(rendererIndex);
289: if (dataset != null) {
290: int columns = dataset.getColumnCount();
291: int rows = dataset.getRowCount();
292: double space = 0.0;
293: PlotOrientation orientation = plot.getOrientation();
294: if (orientation == PlotOrientation.HORIZONTAL) {
295: space = dataArea.getHeight();
296: }
297: else if (orientation == PlotOrientation.VERTICAL) {
298: space = dataArea.getWidth();
299: }
300: double categoryMargin = 0.0;
301: double currentItemMargin = 0.0;
302: if (columns > 1) {
303: categoryMargin = domainAxis.getCategoryMargin();
304: }
305: if (rows > 1) {
306: currentItemMargin = getItemMargin();
307: }
308: double used = space * (1 - domainAxis.getLowerMargin()
309: - domainAxis.getUpperMargin()
310: - categoryMargin - currentItemMargin);
311: if ((rows * columns) > 0) {
312: state.setBarWidth(used / (dataset.getColumnCount()
313: * dataset.getRowCount()));
314: }
315: else {
316: state.setBarWidth(used);
317: }
318: }
319:
320: return state;
321:
322: }
323:
324:
338: public void drawItem(Graphics2D g2,
339: CategoryItemRendererState state,
340: Rectangle2D dataArea,
341: CategoryPlot plot,
342: CategoryAxis domainAxis,
343: ValueAxis rangeAxis,
344: CategoryDataset dataset,
345: int row,
346: int column,
347: int pass) {
348:
349: if (!(dataset instanceof BoxAndWhiskerCategoryDataset)) {
350: throw new IllegalArgumentException(
351: "BoxAndWhiskerRenderer.drawItem() : the data should be "
352: + "of type BoxAndWhiskerCategoryDataset only.");
353: }
354:
355: PlotOrientation orientation = plot.getOrientation();
356:
357: if (orientation == PlotOrientation.HORIZONTAL) {
358: drawHorizontalItem(g2, state, dataArea, plot, domainAxis,
359: rangeAxis, dataset, row, column);
360: }
361: else if (orientation == PlotOrientation.VERTICAL) {
362: drawVerticalItem(g2, state, dataArea, plot, domainAxis,
363: rangeAxis, dataset, row, column);
364: }
365:
366: }
367:
368:
383: public void drawHorizontalItem(Graphics2D g2,
384: CategoryItemRendererState state,
385: Rectangle2D dataArea,
386: CategoryPlot plot,
387: CategoryAxis domainAxis,
388: ValueAxis rangeAxis,
389: CategoryDataset dataset,
390: int row,
391: int column) {
392:
393: BoxAndWhiskerCategoryDataset bawDataset
394: = (BoxAndWhiskerCategoryDataset) dataset;
395:
396: double categoryEnd = domainAxis.getCategoryEnd(column,
397: getColumnCount(), dataArea, plot.getDomainAxisEdge());
398: double categoryStart = domainAxis.getCategoryStart(column,
399: getColumnCount(), dataArea, plot.getDomainAxisEdge());
400: double categoryWidth = Math.abs(categoryEnd - categoryStart);
401:
402: double yy = categoryStart;
403: int seriesCount = getRowCount();
404: int categoryCount = getColumnCount();
405:
406: if (seriesCount > 1) {
407: double seriesGap = dataArea.getWidth() * getItemMargin()
408: / (categoryCount * (seriesCount - 1));
409: double usedWidth = (state.getBarWidth() * seriesCount)
410: + (seriesGap * (seriesCount - 1));
411:
412:
413: double offset = (categoryWidth - usedWidth) / 2;
414: yy = yy + offset + (row * (state.getBarWidth() + seriesGap));
415: }
416: else {
417:
418:
419: double offset = (categoryWidth - state.getBarWidth()) / 2;
420: yy = yy + offset;
421: }
422:
423: Paint p = getItemPaint(row, column);
424: if (p != null) {
425: g2.setPaint(p);
426: }
427: Stroke s = getItemStroke(row, column);
428: g2.setStroke(s);
429:
430: RectangleEdge location = plot.getRangeAxisEdge();
431:
432: Number xQ1 = bawDataset.getQ1Value(row, column);
433: Number xQ3 = bawDataset.getQ3Value(row, column);
434: Number xMax = bawDataset.getMaxRegularValue(row, column);
435: Number xMin = bawDataset.getMinRegularValue(row, column);
436:
437: Shape box = null;
438: if (xQ1 != null && xQ3 != null && xMax != null && xMin != null) {
439:
440: double xxQ1 = rangeAxis.valueToJava2D(xQ1.doubleValue(), dataArea,
441: location);
442: double xxQ3 = rangeAxis.valueToJava2D(xQ3.doubleValue(), dataArea,
443: location);
444: double xxMax = rangeAxis.valueToJava2D(xMax.doubleValue(), dataArea,
445: location);
446: double xxMin = rangeAxis.valueToJava2D(xMin.doubleValue(), dataArea,
447: location);
448: double yymid = yy + state.getBarWidth() / 2.0;
449:
450:
451: g2.draw(new Line2D.Double(xxMax, yymid, xxQ3, yymid));
452: g2.draw(new Line2D.Double(xxMax, yy, xxMax,
453: yy + state.getBarWidth()));
454:
455:
456: g2.draw(new Line2D.Double(xxMin, yymid, xxQ1, yymid));
457: g2.draw(new Line2D.Double(xxMin, yy, xxMin,
458: yy + state.getBarWidth()));
459:
460:
461: box = new Rectangle2D.Double(Math.min(xxQ1, xxQ3), yy,
462: Math.abs(xxQ1 - xxQ3), state.getBarWidth());
463: if (this.fillBox) {
464: g2.fill(box);
465: }
466: g2.draw(box);
467:
468: }
469:
470: g2.setPaint(this.artifactPaint);
471: double aRadius = 0;
472:
473:
474: Number xMean = bawDataset.getMeanValue(row, column);
475: if (xMean != null) {
476: double xxMean = rangeAxis.valueToJava2D(xMean.doubleValue(),
477: dataArea, location);
478: aRadius = state.getBarWidth() / 4;
479:
480:
481: if ((xxMean > (dataArea.getMinX() - aRadius))
482: && (xxMean < (dataArea.getMaxX() + aRadius))) {
483: Ellipse2D.Double avgEllipse = new Ellipse2D.Double(xxMean
484: - aRadius, yy + aRadius, aRadius * 2, aRadius * 2);
485: g2.fill(avgEllipse);
486: g2.draw(avgEllipse);
487: }
488: }
489:
490:
491: Number xMedian = bawDataset.getMedianValue(row, column);
492: if (xMedian != null) {
493: double xxMedian = rangeAxis.valueToJava2D(xMedian.doubleValue(),
494: dataArea, location);
495: g2.draw(new Line2D.Double(xxMedian, yy, xxMedian,
496: yy + state.getBarWidth()));
497: }
498:
499:
500: if (state.getInfo() != null && box != null) {
501: EntityCollection entities = state.getEntityCollection();
502: if (entities != null) {
503: String tip = null;
504: CategoryToolTipGenerator tipster
505: = getToolTipGenerator(row, column);
506: if (tipster != null) {
507: tip = tipster.generateToolTip(dataset, row, column);
508: }
509: String url = null;
510: if (getItemURLGenerator(row, column) != null) {
511: url = getItemURLGenerator(row, column).generateURL(
512: dataset, row, column);
513: }
514: CategoryItemEntity entity = new CategoryItemEntity(box, tip,
515: url, dataset, dataset.getRowKey(row),
516: dataset.getColumnKey(column));
517: entities.add(entity);
518: }
519: }
520:
521: }
522:
523:
538: public void drawVerticalItem(Graphics2D g2,
539: CategoryItemRendererState state,
540: Rectangle2D dataArea,
541: CategoryPlot plot,
542: CategoryAxis domainAxis,
543: ValueAxis rangeAxis,
544: CategoryDataset dataset,
545: int row,
546: int column) {
547:
548: BoxAndWhiskerCategoryDataset bawDataset
549: = (BoxAndWhiskerCategoryDataset) dataset;
550:
551: double categoryEnd = domainAxis.getCategoryEnd(column,
552: getColumnCount(), dataArea, plot.getDomainAxisEdge());
553: double categoryStart = domainAxis.getCategoryStart(column,
554: getColumnCount(), dataArea, plot.getDomainAxisEdge());
555: double categoryWidth = categoryEnd - categoryStart;
556:
557: double xx = categoryStart;
558: int seriesCount = getRowCount();
559: int categoryCount = getColumnCount();
560:
561: if (seriesCount > 1) {
562: double seriesGap = dataArea.getWidth() * getItemMargin()
563: / (categoryCount * (seriesCount - 1));
564: double usedWidth = (state.getBarWidth() * seriesCount)
565: + (seriesGap * (seriesCount - 1));
566:
567:
568: double offset = (categoryWidth - usedWidth) / 2;
569: xx = xx + offset + (row * (state.getBarWidth() + seriesGap));
570: }
571: else {
572:
573:
574: double offset = (categoryWidth - state.getBarWidth()) / 2;
575: xx = xx + offset;
576: }
577:
578: double yyAverage = 0.0;
579: double yyOutlier;
580:
581: Paint p = getItemPaint(row, column);
582: if (p != null) {
583: g2.setPaint(p);
584: }
585: Stroke s = getItemStroke(row, column);
586: g2.setStroke(s);
587:
588: double aRadius = 0;
589:
590: RectangleEdge location = plot.getRangeAxisEdge();
591:
592: Number yQ1 = bawDataset.getQ1Value(row, column);
593: Number yQ3 = bawDataset.getQ3Value(row, column);
594: Number yMax = bawDataset.getMaxRegularValue(row, column);
595: Number yMin = bawDataset.getMinRegularValue(row, column);
596: Shape box = null;
597: if (yQ1 != null && yQ3 != null && yMax != null && yMin != null) {
598:
599: double yyQ1 = rangeAxis.valueToJava2D(yQ1.doubleValue(), dataArea,
600: location);
601: double yyQ3 = rangeAxis.valueToJava2D(yQ3.doubleValue(), dataArea,
602: location);
603: double yyMax = rangeAxis.valueToJava2D(yMax.doubleValue(),
604: dataArea, location);
605: double yyMin = rangeAxis.valueToJava2D(yMin.doubleValue(),
606: dataArea, location);
607: double xxmid = xx + state.getBarWidth() / 2.0;
608:
609:
610: g2.draw(new Line2D.Double(xxmid, yyMax, xxmid, yyQ3));
611: g2.draw(new Line2D.Double(xx, yyMax, xx + state.getBarWidth(),
612: yyMax));
613:
614:
615: g2.draw(new Line2D.Double(xxmid, yyMin, xxmid, yyQ1));
616: g2.draw(new Line2D.Double(xx, yyMin, xx + state.getBarWidth(),
617: yyMin));
618:
619:
620: box = new Rectangle2D.Double(xx, Math.min(yyQ1, yyQ3),
621: state.getBarWidth(), Math.abs(yyQ1 - yyQ3));
622: if (this.fillBox) {
623: g2.fill(box);
624: }
625: g2.draw(box);
626:
627: }
628:
629: g2.setPaint(this.artifactPaint);
630:
631:
632: Number yMean = bawDataset.getMeanValue(row, column);
633: if (yMean != null) {
634: yyAverage = rangeAxis.valueToJava2D(yMean.doubleValue(),
635: dataArea, location);
636: aRadius = state.getBarWidth() / 4;
637:
638:
639: if ((yyAverage > (dataArea.getMinY() - aRadius))
640: && (yyAverage < (dataArea.getMaxY() + aRadius))) {
641: Ellipse2D.Double avgEllipse = new Ellipse2D.Double(xx + aRadius,
642: yyAverage - aRadius, aRadius * 2, aRadius * 2);
643: g2.fill(avgEllipse);
644: g2.draw(avgEllipse);
645: }
646: }
647:
648:
649: Number yMedian = bawDataset.getMedianValue(row, column);
650: if (yMedian != null) {
651: double yyMedian = rangeAxis.valueToJava2D(yMedian.doubleValue(),
652: dataArea, location);
653: g2.draw(new Line2D.Double(xx, yyMedian, xx + state.getBarWidth(),
654: yyMedian));
655: }
656:
657:
658: double maxAxisValue = rangeAxis.valueToJava2D(
659: rangeAxis.getUpperBound(), dataArea, location) + aRadius;
660: double minAxisValue = rangeAxis.valueToJava2D(
661: rangeAxis.getLowerBound(), dataArea, location) - aRadius;
662:
663: g2.setPaint(p);
664:
665:
666: double oRadius = state.getBarWidth() / 3;
667: List outliers = new ArrayList();
668: OutlierListCollection outlierListCollection
669: = new OutlierListCollection();
670:
671:
672:
673:
674: List yOutliers = bawDataset.getOutliers(row, column);
675: if (yOutliers != null) {
676: for (int i = 0; i < yOutliers.size(); i++) {
677: double outlier = ((Number) yOutliers.get(i)).doubleValue();
678: Number minOutlier = bawDataset.getMinOutlier(row, column);
679: Number maxOutlier = bawDataset.getMaxOutlier(row, column);
680: Number minRegular = bawDataset.getMinRegularValue(row, column);
681: Number maxRegular = bawDataset.getMaxRegularValue(row, column);
682: if (outlier > maxOutlier.doubleValue()) {
683: outlierListCollection.setHighFarOut(true);
684: }
685: else if (outlier < minOutlier.doubleValue()) {
686: outlierListCollection.setLowFarOut(true);
687: }
688: else if (outlier > maxRegular.doubleValue()) {
689: yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea,
690: location);
691: outliers.add(new Outlier(xx + state.getBarWidth() / 2.0,
692: yyOutlier, oRadius));
693: }
694: else if (outlier < minRegular.doubleValue()) {
695: yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea,
696: location);
697: outliers.add(new Outlier(xx + state.getBarWidth() / 2.0,
698: yyOutlier, oRadius));
699: }
700: Collections.sort(outliers);
701: }
702:
703:
704:
705: for (Iterator iterator = outliers.iterator(); iterator.hasNext();) {
706: Outlier outlier = (Outlier) iterator.next();
707: outlierListCollection.add(outlier);
708: }
709:
710: for (Iterator iterator = outlierListCollection.iterator();
711: iterator.hasNext();) {
712: OutlierList list = (OutlierList) iterator.next();
713: Outlier outlier = list.getAveragedOutlier();
714: Point2D point = outlier.getPoint();
715:
716: if (list.isMultiple()) {
717: drawMultipleEllipse(point, state.getBarWidth(), oRadius,
718: g2);
719: }
720: else {
721: drawEllipse(point, oRadius, g2);
722: }
723: }
724:
725:
726: if (outlierListCollection.isHighFarOut()) {
727: drawHighFarOut(aRadius / 2.0, g2,
728: xx + state.getBarWidth() / 2.0, maxAxisValue);
729: }
730:
731: if (outlierListCollection.isLowFarOut()) {
732: drawLowFarOut(aRadius / 2.0, g2,
733: xx + state.getBarWidth() / 2.0, minAxisValue);
734: }
735: }
736:
737: if (state.getInfo() != null && box != null) {
738: EntityCollection entities = state.getEntityCollection();
739: if (entities != null) {
740: String tip = null;
741: CategoryToolTipGenerator tipster
742: = getToolTipGenerator(row, column);
743: if (tipster != null) {
744: tip = tipster.generateToolTip(dataset, row, column);
745: }
746: String url = null;
747: if (getItemURLGenerator(row, column) != null) {
748: url = getItemURLGenerator(row, column).generateURL(dataset,
749: row, column);
750: }
751: CategoryItemEntity entity = new CategoryItemEntity(box, tip,
752: url, dataset, dataset.getRowKey(row),
753: dataset.getColumnKey(column));
754: entities.add(entity);
755: }
756: }
757:
758: }
759:
760:
767: private void drawEllipse(Point2D point, double oRadius, Graphics2D g2) {
768: Ellipse2D dot = new Ellipse2D.Double(point.getX() + oRadius / 2,
769: point.getY(), oRadius, oRadius);
770: g2.draw(dot);
771: }
772:
773:
781: private void drawMultipleEllipse(Point2D point, double boxWidth,
782: double oRadius, Graphics2D g2) {
783:
784: Ellipse2D dot1 = new Ellipse2D.Double(point.getX() - (boxWidth / 2)
785: + oRadius, point.getY(), oRadius, oRadius);
786: Ellipse2D dot2 = new Ellipse2D.Double(point.getX() + (boxWidth / 2),
787: point.getY(), oRadius, oRadius);
788: g2.draw(dot1);
789: g2.draw(dot2);
790: }
791:
792:
800: private void drawHighFarOut(double aRadius, Graphics2D g2, double xx,
801: double m) {
802: double side = aRadius * 2;
803: g2.draw(new Line2D.Double(xx - side, m + side, xx + side, m + side));
804: g2.draw(new Line2D.Double(xx - side, m + side, xx, m));
805: g2.draw(new Line2D.Double(xx + side, m + side, xx, m));
806: }
807:
808:
816: private void drawLowFarOut(double aRadius, Graphics2D g2, double xx,
817: double m) {
818: double side = aRadius * 2;
819: g2.draw(new Line2D.Double(xx - side, m - side, xx + side, m - side));
820: g2.draw(new Line2D.Double(xx - side, m - side, xx, m));
821: g2.draw(new Line2D.Double(xx + side, m - side, xx, m));
822: }
823:
824:
831: public boolean equals(Object obj) {
832: if (obj == this) {
833: return true;
834: }
835: if (!(obj instanceof BoxAndWhiskerRenderer)) {
836: return false;
837: }
838: if (!super.equals(obj)) {
839: return false;
840: }
841: BoxAndWhiskerRenderer that = (BoxAndWhiskerRenderer) obj;
842: if (!PaintUtilities.equal(this.artifactPaint, that.artifactPaint)) {
843: return false;
844: }
845: if (!(this.fillBox == that.fillBox)) {
846: return false;
847: }
848: if (!(this.itemMargin == that.itemMargin)) {
849: return false;
850: }
851: return true;
852: }
853:
854:
861: private void writeObject(ObjectOutputStream stream) throws IOException {
862: stream.defaultWriteObject();
863: SerialUtilities.writePaint(this.artifactPaint, stream);
864: }
865:
866:
874: private void readObject(ObjectInputStream stream)
875: throws IOException, ClassNotFoundException {
876: stream.defaultReadObject();
877: this.artifactPaint = SerialUtilities.readPaint(stream);
878: }
879:
880: }