1:
62:
63: package ;
64:
65: import ;
66: import ;
67: import ;
68: import ;
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:
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: import ;
96: import ;
97: import ;
98: import ;
99: import ;
100: import ;
101: import ;
102:
103:
106: public class BoxAndWhiskerRenderer extends AbstractCategoryItemRenderer
107: implements Cloneable, PublicCloneable,
108: Serializable {
109:
110:
111: private static final long serialVersionUID = 632027470694481177L;
112:
113:
114: private transient Paint artifactPaint;
115:
116:
117: private boolean fillBox;
118:
119:
120: private double itemMargin;
121:
122:
125: public BoxAndWhiskerRenderer() {
126: this.artifactPaint = Color.black;
127: this.fillBox = true;
128: this.itemMargin = 0.20;
129: }
130:
131:
136: public Paint getArtifactPaint() {
137: return this.artifactPaint;
138: }
139:
140:
145: public void setArtifactPaint(Paint paint) {
146: this.artifactPaint = paint;
147: }
148:
149:
154: public boolean getFillBox() {
155: return this.fillBox;
156: }
157:
158:
164: public void setFillBox(boolean flag) {
165: this.fillBox = flag;
166: notifyListeners(new RendererChangeEvent(this));
167: }
168:
169:
175: public double getItemMargin() {
176: return this.itemMargin;
177: }
178:
179:
184: public void setItemMargin(double margin) {
185: this.itemMargin = margin;
186: }
187:
188:
196: public LegendItem getLegendItem(int datasetIndex, int series) {
197:
198: CategoryPlot cp = getPlot();
199: if (cp == null) {
200: return null;
201: }
202:
203: CategoryDataset dataset;
204: dataset = cp.getDataset(datasetIndex);
205: String label = getLegendItemLabelGenerator().generateLabel(
206: dataset, series
207: );
208: String description = label;
209: String toolTipText = null;
210: if (getLegendItemToolTipGenerator() != null) {
211: toolTipText = getLegendItemToolTipGenerator().generateLabel(
212: dataset, series
213: );
214: }
215: String urlText = null;
216: if (getLegendItemURLGenerator() != null) {
217: urlText = getLegendItemURLGenerator().generateLabel(
218: dataset, series
219: );
220: }
221: Shape shape = new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0);
222: Paint paint = getSeriesPaint(series);
223: Paint outlinePaint = getSeriesOutlinePaint(series);
224: Stroke outlineStroke = getSeriesOutlineStroke(series);
225:
226: return new LegendItem(label, description, toolTipText, urlText,
227: shape, paint, outlineStroke, outlinePaint);
228:
229: }
230:
231:
243: public CategoryItemRendererState initialise(Graphics2D g2,
244: Rectangle2D dataArea,
245: CategoryPlot plot,
246: int rendererIndex,
247: PlotRenderingInfo info) {
248:
249: CategoryItemRendererState state = super.initialise(
250: g2, dataArea, plot, rendererIndex, info
251: );
252:
253:
254: CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
255: CategoryDataset dataset = plot.getDataset(rendererIndex);
256: if (dataset != null) {
257: int columns = dataset.getColumnCount();
258: int rows = dataset.getRowCount();
259: double space = 0.0;
260: PlotOrientation orientation = plot.getOrientation();
261: if (orientation == PlotOrientation.HORIZONTAL) {
262: space = dataArea.getHeight();
263: }
264: else if (orientation == PlotOrientation.VERTICAL) {
265: space = dataArea.getWidth();
266: }
267: double categoryMargin = 0.0;
268: double currentItemMargin = 0.0;
269: if (columns > 1) {
270: categoryMargin = domainAxis.getCategoryMargin();
271: }
272: if (rows > 1) {
273: currentItemMargin = getItemMargin();
274: }
275: double used = space * (1 - domainAxis.getLowerMargin()
276: - domainAxis.getUpperMargin()
277: - categoryMargin - currentItemMargin);
278: if ((rows * columns) > 0) {
279: state.setBarWidth(
280: used / (dataset.getColumnCount() * dataset.getRowCount())
281: );
282: }
283: else {
284: state.setBarWidth(used);
285: }
286: }
287:
288: return state;
289:
290: }
291:
292:
306: public void drawItem(Graphics2D g2,
307: CategoryItemRendererState state,
308: Rectangle2D dataArea,
309: CategoryPlot plot,
310: CategoryAxis domainAxis,
311: ValueAxis rangeAxis,
312: CategoryDataset dataset,
313: int row,
314: int column,
315: int pass) {
316:
317: if (!(dataset instanceof BoxAndWhiskerCategoryDataset)) {
318: throw new IllegalArgumentException(
319: "BoxAndWhiskerRenderer.drawItem() : the data should be of type "
320: + "BoxAndWhiskerCategoryDataset only."
321: );
322: }
323:
324: PlotOrientation orientation = plot.getOrientation();
325:
326: if (orientation == PlotOrientation.HORIZONTAL) {
327: drawHorizontalItem(
328: g2, state, dataArea, plot, domainAxis, rangeAxis,
329: dataset, row, column
330: );
331: }
332: else if (orientation == PlotOrientation.VERTICAL) {
333: drawVerticalItem(
334: g2, state, dataArea, plot, domainAxis, rangeAxis,
335: dataset, row, column
336: );
337: }
338:
339: }
340:
341:
356: public void drawHorizontalItem(Graphics2D g2,
357: CategoryItemRendererState state,
358: Rectangle2D dataArea,
359: CategoryPlot plot,
360: CategoryAxis domainAxis,
361: ValueAxis rangeAxis,
362: CategoryDataset dataset,
363: int row,
364: int column) {
365:
366: BoxAndWhiskerCategoryDataset bawDataset
367: = (BoxAndWhiskerCategoryDataset) dataset;
368:
369: double categoryEnd = domainAxis.getCategoryEnd(
370: column, getColumnCount(), dataArea, plot.getDomainAxisEdge()
371: );
372: double categoryStart = domainAxis.getCategoryStart(
373: column, getColumnCount(), dataArea, plot.getDomainAxisEdge()
374: );
375: double categoryWidth = Math.abs(categoryEnd - categoryStart);
376:
377: double yy = categoryStart;
378: int seriesCount = getRowCount();
379: int categoryCount = getColumnCount();
380:
381: if (seriesCount > 1) {
382: double seriesGap = dataArea.getWidth() * getItemMargin()
383: / (categoryCount * (seriesCount - 1));
384: double usedWidth = (state.getBarWidth() * seriesCount)
385: + (seriesGap * (seriesCount - 1));
386:
387:
388: double offset = (categoryWidth - usedWidth) / 2;
389: yy = yy + offset + (row * (state.getBarWidth() + seriesGap));
390: }
391: else {
392:
393:
394: double offset = (categoryWidth - state.getBarWidth()) / 2;
395: yy = yy + offset;
396: }
397:
398: Paint p = getItemPaint(row, column);
399: if (p != null) {
400: g2.setPaint(p);
401: }
402: Stroke s = getItemStroke(row, column);
403: g2.setStroke(s);
404:
405: RectangleEdge location = plot.getRangeAxisEdge();
406:
407: Number xQ1 = bawDataset.getQ1Value(row, column);
408: Number xQ3 = bawDataset.getQ3Value(row, column);
409: Number xMax = bawDataset.getMaxRegularValue(row, column);
410: Number xMin = bawDataset.getMinRegularValue(row, column);
411:
412: Shape box = null;
413: if (xQ1 != null && xQ3 != null && xMax != null && xMin != null) {
414:
415: double xxQ1 = rangeAxis.valueToJava2D(
416: xQ1.doubleValue(), dataArea, location
417: );
418: double xxQ3 = rangeAxis.valueToJava2D(
419: xQ3.doubleValue(), dataArea, location
420: );
421: double xxMax = rangeAxis.valueToJava2D(
422: xMax.doubleValue(), dataArea, location
423: );
424: double xxMin = rangeAxis.valueToJava2D(
425: xMin.doubleValue(), dataArea, location
426: );
427: double yymid = yy + state.getBarWidth() / 2.0;
428:
429:
430: g2.draw(new Line2D.Double(xxMax, yymid, xxQ3, yymid));
431: g2.draw(
432: new Line2D.Double(xxMax, yy, xxMax, yy + state.getBarWidth())
433: );
434:
435:
436: g2.draw(new Line2D.Double(xxMin, yymid, xxQ1, yymid));
437: g2.draw(
438: new Line2D.Double(xxMin, yy, xxMin, yy + state.getBarWidth())
439: );
440:
441:
442: box = new Rectangle2D.Double(
443: Math.min(xxQ1, xxQ3), yy, Math.abs(xxQ1 - xxQ3),
444: state.getBarWidth()
445: );
446: if (this.fillBox) {
447: g2.fill(box);
448: }
449: g2.draw(box);
450:
451: }
452:
453: g2.setPaint(this.artifactPaint);
454: double aRadius = 0;
455:
456:
457: Number xMean = bawDataset.getMeanValue(row, column);
458: if (xMean != null) {
459: double xxMean = rangeAxis.valueToJava2D(
460: xMean.doubleValue(), dataArea, location
461: );
462: aRadius = state.getBarWidth() / 4;
463: Ellipse2D.Double avgEllipse = new Ellipse2D.Double(
464: xxMean - aRadius, yy + aRadius, aRadius * 2, aRadius * 2
465: );
466: g2.fill(avgEllipse);
467: g2.draw(avgEllipse);
468: }
469:
470:
471: Number xMedian = bawDataset.getMedianValue(row, column);
472: if (xMedian != null) {
473: double xxMedian = rangeAxis.valueToJava2D(
474: xMedian.doubleValue(), dataArea, location
475: );
476: g2.draw(
477: new Line2D.Double(
478: xxMedian, yy, xxMedian, yy + state.getBarWidth()
479: )
480: );
481: }
482:
483:
484: if (state.getInfo() != 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: }
499: CategoryItemEntity entity = new CategoryItemEntity(
500: box, tip, url, dataset, row, dataset.getColumnKey(column),
501: column
502: );
503: entities.add(entity);
504: }
505: }
506:
507: }
508:
509:
524: public void drawVerticalItem(Graphics2D g2,
525: CategoryItemRendererState state,
526: Rectangle2D dataArea,
527: CategoryPlot plot,
528: CategoryAxis domainAxis,
529: ValueAxis rangeAxis,
530: CategoryDataset dataset,
531: int row,
532: int column) {
533:
534: BoxAndWhiskerCategoryDataset bawDataset
535: = (BoxAndWhiskerCategoryDataset) dataset;
536:
537: double categoryEnd = domainAxis.getCategoryEnd(
538: column, getColumnCount(), dataArea, plot.getDomainAxisEdge()
539: );
540: double categoryStart = domainAxis.getCategoryStart(
541: column, getColumnCount(), dataArea, plot.getDomainAxisEdge()
542: );
543: double categoryWidth = categoryEnd - categoryStart;
544:
545: double xx = categoryStart;
546: int seriesCount = getRowCount();
547: int categoryCount = getColumnCount();
548:
549: if (seriesCount > 1) {
550: double seriesGap = dataArea.getWidth() * getItemMargin()
551: / (categoryCount * (seriesCount - 1));
552: double usedWidth = (state.getBarWidth() * seriesCount)
553: + (seriesGap * (seriesCount - 1));
554:
555:
556: double offset = (categoryWidth - usedWidth) / 2;
557: xx = xx + offset + (row * (state.getBarWidth() + seriesGap));
558: }
559: else {
560:
561:
562: double offset = (categoryWidth - state.getBarWidth()) / 2;
563: xx = xx + offset;
564: }
565:
566: double yyAverage = 0.0;
567: double yyOutlier;
568:
569: Paint p = getItemPaint(row, column);
570: if (p != null) {
571: g2.setPaint(p);
572: }
573: Stroke s = getItemStroke(row, column);
574: g2.setStroke(s);
575:
576: double aRadius = 0;
577:
578: RectangleEdge location = plot.getRangeAxisEdge();
579:
580: Number yQ1 = bawDataset.getQ1Value(row, column);
581: Number yQ3 = bawDataset.getQ3Value(row, column);
582: Number yMax = bawDataset.getMaxRegularValue(row, column);
583: Number yMin = bawDataset.getMinRegularValue(row, column);
584: Shape box = null;
585: if (yQ1 != null && yQ3 != null && yMax != null && yMin != null) {
586:
587: double yyQ1 = rangeAxis.valueToJava2D(
588: yQ1.doubleValue(), dataArea, location
589: );
590: double yyQ3 = rangeAxis.valueToJava2D(
591: yQ3.doubleValue(), dataArea, location
592: );
593: double yyMax = rangeAxis.valueToJava2D(
594: yMax.doubleValue(), dataArea, location
595: );
596: double yyMin = rangeAxis.valueToJava2D(
597: yMin.doubleValue(), dataArea, location
598: );
599: double xxmid = xx + state.getBarWidth() / 2.0;
600:
601:
602: g2.draw(new Line2D.Double(xxmid, yyMax, xxmid, yyQ3));
603: g2.draw(
604: new Line2D.Double(xx, yyMax, xx + state.getBarWidth(), yyMax)
605: );
606:
607:
608: g2.draw(new Line2D.Double(xxmid, yyMin, xxmid, yyQ1));
609: g2.draw(
610: new Line2D.Double(xx, yyMin, xx + state.getBarWidth(), yyMin)
611: );
612:
613:
614: box = new Rectangle2D.Double(
615: xx, Math.min(yyQ1, yyQ3), state.getBarWidth(),
616: Math.abs(yyQ1 - yyQ3)
617: );
618: if (this.fillBox) {
619: g2.fill(box);
620: }
621: g2.draw(box);
622:
623: }
624:
625: g2.setPaint(this.artifactPaint);
626:
627:
628: Number yMean = bawDataset.getMeanValue(row, column);
629: if (yMean != null) {
630: yyAverage = rangeAxis.valueToJava2D(
631: yMean.doubleValue(), dataArea, location
632: );
633: aRadius = state.getBarWidth() / 4;
634: Ellipse2D.Double avgEllipse = new Ellipse2D.Double(
635: xx + aRadius, yyAverage - aRadius, aRadius * 2, aRadius * 2
636: );
637: g2.fill(avgEllipse);
638: g2.draw(avgEllipse);
639: }
640:
641:
642: Number yMedian = bawDataset.getMedianValue(row, column);
643: if (yMedian != null) {
644: double yyMedian = rangeAxis.valueToJava2D(
645: yMedian.doubleValue(), dataArea, location
646: );
647: g2.draw(
648: new Line2D.Double(
649: xx, yyMedian, xx + state.getBarWidth(), yyMedian
650: )
651: );
652: }
653:
654:
655: double maxAxisValue = rangeAxis.valueToJava2D(
656: rangeAxis.getUpperBound(), dataArea, location
657: ) + aRadius;
658: double minAxisValue = rangeAxis.valueToJava2D(
659: rangeAxis.getLowerBound(), dataArea, location
660: ) - aRadius;
661:
662: g2.setPaint(p);
663:
664:
665: double oRadius = state.getBarWidth() / 3;
666: List outliers = new ArrayList();
667: OutlierListCollection outlierListCollection
668: = new OutlierListCollection();
669:
670:
671:
672:
673: List yOutliers = bawDataset.getOutliers(row, column);
674: if (yOutliers != null) {
675: for (int i = 0; i < yOutliers.size(); i++) {
676: double outlier = ((Number) yOutliers.get(i)).doubleValue();
677: Number minOutlier = bawDataset.getMinOutlier(row, column);
678: Number maxOutlier = bawDataset.getMaxOutlier(row, column);
679: Number minRegular = bawDataset.getMinRegularValue(row, column);
680: Number maxRegular = bawDataset.getMaxRegularValue(row, column);
681: if (outlier > maxOutlier.doubleValue()) {
682: outlierListCollection.setHighFarOut(true);
683: }
684: else if (outlier < minOutlier.doubleValue()) {
685: outlierListCollection.setLowFarOut(true);
686: }
687: else if (outlier > maxRegular.doubleValue()) {
688: yyOutlier = rangeAxis.valueToJava2D(
689: outlier, dataArea, location
690: );
691: outliers.add(
692: new Outlier(
693: xx + state.getBarWidth() / 2.0, yyOutlier, oRadius
694: )
695: );
696: }
697: else if (outlier < minRegular.doubleValue()) {
698: yyOutlier = rangeAxis.valueToJava2D(
699: outlier, dataArea, location
700: );
701: outliers.add(
702: new Outlier(
703: xx + state.getBarWidth() / 2.0, yyOutlier, oRadius
704: )
705: );
706: }
707: Collections.sort(outliers);
708: }
709:
710:
711:
712: for (Iterator iterator = outliers.iterator(); iterator.hasNext();) {
713: Outlier outlier = (Outlier) iterator.next();
714: outlierListCollection.add(outlier);
715: }
716:
717: for (Iterator iterator = outlierListCollection.iterator();
718: iterator.hasNext();) {
719: OutlierList list = (OutlierList) iterator.next();
720: Outlier outlier = list.getAveragedOutlier();
721: Point2D point = outlier.getPoint();
722:
723: if (list.isMultiple()) {
724: drawMultipleEllipse(
725: point, state.getBarWidth(), oRadius, g2
726: );
727: }
728: else {
729: drawEllipse(point, oRadius, g2);
730: }
731: }
732:
733:
734: if (outlierListCollection.isHighFarOut()) {
735: drawHighFarOut(
736: aRadius / 2.0, g2, xx + state.getBarWidth() / 2.0,
737: maxAxisValue
738: );
739: }
740:
741: if (outlierListCollection.isLowFarOut()) {
742: drawLowFarOut(
743: aRadius / 2.0, g2, xx + state.getBarWidth() / 2.0,
744: minAxisValue
745: );
746: }
747: }
748:
749: if (state.getInfo() != null) {
750: EntityCollection entities = state.getEntityCollection();
751: if (entities != null) {
752: String tip = null;
753: CategoryToolTipGenerator tipster
754: = getToolTipGenerator(row, column);
755: if (tipster != null) {
756: tip = tipster.generateToolTip(dataset, row, column);
757: }
758: String url = null;
759: if (getItemURLGenerator(row, column) != null) {
760: url = getItemURLGenerator(row, column).generateURL(
761: dataset, row, column
762: );
763: }
764: CategoryItemEntity entity = new CategoryItemEntity(
765: box, tip, url, dataset, row, dataset.getColumnKey(column),
766: column
767: );
768: entities.add(entity);
769: }
770: }
771:
772: }
773:
774:
781: private void drawEllipse(Point2D point, double oRadius, Graphics2D g2) {
782: Ellipse2D dot = new Ellipse2D.Double(
783: point.getX() + oRadius / 2, point.getY(), oRadius, oRadius
784: );
785: g2.draw(dot);
786: }
787:
788:
796: private void drawMultipleEllipse(Point2D point, double boxWidth,
797: double oRadius, Graphics2D g2) {
798:
799: Ellipse2D dot1 = new Ellipse2D.Double(
800: point.getX() - (boxWidth / 2) + oRadius, point.getY(),
801: oRadius, oRadius
802: );
803: Ellipse2D dot2 = new Ellipse2D.Double(
804: point.getX() + (boxWidth / 2), point.getY(), oRadius, oRadius
805: );
806: g2.draw(dot1);
807: g2.draw(dot2);
808: }
809:
810:
818: private void drawHighFarOut(double aRadius, Graphics2D g2, double xx,
819: double m) {
820: double side = aRadius * 2;
821: g2.draw(new Line2D.Double(xx - side, m + side, xx + side, m + side));
822: g2.draw(new Line2D.Double(xx - side, m + side, xx, m));
823: g2.draw(new Line2D.Double(xx + side, m + side, xx, m));
824: }
825:
826:
834: private void drawLowFarOut(double aRadius, Graphics2D g2, double xx,
835: double m) {
836: double side = aRadius * 2;
837: g2.draw(new Line2D.Double(xx - side, m - side, xx + side, m - side));
838: g2.draw(new Line2D.Double(xx - side, m - side, xx, m));
839: g2.draw(new Line2D.Double(xx + side, m - side, xx, m));
840: }
841:
842:
849: public boolean equals(Object obj) {
850: if (obj == this) {
851: return true;
852: }
853: if (!(obj instanceof BoxAndWhiskerRenderer)) {
854: return false;
855: }
856: if (!super.equals(obj)) {
857: return false;
858: }
859: BoxAndWhiskerRenderer that = (BoxAndWhiskerRenderer) obj;
860: if (!PaintUtilities.equal(this.artifactPaint, that.artifactPaint)) {
861: return false;
862: }
863: if (!(this.fillBox == that.fillBox)) {
864: return false;
865: }
866: if (!(this.itemMargin == that.itemMargin)) {
867: return false;
868: }
869: return true;
870: }
871:
872:
879: private void writeObject(ObjectOutputStream stream) throws IOException {
880: stream.defaultWriteObject();
881: SerialUtilities.writePaint(this.artifactPaint, stream);
882: }
883:
884:
892: private void readObject(ObjectInputStream stream)
893: throws IOException, ClassNotFoundException {
894: stream.defaultReadObject();
895: this.artifactPaint = SerialUtilities.readPaint(stream);
896: }
897:
898: }