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:
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:
102:
111: public class XYDifferenceRenderer extends AbstractXYItemRenderer
112: implements XYItemRenderer,
113: Cloneable,
114: PublicCloneable,
115: Serializable {
116:
117:
118: private static final long serialVersionUID = -8447915602375584857L;
119:
120:
121: private transient Paint positivePaint;
122:
123:
124: private transient Paint negativePaint;
125:
126:
127: private boolean shapesVisible;
128:
129:
130: private transient Shape legendLine;
131:
132:
141: private boolean roundXCoordinates;
142:
143:
146: public XYDifferenceRenderer() {
147: this(Color.green, Color.red, false);
148: }
149:
150:
159: public XYDifferenceRenderer(Paint positivePaint, Paint negativePaint,
160: boolean shapes) {
161: if (positivePaint == null) {
162: throw new IllegalArgumentException(
163: "Null 'positivePaint' argument.");
164: }
165: if (negativePaint == null) {
166: throw new IllegalArgumentException(
167: "Null 'negativePaint' argument.");
168: }
169: this.positivePaint = positivePaint;
170: this.negativePaint = negativePaint;
171: this.shapesVisible = shapes;
172: this.legendLine = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
173: this.roundXCoordinates = false;
174: }
175:
176:
183: public Paint getPositivePaint() {
184: return this.positivePaint;
185: }
186:
187:
194: public void setPositivePaint(Paint paint) {
195: if (paint == null) {
196: throw new IllegalArgumentException("Null 'paint' argument.");
197: }
198: this.positivePaint = paint;
199: notifyListeners(new RendererChangeEvent(this));
200: }
201:
202:
209: public Paint getNegativePaint() {
210: return this.negativePaint;
211: }
212:
213:
220: public void setNegativePaint(Paint paint) {
221: if (paint == null) {
222: throw new IllegalArgumentException("Null 'paint' argument.");
223: }
224: this.negativePaint = paint;
225: notifyListeners(new RendererChangeEvent(this));
226: }
227:
228:
236: public boolean getShapesVisible() {
237: return this.shapesVisible;
238: }
239:
240:
248: public void setShapesVisible(boolean flag) {
249: this.shapesVisible = flag;
250: notifyListeners(new RendererChangeEvent(this));
251: }
252:
253:
260: public Shape getLegendLine() {
261: return this.legendLine;
262: }
263:
264:
272: public void setLegendLine(Shape line) {
273: if (line == null) {
274: throw new IllegalArgumentException("Null 'line' argument.");
275: }
276: this.legendLine = line;
277: notifyListeners(new RendererChangeEvent(this));
278: }
279:
280:
290: public boolean getRoundXCoordinates() {
291: return this.roundXCoordinates;
292: }
293:
294:
305: public void setRoundXCoordinates(boolean round) {
306: this.roundXCoordinates = round;
307: notifyListeners(new RendererChangeEvent(this));
308: }
309:
310:
326: public XYItemRendererState initialise(Graphics2D g2,
327: Rectangle2D dataArea,
328: XYPlot plot,
329: XYDataset data,
330: PlotRenderingInfo info) {
331:
332: return super.initialise(g2, dataArea, plot, data, info);
333:
334: }
335:
336:
342: public int getPassCount() {
343: return 2;
344: }
345:
346:
364: public void drawItem(Graphics2D g2,
365: XYItemRendererState state,
366: Rectangle2D dataArea,
367: PlotRenderingInfo info,
368: XYPlot plot,
369: ValueAxis domainAxis,
370: ValueAxis rangeAxis,
371: XYDataset dataset,
372: int series,
373: int item,
374: CrosshairState crosshairState,
375: int pass) {
376:
377: if (pass == 0) {
378: drawItemPass0(g2, dataArea, info, plot, domainAxis, rangeAxis,
379: dataset, series, item, crosshairState);
380: }
381: else if (pass == 1) {
382: drawItemPass1(g2, dataArea, info, plot, domainAxis, rangeAxis,
383: dataset, series, item, crosshairState);
384: }
385:
386: }
387:
388:
404: protected void drawItemPass0(Graphics2D g2,
405: Rectangle2D dataArea,
406: PlotRenderingInfo info,
407: XYPlot plot,
408: ValueAxis domainAxis,
409: ValueAxis rangeAxis,
410: XYDataset dataset,
411: int series,
412: int item,
413: CrosshairState crosshairState) {
414:
415: if (series == 0) {
416:
417: PlotOrientation orientation = plot.getOrientation();
418: RectangleEdge domainAxisLocation = plot.getDomainAxisEdge();
419: RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();
420:
421: double y0 = dataset.getYValue(0, item);
422: double x1 = dataset.getXValue(1, item);
423: double y1 = dataset.getYValue(1, item);
424:
425: double transY0 = rangeAxis.valueToJava2D(y0, dataArea,
426: rangeAxisLocation);
427: double transX1 = domainAxis.valueToJava2D(x1, dataArea,
428: domainAxisLocation);
429: if (this.roundXCoordinates) {
430: transX1 = Math.rint(transX1);
431: }
432: double transY1 = rangeAxis.valueToJava2D(y1, dataArea,
433: rangeAxisLocation);
434:
435: if (item > 0) {
436: double prevx0 = dataset.getXValue(0, item - 1);
437: double prevy0 = dataset.getYValue(0, item - 1);
438: double prevy1 = dataset.getYValue(1, item - 1);
439:
440: double prevtransX0 = domainAxis.valueToJava2D(prevx0, dataArea,
441: domainAxisLocation);
442: if (this.roundXCoordinates) {
443: prevtransX0 = Math.rint(prevtransX0);
444: }
445: double prevtransY0 = rangeAxis.valueToJava2D(prevy0, dataArea,
446: rangeAxisLocation);
447: double prevtransY1 = rangeAxis.valueToJava2D(prevy1, dataArea,
448: rangeAxisLocation);
449:
450: Shape positive = getPositiveArea((float) prevtransX0,
451: (float) prevtransY0, (float) prevtransY1,
452: (float) transX1, (float) transY0, (float) transY1,
453: orientation);
454: if (positive != null) {
455: g2.setPaint(getPositivePaint());
456: g2.fill(positive);
457: }
458:
459: Shape negative = getNegativeArea((float) prevtransX0,
460: (float) prevtransY0, (float) prevtransY1,
461: (float) transX1, (float) transY0, (float) transY1,
462: orientation);
463:
464: if (negative != null) {
465: g2.setPaint(getNegativePaint());
466: g2.fill(negative);
467: }
468: }
469: }
470:
471: }
472:
473:
491: protected void drawItemPass1(Graphics2D g2,
492: Rectangle2D dataArea,
493: PlotRenderingInfo info,
494: XYPlot plot,
495: ValueAxis domainAxis,
496: ValueAxis rangeAxis,
497: XYDataset dataset,
498: int series,
499: int item,
500: CrosshairState crosshairState) {
501:
502: Shape entityArea0 = null;
503: Shape entityArea1 = null;
504: EntityCollection entities = null;
505: if (info != null) {
506: entities = info.getOwner().getEntityCollection();
507: }
508:
509: Paint seriesPaint = getItemPaint(series, item);
510: Stroke seriesStroke = getItemStroke(series, item);
511: g2.setPaint(seriesPaint);
512: g2.setStroke(seriesStroke);
513:
514: if (series == 0) {
515:
516: PlotOrientation orientation = plot.getOrientation();
517: RectangleEdge domainAxisLocation = plot.getDomainAxisEdge();
518: RectangleEdge rangeAxisLocation = plot.getRangeAxisEdge();
519:
520: double x0 = dataset.getXValue(0, item);
521: double y0 = dataset.getYValue(0, item);
522: double x1 = dataset.getXValue(1, item);
523: double y1 = dataset.getYValue(1, item);
524:
525: double transX0 = domainAxis.valueToJava2D(x0, dataArea,
526: domainAxisLocation);
527: double transY0 = rangeAxis.valueToJava2D(y0, dataArea,
528: rangeAxisLocation);
529: double transX1 = domainAxis.valueToJava2D(x1, dataArea,
530: domainAxisLocation);
531: double transY1 = rangeAxis.valueToJava2D(y1, dataArea,
532: rangeAxisLocation);
533:
534: if (item > 0) {
535:
536: double prevx0 = dataset.getXValue(0, item - 1);
537: double prevy0 = dataset.getYValue(0, item - 1);
538: double prevx1 = dataset.getXValue(1, item - 1);
539: double prevy1 = dataset.getYValue(1, item - 1);
540:
541: double prevtransX0 = domainAxis.valueToJava2D(prevx0, dataArea,
542: domainAxisLocation);
543: double prevtransY0 = rangeAxis.valueToJava2D(prevy0, dataArea,
544: rangeAxisLocation);
545: double prevtransX1 = domainAxis.valueToJava2D(prevx1, dataArea,
546: domainAxisLocation);
547: double prevtransY1 = rangeAxis.valueToJava2D(prevy1, dataArea,
548: rangeAxisLocation);
549:
550: Line2D line0 = null;
551: Line2D line1 = null;
552: if (orientation == PlotOrientation.HORIZONTAL) {
553: line0 = new Line2D.Double(transY0, transX0, prevtransY0,
554: prevtransX0);
555: line1 = new Line2D.Double(transY1, transX1, prevtransY1,
556: prevtransX1);
557: }
558: else if (orientation == PlotOrientation.VERTICAL) {
559: line0 = new Line2D.Double(transX0, transY0, prevtransX0,
560: prevtransY0);
561: line1 = new Line2D.Double(transX1, transY1, prevtransX1,
562: prevtransY1);
563: }
564: if (line0 != null && line0.intersects(dataArea)) {
565: g2.setPaint(getItemPaint(series, item));
566: g2.setStroke(getItemStroke(series, item));
567: g2.draw(line0);
568: }
569: if (line1 != null && line1.intersects(dataArea)) {
570: g2.setPaint(getItemPaint(1, item));
571: g2.setStroke(getItemStroke(1, item));
572: g2.draw(line1);
573: }
574: }
575:
576: if (getShapesVisible()) {
577: Shape shape0 = getItemShape(series, item);
578: if (orientation == PlotOrientation.HORIZONTAL) {
579: shape0 = ShapeUtilities.createTranslatedShape(shape0,
580: transY0, transX0);
581: }
582: else {
583: shape0 = ShapeUtilities.createTranslatedShape(shape0,
584: transX0, transY0);
585: }
586: if (shape0.intersects(dataArea)) {
587: g2.setPaint(getItemPaint(series, item));
588: g2.fill(shape0);
589: }
590: entityArea0 = shape0;
591:
592: Shape shape1 = getItemShape(series + 1, item);
593: if (orientation == PlotOrientation.HORIZONTAL) {
594: shape1 = ShapeUtilities.createTranslatedShape(shape1,
595: transY1, transX1);
596: }
597: else {
598: shape1 = ShapeUtilities.createTranslatedShape(shape1,
599: transX1, transY1);
600: }
601: if (shape1.intersects(dataArea)) {
602: g2.setPaint(getItemPaint(series + 1, item));
603: g2.fill(shape1);
604: }
605: entityArea1 = shape1;
606:
607: }
608:
609:
610: if (entities != null) {
611: if (entityArea0 == null) {
612: entityArea0 = new Rectangle2D.Double(transX0 - 2,
613: transY0 - 2, 4, 4);
614: }
615: String tip = null;
616: XYToolTipGenerator generator = getToolTipGenerator(series,
617: item);
618: if (generator != null) {
619: tip = generator.generateToolTip(dataset, series, item);
620: }
621: String url = null;
622: if (getURLGenerator() != null) {
623: url = getURLGenerator().generateURL(dataset, series,
624: item);
625: }
626: XYItemEntity entity = new XYItemEntity(entityArea0, dataset,
627: series, item, tip, url);
628: entities.add(entity);
629:
630:
631: if (entityArea1 == null) {
632: entityArea1 = new Rectangle2D.Double(transX1 - 2,
633: transY1 - 2, 4, 4);
634: }
635: tip = null;
636: generator = getToolTipGenerator(series, item);
637: if (generator != null) {
638: tip = generator.generateToolTip(dataset, series + 1, item);
639: }
640: url = null;
641: if (getURLGenerator() != null) {
642: url = getURLGenerator().generateURL(dataset,
643: series + 1, item);
644: }
645: entity = new XYItemEntity(entityArea1, dataset,
646: series + 1, item, tip, url);
647: entities.add(entity);
648: }
649:
650: int domainAxisIndex = plot.getDomainAxisIndex(domainAxis);
651: int rangeAxisIndex = plot.getRangeAxisIndex(rangeAxis);
652: updateCrosshairValues(crosshairState, x1, y1, domainAxisIndex,
653: rangeAxisIndex, transX1, transY1, orientation);
654: updateCrosshairValues(crosshairState, x0, y0, domainAxisIndex,
655: rangeAxisIndex, transX0, transY0, orientation);
656: }
657:
658: }
659:
660:
673: protected Shape getPositiveArea(float x0, float y0A, float y0B,
674: float x1, float y1A, float y1B,
675: PlotOrientation orientation) {
676:
677: Shape result = null;
678:
679: boolean startsNegative = (y0A >= y0B);
680: boolean endsNegative = (y1A >= y1B);
681: if (orientation == PlotOrientation.HORIZONTAL) {
682: startsNegative = (y0B >= y0A);
683: endsNegative = (y1B >= y1A);
684: }
685:
686: if (startsNegative) {
687: if (endsNegative) {
688:
689: result = null;
690: }
691: else {
692:
693: float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
694: GeneralPath area = new GeneralPath();
695: if (orientation == PlotOrientation.HORIZONTAL) {
696: area.moveTo(y1A, x1);
697: area.lineTo(p[1], p[0]);
698: area.lineTo(y1B, x1);
699: area.closePath();
700: }
701: else if (orientation == PlotOrientation.VERTICAL) {
702: area.moveTo(x1, y1A);
703: area.lineTo(p[0], p[1]);
704: area.lineTo(x1, y1B);
705: area.closePath();
706: }
707: result = area;
708: }
709: }
710: else {
711: if (endsNegative) {
712:
713: float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
714: GeneralPath area = new GeneralPath();
715: if (orientation == PlotOrientation.HORIZONTAL) {
716: area.moveTo(y0A, x0);
717: area.lineTo(p[1], p[0]);
718: area.lineTo(y0B, x0);
719: area.closePath();
720: }
721: else if (orientation == PlotOrientation.VERTICAL) {
722: area.moveTo(x0, y0A);
723: area.lineTo(p[0], p[1]);
724: area.lineTo(x0, y0B);
725: area.closePath();
726: }
727: result = area;
728:
729: }
730: else {
731: GeneralPath area = new GeneralPath();
732: if (orientation == PlotOrientation.HORIZONTAL) {
733: area.moveTo(y0A, x0);
734: area.lineTo(y1A, x1);
735: area.lineTo(y1B, x1);
736: area.lineTo(y0B, x0);
737: area.closePath();
738: }
739: else if (orientation == PlotOrientation.VERTICAL) {
740: area.moveTo(x0, y0A);
741: area.lineTo(x1, y1A);
742: area.lineTo(x1, y1B);
743: area.lineTo(x0, y0B);
744: area.closePath();
745: }
746: result = area;
747: }
748:
749: }
750:
751: return result;
752:
753: }
754:
755:
768: protected Shape getNegativeArea(float x0, float y0A, float y0B,
769: float x1, float y1A, float y1B,
770: PlotOrientation orientation) {
771:
772: Shape result = null;
773:
774: boolean startsNegative = (y0A >= y0B);
775: boolean endsNegative = (y1A >= y1B);
776: if (orientation == PlotOrientation.HORIZONTAL) {
777: startsNegative = (y0B >= y0A);
778: endsNegative = (y1B >= y1A);
779: }
780: if (startsNegative) {
781: if (endsNegative) {
782: GeneralPath area = new GeneralPath();
783: if (orientation == PlotOrientation.HORIZONTAL) {
784: area.moveTo(y0A, x0);
785: area.lineTo(y1A, x1);
786: area.lineTo(y1B, x1);
787: area.lineTo(y0B, x0);
788: area.closePath();
789: }
790: else if (orientation == PlotOrientation.VERTICAL) {
791: area.moveTo(x0, y0A);
792: area.lineTo(x1, y1A);
793: area.lineTo(x1, y1B);
794: area.lineTo(x0, y0B);
795: area.closePath();
796: }
797: result = area;
798: }
799: else {
800: float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
801: GeneralPath area = new GeneralPath();
802: if (orientation == PlotOrientation.HORIZONTAL) {
803: area.moveTo(y0A, x0);
804: area.lineTo(p[1], p[0]);
805: area.lineTo(y0B, x0);
806: area.closePath();
807: }
808: else if (orientation == PlotOrientation.VERTICAL) {
809: area.moveTo(x0, y0A);
810: area.lineTo(p[0], p[1]);
811: area.lineTo(x0, y0B);
812: area.closePath();
813: }
814: result = area;
815: }
816: }
817: else {
818: if (endsNegative) {
819:
820: float[] p = getIntersection(x0, y0A, x1, y1A, x0, y0B, x1, y1B);
821: GeneralPath area = new GeneralPath();
822: if (orientation == PlotOrientation.HORIZONTAL) {
823: area.moveTo(y1A, x1);
824: area.lineTo(p[1], p[0]);
825: area.lineTo(y1B, x1);
826: area.closePath();
827: }
828: else if (orientation == PlotOrientation.VERTICAL) {
829: area.moveTo(x1, y1A);
830: area.lineTo(p[0], p[1]);
831: area.lineTo(x1, y1B);
832: area.closePath();
833: }
834: result = area;
835: }
836: else {
837:
838: }
839:
840: }
841:
842: return result;
843:
844: }
845:
846:
860: private float[] getIntersection(float x1, float y1, float x2, float y2,
861: float x3, float y3, float x4, float y4) {
862:
863: float n = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
864: float d = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
865: float u = n / d;
866:
867: float[] result = new float[2];
868: result[0] = x1 + u * (x2 - x1);
869: result[1] = y1 + u * (y2 - y1);
870: return result;
871:
872: }
873:
874:
883: public LegendItem getLegendItem(int datasetIndex, int series) {
884: LegendItem result = null;
885: XYPlot p = getPlot();
886: if (p != null) {
887: XYDataset dataset = p.getDataset(datasetIndex);
888: if (dataset != null) {
889: if (getItemVisible(series, 0)) {
890: String label = getLegendItemLabelGenerator().generateLabel(
891: dataset, series);
892: String description = label;
893: String toolTipText = null;
894: if (getLegendItemToolTipGenerator() != null) {
895: toolTipText
896: = getLegendItemToolTipGenerator().generateLabel(
897: dataset, series);
898: }
899: String urlText = null;
900: if (getLegendItemURLGenerator() != null) {
901: urlText = getLegendItemURLGenerator().generateLabel(
902: dataset, series);
903: }
904: Paint paint = getSeriesPaint(series);
905: Stroke stroke = getSeriesStroke(series);
906:
907: Line2D line = new Line2D.Double(-7.0, 0.0, 7.0, 0.0);
908: result = new LegendItem(label, description,
909: toolTipText, urlText, line, stroke, paint);
910: }
911: }
912:
913: }
914:
915: return result;
916:
917: }
918:
919:
926: public boolean equals(Object obj) {
927: if (obj == this) {
928: return true;
929: }
930: if (!(obj instanceof XYDifferenceRenderer)) {
931: return false;
932: }
933: if (!super.equals(obj)) {
934: return false;
935: }
936: XYDifferenceRenderer that = (XYDifferenceRenderer) obj;
937: if (!PaintUtilities.equal(this.positivePaint, that.positivePaint)) {
938: return false;
939: }
940: if (!PaintUtilities.equal(this.negativePaint, that.negativePaint)) {
941: return false;
942: }
943: if (this.shapesVisible != that.shapesVisible) {
944: return false;
945: }
946: if (!ShapeUtilities.equal(this.legendLine, that.legendLine)) {
947: return false;
948: }
949: if (this.roundXCoordinates != that.roundXCoordinates) {
950: return false;
951: }
952: return true;
953: }
954:
955:
962: public Object clone() throws CloneNotSupportedException {
963: XYDifferenceRenderer clone = (XYDifferenceRenderer) super.clone();
964: clone.legendLine = ShapeUtilities.clone(this.legendLine);
965: return clone;
966: }
967:
968:
975: private void writeObject(ObjectOutputStream stream) throws IOException {
976: stream.defaultWriteObject();
977: SerialUtilities.writePaint(this.positivePaint, stream);
978: SerialUtilities.writePaint(this.negativePaint, stream);
979: SerialUtilities.writeShape(this.legendLine, stream);
980: }
981:
982:
990: private void readObject(ObjectInputStream stream)
991: throws IOException, ClassNotFoundException {
992: stream.defaultReadObject();
993: this.positivePaint = SerialUtilities.readPaint(stream);
994: this.negativePaint = SerialUtilities.readPaint(stream);
995: this.legendLine = SerialUtilities.readShape(stream);
996: }
997:
998: }