1:
49:
50: package ;
51:
52:
53: import ;
54: import ;
55: import ;
56: import ;
57: import ;
58: import ;
59: import ;
60: import ;
61: import ;
62: import ;
63: import ;
64: import ;
65: import ;
66: import ;
67: import ;
68: import ;
69: import ;
70: import ;
71: import ;
72: import ;
73: import ;
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: import ;
93: import ;
94: import ;
95:
96:
97:
101: public class PolarPlot extends Plot implements ValueAxisPlot,
102: Zoomable,
103: RendererChangeListener,
104: Cloneable,
105: Serializable {
106:
107:
108: private static final long serialVersionUID = 3794383185924179525L;
109:
110:
111: private static final int MARGIN = 20;
112:
113:
114: private static final double ANNOTATION_MARGIN = 7.0;
115:
116:
117: public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(
118: 0.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL,
119: 0.0f, new float[]{2.0f, 2.0f}, 0.0f);
120:
121:
122: public static final Paint DEFAULT_GRIDLINE_PAINT = Color.gray;
123:
124:
125: protected static ResourceBundle localizationResources
126: = ResourceBundle.getBundle("org.jfree.chart.plot.LocalizationBundle");
127:
128:
129: private List angleTicks;
130:
131:
132: private ValueAxis axis;
133:
134:
135: private XYDataset dataset;
136:
137:
141: private PolarItemRenderer renderer;
142:
143:
144: private boolean angleLabelsVisible = true;
145:
146:
147: private Font angleLabelFont = new Font("SansSerif", Font.PLAIN, 12);
148:
149:
150: private transient Paint angleLabelPaint = Color.black;
151:
152:
153: private boolean angleGridlinesVisible;
154:
155:
156: private transient Stroke angleGridlineStroke;
157:
158:
159: private transient Paint angleGridlinePaint;
160:
161:
162: private boolean radiusGridlinesVisible;
163:
164:
165: private transient Stroke radiusGridlineStroke;
166:
167:
168: private transient Paint radiusGridlinePaint;
169:
170:
171: private List cornerTextItems = new ArrayList();
172:
173:
176: public PolarPlot() {
177: this(null, null, null);
178: }
179:
180:
187: public PolarPlot(XYDataset dataset,
188: ValueAxis radiusAxis,
189: PolarItemRenderer renderer) {
190:
191: super();
192:
193: this.dataset = dataset;
194: if (this.dataset != null) {
195: this.dataset.addChangeListener(this);
196: }
197:
198: this.angleTicks = new java.util.ArrayList();
199: this.angleTicks.add(new NumberTick(new Double(0.0), "0",
200: TextAnchor.CENTER, TextAnchor.CENTER, 0.0));
201: this.angleTicks.add(new NumberTick(new Double(45.0), "45",
202: TextAnchor.CENTER, TextAnchor.CENTER, 0.0));
203: this.angleTicks.add(new NumberTick(new Double(90.0), "90",
204: TextAnchor.CENTER, TextAnchor.CENTER, 0.0));
205: this.angleTicks.add(new NumberTick(new Double(135.0), "135",
206: TextAnchor.CENTER, TextAnchor.CENTER, 0.0));
207: this.angleTicks.add(new NumberTick(new Double(180.0), "180",
208: TextAnchor.CENTER, TextAnchor.CENTER, 0.0));
209: this.angleTicks.add(new NumberTick(new Double(225.0), "225",
210: TextAnchor.CENTER, TextAnchor.CENTER, 0.0));
211: this.angleTicks.add(new NumberTick(new Double(270.0), "270",
212: TextAnchor.CENTER, TextAnchor.CENTER, 0.0));
213: this.angleTicks.add(new NumberTick(new Double(315.0), "315",
214: TextAnchor.CENTER, TextAnchor.CENTER, 0.0));
215:
216: this.axis = radiusAxis;
217: if (this.axis != null) {
218: this.axis.setPlot(this);
219: this.axis.addChangeListener(this);
220: }
221:
222: this.renderer = renderer;
223: if (this.renderer != null) {
224: this.renderer.setPlot(this);
225: this.renderer.addChangeListener(this);
226: }
227:
228: this.angleGridlinesVisible = true;
229: this.angleGridlineStroke = DEFAULT_GRIDLINE_STROKE;
230: this.angleGridlinePaint = DEFAULT_GRIDLINE_PAINT;
231:
232: this.radiusGridlinesVisible = true;
233: this.radiusGridlineStroke = DEFAULT_GRIDLINE_STROKE;
234: this.radiusGridlinePaint = DEFAULT_GRIDLINE_PAINT;
235: }
236:
237:
245: public void addCornerTextItem(String text) {
246: if (text == null) {
247: throw new IllegalArgumentException("Null 'text' argument.");
248: }
249: this.cornerTextItems.add(text);
250: this.notifyListeners(new PlotChangeEvent(this));
251: }
252:
253:
261: public void removeCornerTextItem(String text) {
262: boolean removed = this.cornerTextItems.remove(text);
263: if (removed) {
264: this.notifyListeners(new PlotChangeEvent(this));
265: }
266: }
267:
268:
275: public void clearCornerTextItems() {
276: if (this.cornerTextItems.size() > 0) {
277: this.cornerTextItems.clear();
278: this.notifyListeners(new PlotChangeEvent(this));
279: }
280: }
281:
282:
287: public String getPlotType() {
288: return PolarPlot.localizationResources.getString("Polar_Plot");
289: }
290:
291:
298: public ValueAxis getAxis() {
299: return this.axis;
300: }
301:
302:
308: public void setAxis(ValueAxis axis) {
309: if (axis != null) {
310: axis.setPlot(this);
311: }
312:
313:
314: if (this.axis != null) {
315: this.axis.removeChangeListener(this);
316: }
317:
318: this.axis = axis;
319: if (this.axis != null) {
320: this.axis.configure();
321: this.axis.addChangeListener(this);
322: }
323: notifyListeners(new PlotChangeEvent(this));
324: }
325:
326:
333: public XYDataset getDataset() {
334: return this.dataset;
335: }
336:
337:
345: public void setDataset(XYDataset dataset) {
346:
347:
348: XYDataset existing = this.dataset;
349: if (existing != null) {
350: existing.removeChangeListener(this);
351: }
352:
353:
354: this.dataset = dataset;
355: if (this.dataset != null) {
356: setDatasetGroup(this.dataset.getGroup());
357: this.dataset.addChangeListener(this);
358: }
359:
360:
361: DatasetChangeEvent event = new DatasetChangeEvent(this, this.dataset);
362: datasetChanged(event);
363: }
364:
365:
372: public PolarItemRenderer getRenderer() {
373: return this.renderer;
374: }
375:
376:
386: public void setRenderer(PolarItemRenderer renderer) {
387: if (this.renderer != null) {
388: this.renderer.removeChangeListener(this);
389: }
390:
391: this.renderer = renderer;
392: if (this.renderer != null) {
393: this.renderer.setPlot(this);
394: }
395:
396: notifyListeners(new PlotChangeEvent(this));
397: }
398:
399:
406: public boolean isAngleLabelsVisible() {
407: return this.angleLabelsVisible;
408: }
409:
410:
418: public void setAngleLabelsVisible(boolean visible) {
419: if (this.angleLabelsVisible != visible) {
420: this.angleLabelsVisible = visible;
421: notifyListeners(new PlotChangeEvent(this));
422: }
423: }
424:
425:
432: public Font getAngleLabelFont() {
433: return this.angleLabelFont;
434: }
435:
436:
444: public void setAngleLabelFont(Font font) {
445: if (font == null) {
446: throw new IllegalArgumentException("Null 'font' argument.");
447: }
448: this.angleLabelFont = font;
449: notifyListeners(new PlotChangeEvent(this));
450: }
451:
452:
459: public Paint getAngleLabelPaint() {
460: return this.angleLabelPaint;
461: }
462:
463:
469: public void setAngleLabelPaint(Paint paint) {
470: if (paint == null) {
471: throw new IllegalArgumentException("Null 'paint' argument.");
472: }
473: this.angleLabelPaint = paint;
474: notifyListeners(new PlotChangeEvent(this));
475: }
476:
477:
485: public boolean isAngleGridlinesVisible() {
486: return this.angleGridlinesVisible;
487: }
488:
489:
500: public void setAngleGridlinesVisible(boolean visible) {
501: if (this.angleGridlinesVisible != visible) {
502: this.angleGridlinesVisible = visible;
503: notifyListeners(new PlotChangeEvent(this));
504: }
505: }
506:
507:
515: public Stroke getAngleGridlineStroke() {
516: return this.angleGridlineStroke;
517: }
518:
519:
529: public void setAngleGridlineStroke(Stroke stroke) {
530: this.angleGridlineStroke = stroke;
531: notifyListeners(new PlotChangeEvent(this));
532: }
533:
534:
542: public Paint getAngleGridlinePaint() {
543: return this.angleGridlinePaint;
544: }
545:
546:
555: public void setAngleGridlinePaint(Paint paint) {
556: this.angleGridlinePaint = paint;
557: notifyListeners(new PlotChangeEvent(this));
558: }
559:
560:
568: public boolean isRadiusGridlinesVisible() {
569: return this.radiusGridlinesVisible;
570: }
571:
572:
583: public void setRadiusGridlinesVisible(boolean visible) {
584: if (this.radiusGridlinesVisible != visible) {
585: this.radiusGridlinesVisible = visible;
586: notifyListeners(new PlotChangeEvent(this));
587: }
588: }
589:
590:
598: public Stroke getRadiusGridlineStroke() {
599: return this.radiusGridlineStroke;
600: }
601:
602:
612: public void setRadiusGridlineStroke(Stroke stroke) {
613: this.radiusGridlineStroke = stroke;
614: notifyListeners(new PlotChangeEvent(this));
615: }
616:
617:
625: public Paint getRadiusGridlinePaint() {
626: return this.radiusGridlinePaint;
627: }
628:
629:
639: public void setRadiusGridlinePaint(Paint paint) {
640: this.radiusGridlinePaint = paint;
641: notifyListeners(new PlotChangeEvent(this));
642: }
643:
644:
664: public void draw(Graphics2D g2,
665: Rectangle2D area,
666: Point2D anchor,
667: PlotState parentState,
668: PlotRenderingInfo info) {
669:
670:
671: boolean b1 = (area.getWidth() <= MINIMUM_WIDTH_TO_DRAW);
672: boolean b2 = (area.getHeight() <= MINIMUM_HEIGHT_TO_DRAW);
673: if (b1 || b2) {
674: return;
675: }
676:
677:
678: if (info != null) {
679: info.setPlotArea(area);
680: }
681:
682:
683: RectangleInsets insets = getInsets();
684: insets.trim(area);
685:
686: Rectangle2D dataArea = area;
687: if (info != null) {
688: info.setDataArea(dataArea);
689: }
690:
691:
692: drawBackground(g2, dataArea);
693: double h = Math.min(dataArea.getWidth() / 2.0,
694: dataArea.getHeight() / 2.0) - MARGIN;
695: Rectangle2D quadrant = new Rectangle2D.Double(dataArea.getCenterX(),
696: dataArea.getCenterY(), h, h);
697: AxisState state = drawAxis(g2, area, quadrant);
698: if (this.renderer != null) {
699: Shape originalClip = g2.getClip();
700: Composite originalComposite = g2.getComposite();
701:
702: g2.clip(dataArea);
703: g2.setComposite(AlphaComposite.getInstance(
704: AlphaComposite.SRC_OVER, getForegroundAlpha()));
705:
706: drawGridlines(g2, dataArea, this.angleTicks, state.getTicks());
707:
708:
709: render(g2, dataArea, info);
710:
711: g2.setClip(originalClip);
712: g2.setComposite(originalComposite);
713: }
714: drawOutline(g2, dataArea);
715: drawCornerTextItems(g2, dataArea);
716: }
717:
718:
724: protected void drawCornerTextItems(Graphics2D g2, Rectangle2D area) {
725: if (this.cornerTextItems.isEmpty()) {
726: return;
727: }
728:
729: g2.setColor(Color.black);
730: double width = 0.0;
731: double height = 0.0;
732: for (Iterator it = this.cornerTextItems.iterator(); it.hasNext();) {
733: String msg = (String) it.next();
734: FontMetrics fm = g2.getFontMetrics();
735: Rectangle2D bounds = TextUtilities.getTextBounds(msg, g2, fm);
736: width = Math.max(width, bounds.getWidth());
737: height += bounds.getHeight();
738: }
739:
740: double xadj = ANNOTATION_MARGIN * 2.0;
741: double yadj = ANNOTATION_MARGIN;
742: width += xadj;
743: height += yadj;
744:
745: double x = area.getMaxX() - width;
746: double y = area.getMaxY() - height;
747: g2.drawRect((int) x, (int) y, (int) width, (int) height);
748: x += ANNOTATION_MARGIN;
749: for (Iterator it = this.cornerTextItems.iterator(); it.hasNext();) {
750: String msg = (String) it.next();
751: Rectangle2D bounds = TextUtilities.getTextBounds(msg, g2,
752: g2.getFontMetrics());
753: y += bounds.getHeight();
754: g2.drawString(msg, (int) x, (int) y);
755: }
756: }
757:
758:
767: protected AxisState drawAxis(Graphics2D g2, Rectangle2D plotArea,
768: Rectangle2D dataArea) {
769: return this.axis.draw(g2, dataArea.getMinY(), plotArea, dataArea,
770: RectangleEdge.TOP, null);
771: }
772:
773:
782: protected void render(Graphics2D g2,
783: Rectangle2D dataArea,
784: PlotRenderingInfo info) {
785:
786:
787:
788: if (!DatasetUtilities.isEmptyOrNull(this.dataset)) {
789: int seriesCount = this.dataset.getSeriesCount();
790: for (int series = 0; series < seriesCount; series++) {
791: this.renderer.drawSeries(g2, dataArea, info, this,
792: this.dataset, series);
793: }
794: }
795: else {
796: drawNoDataMessage(g2, dataArea);
797: }
798: }
799:
800:
808: protected void drawGridlines(Graphics2D g2, Rectangle2D dataArea,
809: List angularTicks, List radialTicks) {
810:
811:
812: if (this.renderer == null) {
813: return;
814: }
815:
816:
817: if (isAngleGridlinesVisible()) {
818: Stroke gridStroke = getAngleGridlineStroke();
819: Paint gridPaint = getAngleGridlinePaint();
820: if ((gridStroke != null) && (gridPaint != null)) {
821: this.renderer.drawAngularGridLines(g2, this, angularTicks,
822: dataArea);
823: }
824: }
825:
826:
827: if (isRadiusGridlinesVisible()) {
828: Stroke gridStroke = getRadiusGridlineStroke();
829: Paint gridPaint = getRadiusGridlinePaint();
830: if ((gridStroke != null) && (gridPaint != null)) {
831: this.renderer.drawRadialGridLines(g2, this, this.axis,
832: radialTicks, dataArea);
833: }
834: }
835: }
836:
837:
842: public void zoom(double percent) {
843: if (percent > 0.0) {
844: double radius = getMaxRadius();
845: double scaledRadius = radius * percent;
846: this.axis.setUpperBound(scaledRadius);
847: getAxis().setAutoRange(false);
848: }
849: else {
850: getAxis().setAutoRange(true);
851: }
852: }
853:
854:
861: public Range getDataRange(ValueAxis axis) {
862: Range result = null;
863: if (this.dataset != null) {
864: result = Range.combine(result,
865: DatasetUtilities.findRangeBounds(this.dataset));
866: }
867: return result;
868: }
869:
870:
877: public void datasetChanged(DatasetChangeEvent event) {
878:
879: if (this.axis != null) {
880: this.axis.configure();
881: }
882:
883: if (getParent() != null) {
884: getParent().datasetChanged(event);
885: }
886: else {
887: super.datasetChanged(event);
888: }
889: }
890:
891:
898: public void rendererChanged(RendererChangeEvent event) {
899: notifyListeners(new PlotChangeEvent(this));
900: }
901:
902:
908: public int getSeriesCount() {
909: int result = 0;
910:
911: if (this.dataset != null) {
912: result = this.dataset.getSeriesCount();
913: }
914: return result;
915: }
916:
917:
924: public LegendItemCollection getLegendItems() {
925: LegendItemCollection result = new LegendItemCollection();
926:
927:
928: if (this.dataset != null) {
929: if (this.renderer != null) {
930: int seriesCount = this.dataset.getSeriesCount();
931: for (int i = 0; i < seriesCount; i++) {
932: LegendItem item = this.renderer.getLegendItem(i);
933: result.add(item);
934: }
935: }
936: }
937: return result;
938: }
939:
940:
947: public boolean equals(Object obj) {
948: if (obj == this) {
949: return true;
950: }
951: if (!(obj instanceof PolarPlot)) {
952: return false;
953: }
954: PolarPlot that = (PolarPlot) obj;
955: if (!ObjectUtilities.equal(this.axis, that.axis)) {
956: return false;
957: }
958: if (!ObjectUtilities.equal(this.renderer, that.renderer)) {
959: return false;
960: }
961: if (this.angleGridlinesVisible != that.angleGridlinesVisible) {
962: return false;
963: }
964: if (this.angleLabelsVisible != that.angleLabelsVisible) {
965: return false;
966: }
967: if (!this.angleLabelFont.equals(that.angleLabelFont)) {
968: return false;
969: }
970: if (!PaintUtilities.equal(this.angleLabelPaint, that.angleLabelPaint)) {
971: return false;
972: }
973: if (!ObjectUtilities.equal(this.angleGridlineStroke,
974: that.angleGridlineStroke)) {
975: return false;
976: }
977: if (!PaintUtilities.equal(
978: this.angleGridlinePaint, that.angleGridlinePaint
979: )) {
980: return false;
981: }
982: if (this.radiusGridlinesVisible != that.radiusGridlinesVisible) {
983: return false;
984: }
985: if (!ObjectUtilities.equal(this.radiusGridlineStroke,
986: that.radiusGridlineStroke)) {
987: return false;
988: }
989: if (!PaintUtilities.equal(this.radiusGridlinePaint,
990: that.radiusGridlinePaint)) {
991: return false;
992: }
993: if (!this.cornerTextItems.equals(that.cornerTextItems)) {
994: return false;
995: }
996: return super.equals(obj);
997: }
998:
999:
1007: public Object clone() throws CloneNotSupportedException {
1008:
1009: PolarPlot clone = (PolarPlot) super.clone();
1010: if (this.axis != null) {
1011: clone.axis = (ValueAxis) ObjectUtilities.clone(this.axis);
1012: clone.axis.setPlot(clone);
1013: clone.axis.addChangeListener(clone);
1014: }
1015:
1016: if (clone.dataset != null) {
1017: clone.dataset.addChangeListener(clone);
1018: }
1019:
1020: if (this.renderer != null) {
1021: clone.renderer
1022: = (PolarItemRenderer) ObjectUtilities.clone(this.renderer);
1023: }
1024:
1025: clone.cornerTextItems = new ArrayList(this.cornerTextItems);
1026:
1027: return clone;
1028: }
1029:
1030:
1037: private void writeObject(ObjectOutputStream stream) throws IOException {
1038: stream.defaultWriteObject();
1039: SerialUtilities.writeStroke(this.angleGridlineStroke, stream);
1040: SerialUtilities.writePaint(this.angleGridlinePaint, stream);
1041: SerialUtilities.writeStroke(this.radiusGridlineStroke, stream);
1042: SerialUtilities.writePaint(this.radiusGridlinePaint, stream);
1043: SerialUtilities.writePaint(this.angleLabelPaint, stream);
1044: }
1045:
1046:
1054: private void readObject(ObjectInputStream stream)
1055: throws IOException, ClassNotFoundException {
1056:
1057: stream.defaultReadObject();
1058: this.angleGridlineStroke = SerialUtilities.readStroke(stream);
1059: this.angleGridlinePaint = SerialUtilities.readPaint(stream);
1060: this.radiusGridlineStroke = SerialUtilities.readStroke(stream);
1061: this.radiusGridlinePaint = SerialUtilities.readPaint(stream);
1062: this.angleLabelPaint = SerialUtilities.readPaint(stream);
1063:
1064: if (this.axis != null) {
1065: this.axis.setPlot(this);
1066: this.axis.addChangeListener(this);
1067: }
1068:
1069: if (this.dataset != null) {
1070: this.dataset.addChangeListener(this);
1071: }
1072: }
1073:
1074:
1082: public void zoomDomainAxes(double factor, PlotRenderingInfo state,
1083: Point2D source) {
1084:
1085: }
1086:
1087:
1096: public void zoomDomainAxes(double lowerPercent, double upperPercent,
1097: PlotRenderingInfo state, Point2D source) {
1098:
1099: }
1100:
1101:
1108: public void zoomRangeAxes(double factor, PlotRenderingInfo state,
1109: Point2D source) {
1110: zoom(factor);
1111: }
1112:
1113:
1121: public void zoomRangeAxes(double lowerPercent, double upperPercent,
1122: PlotRenderingInfo state, Point2D source) {
1123: zoom((upperPercent + lowerPercent) / 2.0);
1124: }
1125:
1126:
1131: public boolean isDomainZoomable() {
1132: return false;
1133: }
1134:
1135:
1140: public boolean isRangeZoomable() {
1141: return true;
1142: }
1143:
1144:
1149: public PlotOrientation getOrientation() {
1150: return PlotOrientation.HORIZONTAL;
1151: }
1152:
1153:
1158: public double getMaxRadius() {
1159: return this.axis.getUpperBound();
1160: }
1161:
1162:
1173: public Point translateValueThetaRadiusToJava2D(double angleDegrees,
1174: double radius,
1175: Rectangle2D dataArea) {
1176:
1177: double radians = Math.toRadians(angleDegrees - 90.0);
1178:
1179: double minx = dataArea.getMinX() + MARGIN;
1180: double maxx = dataArea.getMaxX() - MARGIN;
1181: double miny = dataArea.getMinY() + MARGIN;
1182: double maxy = dataArea.getMaxY() - MARGIN;
1183:
1184: double lengthX = maxx - minx;
1185: double lengthY = maxy - miny;
1186: double length = Math.min(lengthX, lengthY);
1187:
1188: double midX = minx + lengthX / 2.0;
1189: double midY = miny + lengthY / 2.0;
1190:
1191: double axisMin = this.axis.getLowerBound();
1192: double axisMax = getMaxRadius();
1193: double adjustedRadius = Math.max(radius, axisMin);
1194:
1195: double xv = length / 2.0 * Math.cos(radians);
1196: double yv = length / 2.0 * Math.sin(radians);
1197:
1198: float x = (float) (midX + (xv * (adjustedRadius - axisMin)
1199: / (axisMax - axisMin)));
1200: float y = (float) (midY + (yv * (adjustedRadius - axisMin)
1201: / (axisMax - axisMin)));
1202:
1203: int ix = Math.round(x);
1204: int iy = Math.round(y);
1205:
1206: Point p = new Point(ix, iy);
1207: return p;
1208:
1209: }
1210:
1211: }