1:
72:
73: package ;
74:
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: import ;
90: import ;
91: import ;
92:
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: import ;
110: import ;
111: import ;
112:
113:
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:
185: public void setBoxWidth(double width) {
186: if (width != this.boxWidth) {
187: this.boxWidth = width;
188: notifyListeners(new RendererChangeEvent(this));
189: }
190: }
191:
192:
197: public Paint getBoxPaint() {
198: return this.boxPaint;
199: }
200:
201:
207: public void setBoxPaint(Paint paint) {
208: this.boxPaint = paint;
209: notifyListeners(new RendererChangeEvent(this));
210: }
211:
212:
217: public boolean getFillBox() {
218: return this.fillBox;
219: }
220:
221:
227: public void setFillBox(boolean flag) {
228: this.fillBox = flag;
229: notifyListeners(new RendererChangeEvent(this));
230: }
231:
232:
238: public Paint getArtifactPaint() {
239: return this.artifactPaint;
240: }
241:
242:
248: public void setArtifactPaint(Paint artifactPaint) {
249: this.artifactPaint = artifactPaint;
250: }
251:
252:
270: public void drawItem(Graphics2D g2,
271: XYItemRendererState state,
272: Rectangle2D dataArea,
273: PlotRenderingInfo info,
274: XYPlot plot,
275: ValueAxis domainAxis,
276: ValueAxis rangeAxis,
277: XYDataset dataset,
278: int series,
279: int item,
280: CrosshairState crosshairState,
281: int pass) {
282:
283: PlotOrientation orientation = plot.getOrientation();
284:
285: if (orientation == PlotOrientation.HORIZONTAL) {
286: drawHorizontalItem(
287: g2, dataArea, info, plot, domainAxis, rangeAxis,
288: dataset, series, item, crosshairState, pass
289: );
290: }
291: else if (orientation == PlotOrientation.VERTICAL) {
292: drawVerticalItem(
293: g2, dataArea, info, plot, domainAxis, rangeAxis,
294: dataset, series, item, crosshairState, pass
295: );
296: }
297:
298: }
299:
300:
317: public void drawHorizontalItem(Graphics2D g2,
318: Rectangle2D dataArea,
319: PlotRenderingInfo info,
320: XYPlot plot,
321: ValueAxis domainAxis,
322: ValueAxis rangeAxis,
323: XYDataset dataset,
324: int series,
325: int item,
326: CrosshairState crosshairState,
327: int pass) {
328:
329:
330: EntityCollection entities = null;
331: if (info != null) {
332: entities = info.getOwner().getEntityCollection();
333: }
334:
335: BoxAndWhiskerXYDataset boxAndWhiskerData
336: = (BoxAndWhiskerXYDataset) dataset;
337:
338: Number x = boxAndWhiskerData.getX(series, item);
339: Number yMax = boxAndWhiskerData.getMaxRegularValue(series, item);
340: Number yMin = boxAndWhiskerData.getMinRegularValue(series, item);
341: Number yQ1Median = boxAndWhiskerData.getQ1Value(series, item);
342: Number yQ3Median = boxAndWhiskerData.getQ3Value(series, item);
343:
344: double xx = domainAxis.valueToJava2D(
345: x.doubleValue(), dataArea, plot.getDomainAxisEdge()
346: );
347:
348: RectangleEdge location = plot.getRangeAxisEdge();
349: double yyMax = rangeAxis.valueToJava2D(
350: yMax.doubleValue(), dataArea, location
351: );
352: double yyMin = rangeAxis.valueToJava2D(
353: yMin.doubleValue(), dataArea, location
354: );
355:
356: double yyQ1Median = rangeAxis.valueToJava2D(
357: yQ1Median.doubleValue(), dataArea, location
358: );
359: double yyQ3Median = rangeAxis.valueToJava2D(
360: yQ3Median.doubleValue(), dataArea, location
361: );
362:
363: double exactCandleWidth = getBoxWidth();
364: double thisCandleWidth = exactCandleWidth;
365: if (exactCandleWidth <= 0.0) {
366: int itemCount = boxAndWhiskerData.getItemCount(series);
367: exactCandleWidth = (dataArea.getHeight()) / itemCount * 4.5 / 7;
368: if (exactCandleWidth < 1) {
369: exactCandleWidth = 1;
370: }
371: thisCandleWidth = exactCandleWidth;
372: if (thisCandleWidth < 3) {
373: thisCandleWidth = 3;
374: }
375: }
376:
377: Stroke s = getItemStroke(series, item);
378:
379: g2.setStroke(s);
380:
381:
382: if ((yyMax > yyQ1Median) && (yyMax > yyQ3Median)) {
383: g2.draw(
384: new Line2D.Double(yyMax, xx, Math.max(yyQ1Median, yyQ3Median),
385: xx)
386: );
387: }
388:
389:
390: if ((yyMin < yyQ1Median) && (yyMin < yyQ3Median)) {
391: g2.draw(
392: new Line2D.Double(yyMin, xx, Math.min(yyQ1Median, yyQ3Median),
393: xx)
394: );
395: }
396:
397:
398:
399: Shape box = null;
400: if (yyQ1Median < yyQ3Median) {
401: box = new Rectangle2D.Double(
402: yyQ1Median, xx - thisCandleWidth / 2, yyQ3Median - yyQ1Median,
403: thisCandleWidth
404: );
405: }
406: else {
407: box = new Rectangle2D.Double(
408: yyQ3Median, xx - thisCandleWidth / 2, yyQ1Median - yyQ3Median,
409: thisCandleWidth
410: );
411: if (getBoxPaint() != null) {
412: g2.setPaint(getBoxPaint());
413: }
414: if (this.fillBox) {
415: g2.fill(box);
416: }
417: g2.draw(box);
418: }
419:
420:
421: if (entities != null) {
422: String tip = null;
423: XYToolTipGenerator generator = getToolTipGenerator(series, item);
424: if (generator != null) {
425: tip = generator.generateToolTip(dataset, series, item);
426: }
427: String url = null;
428: if (getURLGenerator() != null) {
429: url = getURLGenerator().generateURL(dataset, series, item);
430: }
431: XYItemEntity entity = new XYItemEntity(box, dataset, series, item,
432: tip, url);
433: entities.add(entity);
434: }
435:
436: }
437:
438:
455: public void drawVerticalItem(Graphics2D g2,
456: Rectangle2D dataArea,
457: PlotRenderingInfo info,
458: XYPlot plot,
459: ValueAxis domainAxis,
460: ValueAxis rangeAxis,
461: XYDataset dataset,
462: int series,
463: int item,
464: CrosshairState crosshairState,
465: int pass) {
466:
467:
468: EntityCollection entities = null;
469: if (info != null) {
470: entities = info.getOwner().getEntityCollection();
471: }
472:
473: BoxAndWhiskerXYDataset boxAndWhiskerData
474: = (BoxAndWhiskerXYDataset) dataset;
475:
476: Number x = boxAndWhiskerData.getX(series, item);
477: Number yMax = boxAndWhiskerData.getMaxRegularValue(series, item);
478: Number yMin = boxAndWhiskerData.getMinRegularValue(series, item);
479: Number yMedian = boxAndWhiskerData.getMedianValue(series, item);
480: Number yAverage = boxAndWhiskerData.getMeanValue(series, item);
481: Number yQ1Median = boxAndWhiskerData.getQ1Value(series, item);
482: Number yQ3Median = boxAndWhiskerData.getQ3Value(series, item);
483: List yOutliers = boxAndWhiskerData.getOutliers(series, item);
484:
485: double xx = domainAxis.valueToJava2D(x.doubleValue(), dataArea,
486: plot.getDomainAxisEdge());
487:
488: RectangleEdge location = plot.getRangeAxisEdge();
489: double yyMax = rangeAxis.valueToJava2D(yMax.doubleValue(), dataArea,
490: location);
491: double yyMin = rangeAxis.valueToJava2D(yMin.doubleValue(), dataArea,
492: location);
493: double yyMedian = rangeAxis.valueToJava2D(yMedian.doubleValue(),
494: dataArea, location);
495: double yyAverage = 0.0;
496: if (yAverage != null) {
497: yyAverage = rangeAxis.valueToJava2D(yAverage.doubleValue(),
498: dataArea, location);
499: }
500: double yyQ1Median = rangeAxis.valueToJava2D(yQ1Median.doubleValue(),
501: dataArea, location);
502: double yyQ3Median = rangeAxis.valueToJava2D(yQ3Median.doubleValue(),
503: dataArea, location);
504: double yyOutlier;
505:
506:
507: double exactBoxWidth = getBoxWidth();
508: double width = exactBoxWidth;
509: double dataAreaX = dataArea.getMaxX() - dataArea.getMinX();
510: double maxBoxPercent = 0.1;
511: double maxBoxWidth = dataAreaX * maxBoxPercent;
512: if (exactBoxWidth <= 0.0) {
513: int itemCount = boxAndWhiskerData.getItemCount(series);
514: exactBoxWidth = dataAreaX / itemCount * 4.5 / 7;
515: if (exactBoxWidth < 3) {
516: width = 3;
517: }
518: else if (exactBoxWidth > maxBoxWidth) {
519: width = maxBoxWidth;
520: }
521: else {
522: width = exactBoxWidth;
523: }
524: }
525:
526: Paint p = getBoxPaint();
527: if (p != null) {
528: g2.setPaint(p);
529: }
530: Stroke s = getItemStroke(series, item);
531:
532: g2.setStroke(s);
533:
534:
535: g2.draw(new Line2D.Double(xx, yyMax, xx, yyQ3Median));
536: g2.draw(new Line2D.Double(xx - width / 2, yyMax, xx + width / 2,
537: yyMax));
538:
539:
540: g2.draw(new Line2D.Double(xx, yyMin, xx, yyQ1Median));
541: g2.draw(new Line2D.Double(xx - width / 2, yyMin, xx + width / 2,
542: yyMin));
543:
544:
545: Shape box = null;
546: if (yyQ1Median > yyQ3Median) {
547: box = new Rectangle2D.Double(
548: xx - width / 2, yyQ3Median, width, yyQ1Median - yyQ3Median
549: );
550: }
551: else {
552: box = new Rectangle2D.Double(
553: xx - width / 2, yyQ1Median, width, yyQ3Median - yyQ1Median
554: );
555: }
556: if (this.fillBox) {
557: g2.fill(box);
558: }
559: g2.draw(box);
560:
561:
562: g2.setPaint(getArtifactPaint());
563: g2.draw(new Line2D.Double(xx - width / 2, yyMedian, xx + width / 2,
564: yyMedian));
565:
566: double aRadius = 0;
567: double oRadius = width / 3;
568:
569:
570: if (yAverage != null) {
571: aRadius = width / 4;
572: Ellipse2D.Double avgEllipse = new Ellipse2D.Double(
573: xx - aRadius, yyAverage - aRadius, aRadius * 2, aRadius * 2
574: );
575: g2.fill(avgEllipse);
576: g2.draw(avgEllipse);
577: }
578:
579: List outliers = new ArrayList();
580: OutlierListCollection outlierListCollection
581: = new OutlierListCollection();
582:
583:
587:
588: for (int i = 0; i < yOutliers.size(); i++) {
589: double outlier = ((Number) yOutliers.get(i)).doubleValue();
590: if (outlier > boxAndWhiskerData.getMaxOutlier(series,
591: item).doubleValue()) {
592: outlierListCollection.setHighFarOut(true);
593: }
594: else if (outlier < boxAndWhiskerData.getMinOutlier(series,
595: item).doubleValue()) {
596: outlierListCollection.setLowFarOut(true);
597: }
598: else if (outlier > boxAndWhiskerData.getMaxRegularValue(series,
599: item).doubleValue()) {
600: yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea,
601: location);
602: outliers.add(new Outlier(xx, yyOutlier, oRadius));
603: }
604: else if (outlier < boxAndWhiskerData.getMinRegularValue(series,
605: item).doubleValue()) {
606: yyOutlier = rangeAxis.valueToJava2D(outlier, dataArea,
607: location);
608: outliers.add(new Outlier(xx, yyOutlier, oRadius));
609: }
610: Collections.sort(outliers);
611: }
612:
613:
614:
615: for (Iterator iterator = outliers.iterator(); iterator.hasNext();) {
616: Outlier outlier = (Outlier) iterator.next();
617: outlierListCollection.add(outlier);
618: }
619:
620:
621: double maxAxisValue = rangeAxis.valueToJava2D(
622: rangeAxis.getUpperBound(), dataArea, location
623: ) + aRadius;
624: double minAxisValue = rangeAxis.valueToJava2D(
625: rangeAxis.getLowerBound(), dataArea, location
626: ) - aRadius;
627:
628:
629: for (Iterator iterator = outlierListCollection.iterator();
630: iterator.hasNext();) {
631: OutlierList list = (OutlierList) iterator.next();
632: Outlier outlier = list.getAveragedOutlier();
633: Point2D point = outlier.getPoint();
634:
635: if (list.isMultiple()) {
636: drawMultipleEllipse(point, width, oRadius, g2);
637: }
638: else {
639: drawEllipse(point, oRadius, g2);
640: }
641: }
642:
643:
644: if (outlierListCollection.isHighFarOut()) {
645: drawHighFarOut(aRadius, g2, xx, maxAxisValue);
646: }
647:
648: if (outlierListCollection.isLowFarOut()) {
649: drawLowFarOut(aRadius, g2, xx, minAxisValue);
650: }
651:
652:
653: if (entities != null) {
654: String tip = null;
655: XYToolTipGenerator generator = getToolTipGenerator(series, item);
656: if (generator != null) {
657: tip = generator.generateToolTip(dataset, series, item);
658: }
659: String url = null;
660: if (getURLGenerator() != null) {
661: url = getURLGenerator().generateURL(dataset, series, item);
662: }
663: XYItemEntity entity = new XYItemEntity(box, dataset, series, item,
664: tip, url);
665: entities.add(entity);
666: }
667:
668: }
669:
670:
677: protected void drawEllipse(Point2D point, double oRadius, Graphics2D g2) {
678: Ellipse2D.Double dot = new Ellipse2D.Double(
679: point.getX() + oRadius / 2, point.getY(), oRadius, oRadius
680: );
681: g2.draw(dot);
682: }
683:
684:
692: protected void drawMultipleEllipse(Point2D point, double boxWidth,
693: double oRadius, Graphics2D g2) {
694:
695: Ellipse2D.Double dot1 = new Ellipse2D.Double(
696: point.getX() - (boxWidth / 2) + oRadius, point.getY(), oRadius,
697: oRadius
698: );
699: Ellipse2D.Double dot2 = new Ellipse2D.Double(
700: point.getX() + (boxWidth / 2), point.getY(), oRadius, oRadius
701: );
702: g2.draw(dot1);
703: g2.draw(dot2);
704:
705: }
706:
707:
715: protected void drawHighFarOut(double aRadius, Graphics2D g2, double xx,
716: double m) {
717: double side = aRadius * 2;
718: g2.draw(new Line2D.Double(xx - side, m + side, xx + side, m + side));
719: g2.draw(new Line2D.Double(xx - side, m + side, xx, m));
720: g2.draw(new Line2D.Double(xx + side, m + side, xx, m));
721: }
722:
723:
731: protected void drawLowFarOut(double aRadius, Graphics2D g2, double xx,
732: double m) {
733: double side = aRadius * 2;
734: g2.draw(new Line2D.Double(xx - side, m - side, xx + side, m - side));
735: g2.draw(new Line2D.Double(xx - side, m - side, xx, m));
736: g2.draw(new Line2D.Double(xx + side, m - side, xx, m));
737: }
738:
739:
746: public boolean equals(Object obj) {
747: if (obj == this) {
748: return true;
749: }
750: if (!(obj instanceof XYBoxAndWhiskerRenderer)) {
751: return false;
752: }
753: if (!super.equals(obj)) {
754: return false;
755: }
756: XYBoxAndWhiskerRenderer that = (XYBoxAndWhiskerRenderer) obj;
757: if (this.boxWidth != that.getBoxWidth()) {
758: return false;
759: }
760: if (!PaintUtilities.equal(this.boxPaint, that.boxPaint)) {
761: return false;
762: }
763: if (!PaintUtilities.equal(this.artifactPaint, that.artifactPaint)) {
764: return false;
765: }
766: if (this.fillBox != that.fillBox) {
767: return false;
768: }
769: return true;
770:
771: }
772:
773:
780: private void writeObject(ObjectOutputStream stream) throws IOException {
781:
782: stream.defaultWriteObject();
783: SerialUtilities.writePaint(this.boxPaint, stream);
784: SerialUtilities.writePaint(this.artifactPaint, stream);
785:
786: }
787:
788:
796: private void readObject(ObjectInputStream stream)
797: throws IOException, ClassNotFoundException {
798:
799: stream.defaultReadObject();
800: this.boxPaint = SerialUtilities.readPaint(stream);
801: this.artifactPaint = SerialUtilities.readPaint(stream);
802:
803: }
804:
805:
812: public Object clone() throws CloneNotSupportedException {
813: return super.clone();
814: }
815:
816: }