1:
48:
49: package ;
50:
51:
52: import ;
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:
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: import ;
90: import ;
91: import ;
92: import ;
93: import ;
94:
95:
96:
100: public class PolarPlot extends Plot implements ValueAxisPlot, Zoomable,
101: RendererChangeListener, Cloneable, Serializable {
102:
103:
104: private static final long serialVersionUID = 3794383185924179525L;
105:
106:
107: private static final int MARGIN = 20;
108:
109:
110: private static final double ANNOTATION_MARGIN = 7.0;
111:
112:
113: public static final Stroke DEFAULT_GRIDLINE_STROKE = new BasicStroke(
114: 0.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL,
115: 0.0f, new float[]{2.0f, 2.0f}, 0.0f);
116:
117:
118: public static final Paint DEFAULT_GRIDLINE_PAINT = Color.gray;
119:
120:
121: protected static ResourceBundle localizationResources
122: = ResourceBundle.getBundle("org.jfree.chart.plot.LocalizationBundle");
123:
124:
125: private List angleTicks;
126:
127:
128: private ValueAxis axis;
129:
130:
131: private XYDataset dataset;
132:
133:
137: private PolarItemRenderer renderer;
138:
139:
140: private boolean angleLabelsVisible = true;
141:
142:
143: private Font angleLabelFont = new Font("SansSerif", Font.PLAIN, 12);
144:
145:
146: private transient Paint angleLabelPaint = Color.black;
147:
148:
149: private boolean angleGridlinesVisible;
150:
151:
152: private transient Stroke angleGridlineStroke;
153:
154:
155: private transient Paint angleGridlinePaint;
156:
157:
158: private boolean radiusGridlinesVisible;
159:
160:
161: private transient Stroke radiusGridlineStroke;
162:
163:
164: private transient Paint radiusGridlinePaint;
165:
166:
167: private List cornerTextItems = new ArrayList();
168:
169:
172: public PolarPlot() {
173: this(null, null, null);
174: }
175:
176:
183: public PolarPlot(XYDataset dataset,
184: ValueAxis radiusAxis,
185: PolarItemRenderer renderer) {
186:
187: super();
188:
189: this.dataset = dataset;
190: if (this.dataset != null) {
191: this.dataset.addChangeListener(this);
192: }
193:
194: this.angleTicks = new java.util.ArrayList();
195: this.angleTicks.add(new NumberTick(new Double(0.0), "0",
196: TextAnchor.CENTER, TextAnchor.CENTER, 0.0));
197: this.angleTicks.add(new NumberTick(new Double(45.0), "45",
198: TextAnchor.CENTER, TextAnchor.CENTER, 0.0));
199: this.angleTicks.add(new NumberTick(new Double(90.0), "90",
200: TextAnchor.CENTER, TextAnchor.CENTER, 0.0));
201: this.angleTicks.add(new NumberTick(new Double(135.0), "135",
202: TextAnchor.CENTER, TextAnchor.CENTER, 0.0));
203: this.angleTicks.add(new NumberTick(new Double(180.0), "180",
204: TextAnchor.CENTER, TextAnchor.CENTER, 0.0));
205: this.angleTicks.add(new NumberTick(new Double(225.0), "225",
206: TextAnchor.CENTER, TextAnchor.CENTER, 0.0));
207: this.angleTicks.add(new NumberTick(new Double(270.0), "270",
208: TextAnchor.CENTER, TextAnchor.CENTER, 0.0));
209: this.angleTicks.add(new NumberTick(new Double(315.0), "315",
210: TextAnchor.CENTER, TextAnchor.CENTER, 0.0));
211:
212: this.axis = radiusAxis;
213: if (this.axis != null) {
214: this.axis.setPlot(this);
215: this.axis.addChangeListener(this);
216: }
217:
218: this.renderer = renderer;
219: if (this.renderer != null) {
220: this.renderer.setPlot(this);
221: this.renderer.addChangeListener(this);
222: }
223:
224: this.angleGridlinesVisible = true;
225: this.angleGridlineStroke = DEFAULT_GRIDLINE_STROKE;
226: this.angleGridlinePaint = DEFAULT_GRIDLINE_PAINT;
227:
228: this.radiusGridlinesVisible = true;
229: this.radiusGridlineStroke = DEFAULT_GRIDLINE_STROKE;
230: this.radiusGridlinePaint = DEFAULT_GRIDLINE_PAINT;
231: }
232:
233:
241: public void addCornerTextItem(String text) {
242: if (text == null) {
243: throw new IllegalArgumentException("Null 'text' argument.");
244: }
245: this.cornerTextItems.add(text);
246: this.notifyListeners(new PlotChangeEvent(this));
247: }
248:
249:
257: public void removeCornerTextItem(String text) {
258: boolean removed = this.cornerTextItems.remove(text);
259: if (removed) {
260: this.notifyListeners(new PlotChangeEvent(this));
261: }
262: }
263:
264:
271: public void clearCornerTextItems() {
272: if (this.cornerTextItems.size() > 0) {
273: this.cornerTextItems.clear();
274: this.notifyListeners(new PlotChangeEvent(this));
275: }
276: }
277:
278:
283: public String getPlotType() {
284: return PolarPlot.localizationResources.getString("Polar_Plot");
285: }
286:
287:
294: public ValueAxis getAxis() {
295: return this.axis;
296: }
297:
298:
304: public void setAxis(ValueAxis axis) {
305: if (axis != null) {
306: axis.setPlot(this);
307: }
308:
309:
310: if (this.axis != null) {
311: this.axis.removeChangeListener(this);
312: }
313:
314: this.axis = axis;
315: if (this.axis != null) {
316: this.axis.configure();
317: this.axis.addChangeListener(this);
318: }
319: notifyListeners(new PlotChangeEvent(this));
320: }
321:
322:
329: public XYDataset getDataset() {
330: return this.dataset;
331: }
332:
333:
341: public void setDataset(XYDataset dataset) {
342:
343:
344: XYDataset existing = this.dataset;
345: if (existing != null) {
346: existing.removeChangeListener(this);
347: }
348:
349:
350: this.dataset = dataset;
351: if (this.dataset != null) {
352: setDatasetGroup(this.dataset.getGroup());
353: this.dataset.addChangeListener(this);
354: }
355:
356:
357: DatasetChangeEvent event = new DatasetChangeEvent(this, this.dataset);
358: datasetChanged(event);
359: }
360:
361:
368: public PolarItemRenderer getRenderer() {
369: return this.renderer;
370: }
371:
372:
382: public void setRenderer(PolarItemRenderer renderer) {
383: if (this.renderer != null) {
384: this.renderer.removeChangeListener(this);
385: }
386:
387: this.renderer = renderer;
388: if (this.renderer != null) {
389: this.renderer.setPlot(this);
390: }
391:
392: notifyListeners(new PlotChangeEvent(this));
393: }
394:
395:
402: public boolean isAngleLabelsVisible() {
403: return this.angleLabelsVisible;
404: }
405:
406:
414: public void setAngleLabelsVisible(boolean visible) {
415: if (this.angleLabelsVisible != visible) {
416: this.angleLabelsVisible = visible;
417: notifyListeners(new PlotChangeEvent(this));
418: }
419: }
420:
421:
428: public Font getAngleLabelFont() {
429: return this.angleLabelFont;
430: }
431:
432:
440: public void setAngleLabelFont(Font font) {
441: if (font == null) {
442: throw new IllegalArgumentException("Null 'font' argument.");
443: }
444: this.angleLabelFont = font;
445: notifyListeners(new PlotChangeEvent(this));
446: }
447:
448:
455: public Paint getAngleLabelPaint() {
456: return this.angleLabelPaint;
457: }
458:
459:
465: public void setAngleLabelPaint(Paint paint) {
466: if (paint == null) {
467: throw new IllegalArgumentException("Null 'paint' argument.");
468: }
469: this.angleLabelPaint = paint;
470: notifyListeners(new PlotChangeEvent(this));
471: }
472:
473:
481: public boolean isAngleGridlinesVisible() {
482: return this.angleGridlinesVisible;
483: }
484:
485:
496: public void setAngleGridlinesVisible(boolean visible) {
497: if (this.angleGridlinesVisible != visible) {
498: this.angleGridlinesVisible = visible;
499: notifyListeners(new PlotChangeEvent(this));
500: }
501: }
502:
503:
511: public Stroke getAngleGridlineStroke() {
512: return this.angleGridlineStroke;
513: }
514:
515:
525: public void setAngleGridlineStroke(Stroke stroke) {
526: this.angleGridlineStroke = stroke;
527: notifyListeners(new PlotChangeEvent(this));
528: }
529:
530:
538: public Paint getAngleGridlinePaint() {
539: return this.angleGridlinePaint;
540: }
541:
542:
551: public void setAngleGridlinePaint(Paint paint) {
552: this.angleGridlinePaint = paint;
553: notifyListeners(new PlotChangeEvent(this));
554: }
555:
556:
564: public boolean isRadiusGridlinesVisible() {
565: return this.radiusGridlinesVisible;
566: }
567:
568:
579: public void setRadiusGridlinesVisible(boolean visible) {
580: if (this.radiusGridlinesVisible != visible) {
581: this.radiusGridlinesVisible = visible;
582: notifyListeners(new PlotChangeEvent(this));
583: }
584: }
585:
586:
594: public Stroke getRadiusGridlineStroke() {
595: return this.radiusGridlineStroke;
596: }
597:
598:
608: public void setRadiusGridlineStroke(Stroke stroke) {
609: this.radiusGridlineStroke = stroke;
610: notifyListeners(new PlotChangeEvent(this));
611: }
612:
613:
621: public Paint getRadiusGridlinePaint() {
622: return this.radiusGridlinePaint;
623: }
624:
625:
635: public void setRadiusGridlinePaint(Paint paint) {
636: this.radiusGridlinePaint = paint;
637: notifyListeners(new PlotChangeEvent(this));
638: }
639:
640:
660: public void draw(Graphics2D g2,
661: Rectangle2D area,
662: Point2D anchor,
663: PlotState parentState,
664: PlotRenderingInfo info) {
665:
666:
667: boolean b1 = (area.getWidth() <= MINIMUM_WIDTH_TO_DRAW);
668: boolean b2 = (area.getHeight() <= MINIMUM_HEIGHT_TO_DRAW);
669: if (b1 || b2) {
670: return;
671: }
672:
673:
674: if (info != null) {
675: info.setPlotArea(area);
676: }
677:
678:
679: RectangleInsets insets = getInsets();
680: insets.trim(area);
681:
682: Rectangle2D dataArea = area;
683: if (info != null) {
684: info.setDataArea(dataArea);
685: }
686:
687:
688: drawBackground(g2, dataArea);
689: double h = Math.min(dataArea.getWidth() / 2.0,
690: dataArea.getHeight() / 2.0) - MARGIN;
691: Rectangle2D quadrant = new Rectangle2D.Double(dataArea.getCenterX(),
692: dataArea.getCenterY(), h, h);
693: AxisState state = drawAxis(g2, area, quadrant);
694: if (this.renderer != null) {
695: Shape originalClip = g2.getClip();
696: Composite originalComposite = g2.getComposite();
697:
698: g2.clip(dataArea);
699: g2.setComposite(AlphaComposite.getInstance(
700: AlphaComposite.SRC_OVER, getForegroundAlpha()));
701:
702: drawGridlines(g2, dataArea, this.angleTicks, state.getTicks());
703:
704:
705: render(g2, dataArea, info);
706:
707: g2.setClip(originalClip);
708: g2.setComposite(originalComposite);
709: }
710: drawOutline(g2, dataArea);
711: drawCornerTextItems(g2, dataArea);
712: }
713:
714:
720: protected void drawCornerTextItems(Graphics2D g2, Rectangle2D area) {
721: if (this.cornerTextItems.isEmpty()) {
722: return;
723: }
724:
725: g2.setColor(Color.black);
726: double width = 0.0;
727: double height = 0.0;
728: for (Iterator it = this.cornerTextItems.iterator(); it.hasNext();) {
729: String msg = (String) it.next();
730: FontMetrics fm = g2.getFontMetrics();
731: Rectangle2D bounds = TextUtilities.getTextBounds(msg, g2, fm);
732: width = Math.max(width, bounds.getWidth());
733: height += bounds.getHeight();
734: }
735:
736: double xadj = ANNOTATION_MARGIN * 2.0;
737: double yadj = ANNOTATION_MARGIN;
738: width += xadj;
739: height += yadj;
740:
741: double x = area.getMaxX() - width;
742: double y = area.getMaxY() - height;
743: g2.drawRect((int) x, (int) y, (int) width, (int) height);
744: x += ANNOTATION_MARGIN;
745: for (Iterator it = this.cornerTextItems.iterator(); it.hasNext();) {
746: String msg = (String) it.next();
747: Rectangle2D bounds = TextUtilities.getTextBounds(msg, g2,
748: g2.getFontMetrics());
749: y += bounds.getHeight();
750: g2.drawString(msg, (int) x, (int) y);
751: }
752: }
753:
754:
763: protected AxisState drawAxis(Graphics2D g2, Rectangle2D plotArea,
764: Rectangle2D dataArea) {
765: return this.axis.draw(g2, dataArea.getMinY(), plotArea, dataArea,
766: RectangleEdge.TOP, null);
767: }
768:
769:
778: protected void render(Graphics2D g2,
779: Rectangle2D dataArea,
780: PlotRenderingInfo info) {
781:
782:
783:
784: if (!DatasetUtilities.isEmptyOrNull(this.dataset)) {
785: int seriesCount = this.dataset.getSeriesCount();
786: for (int series = 0; series < seriesCount; series++) {
787: this.renderer.drawSeries(g2, dataArea, info, this,
788: this.dataset, series);
789: }
790: }
791: else {
792: drawNoDataMessage(g2, dataArea);
793: }
794: }
795:
796:
804: protected void drawGridlines(Graphics2D g2, Rectangle2D dataArea,
805: List angularTicks, List radialTicks) {
806:
807:
808: if (this.renderer == null) {
809: return;
810: }
811:
812:
813: if (isAngleGridlinesVisible()) {
814: Stroke gridStroke = getAngleGridlineStroke();
815: Paint gridPaint = getAngleGridlinePaint();
816: if ((gridStroke != null) && (gridPaint != null)) {
817: this.renderer.drawAngularGridLines(g2, this, angularTicks,
818: dataArea);
819: }
820: }
821:
822:
823: if (isRadiusGridlinesVisible()) {
824: Stroke gridStroke = getRadiusGridlineStroke();
825: Paint gridPaint = getRadiusGridlinePaint();
826: if ((gridStroke != null) && (gridPaint != null)) {
827: this.renderer.drawRadialGridLines(g2, this, this.axis,
828: radialTicks, dataArea);
829: }
830: }
831: }
832:
833:
838: public void zoom(double percent) {
839: if (percent > 0.0) {
840: double radius = getMaxRadius();
841: double scaledRadius = radius * percent;
842: this.axis.setUpperBound(scaledRadius);
843: getAxis().setAutoRange(false);
844: }
845: else {
846: getAxis().setAutoRange(true);
847: }
848: }
849:
850:
857: public Range getDataRange(ValueAxis axis) {
858: Range result = null;
859: if (this.dataset != null) {
860: result = Range.combine(result,
861: DatasetUtilities.findRangeBounds(this.dataset));
862: }
863: return result;
864: }
865:
866:
873: public void datasetChanged(DatasetChangeEvent event) {
874:
875: if (this.axis != null) {
876: this.axis.configure();
877: }
878:
879: if (getParent() != null) {
880: getParent().datasetChanged(event);
881: }
882: else {
883: super.datasetChanged(event);
884: }
885: }
886:
887:
894: public void rendererChanged(RendererChangeEvent event) {
895: notifyListeners(new PlotChangeEvent(this));
896: }
897:
898:
904: public int getSeriesCount() {
905: int result = 0;
906:
907: if (this.dataset != null) {
908: result = this.dataset.getSeriesCount();
909: }
910: return result;
911: }
912:
913:
920: public LegendItemCollection getLegendItems() {
921: LegendItemCollection result = new LegendItemCollection();
922:
923:
924: if (this.dataset != null) {
925: if (this.renderer != null) {
926: int seriesCount = this.dataset.getSeriesCount();
927: for (int i = 0; i < seriesCount; i++) {
928: LegendItem item = this.renderer.getLegendItem(i);
929: result.add(item);
930: }
931: }
932: }
933: return result;
934: }
935:
936:
943: public boolean equals(Object obj) {
944: if (obj == this) {
945: return true;
946: }
947: if (!(obj instanceof PolarPlot)) {
948: return false;
949: }
950: PolarPlot that = (PolarPlot) obj;
951: if (!ObjectUtilities.equal(this.axis, that.axis)) {
952: return false;
953: }
954: if (!ObjectUtilities.equal(this.renderer, that.renderer)) {
955: return false;
956: }
957: if (this.angleGridlinesVisible != that.angleGridlinesVisible) {
958: return false;
959: }
960: if (this.angleLabelsVisible != that.angleLabelsVisible) {
961: return false;
962: }
963: if (!this.angleLabelFont.equals(that.angleLabelFont)) {
964: return false;
965: }
966: if (!PaintUtilities.equal(this.angleLabelPaint, that.angleLabelPaint)) {
967: return false;
968: }
969: if (!ObjectUtilities.equal(this.angleGridlineStroke,
970: that.angleGridlineStroke)) {
971: return false;
972: }
973: if (!PaintUtilities.equal(
974: this.angleGridlinePaint, that.angleGridlinePaint
975: )) {
976: return false;
977: }
978: if (this.radiusGridlinesVisible != that.radiusGridlinesVisible) {
979: return false;
980: }
981: if (!ObjectUtilities.equal(this.radiusGridlineStroke,
982: that.radiusGridlineStroke)) {
983: return false;
984: }
985: if (!PaintUtilities.equal(this.radiusGridlinePaint,
986: that.radiusGridlinePaint)) {
987: return false;
988: }
989: if (!this.cornerTextItems.equals(that.cornerTextItems)) {
990: return false;
991: }
992: return super.equals(obj);
993: }
994:
995:
1003: public Object clone() throws CloneNotSupportedException {
1004:
1005: PolarPlot clone = (PolarPlot) super.clone();
1006: if (this.axis != null) {
1007: clone.axis = (ValueAxis) ObjectUtilities.clone(this.axis);
1008: clone.axis.setPlot(clone);
1009: clone.axis.addChangeListener(clone);
1010: }
1011:
1012: if (clone.dataset != null) {
1013: clone.dataset.addChangeListener(clone);
1014: }
1015:
1016: if (this.renderer != null) {
1017: clone.renderer
1018: = (PolarItemRenderer) ObjectUtilities.clone(this.renderer);
1019: }
1020:
1021: clone.cornerTextItems = new ArrayList(this.cornerTextItems);
1022:
1023: return clone;
1024: }
1025:
1026:
1033: private void writeObject(ObjectOutputStream stream) throws IOException {
1034: stream.defaultWriteObject();
1035: SerialUtilities.writeStroke(this.angleGridlineStroke, stream);
1036: SerialUtilities.writePaint(this.angleGridlinePaint, stream);
1037: SerialUtilities.writeStroke(this.radiusGridlineStroke, stream);
1038: SerialUtilities.writePaint(this.radiusGridlinePaint, stream);
1039: SerialUtilities.writePaint(this.angleLabelPaint, stream);
1040: }
1041:
1042:
1050: private void readObject(ObjectInputStream stream)
1051: throws IOException, ClassNotFoundException {
1052:
1053: stream.defaultReadObject();
1054: this.angleGridlineStroke = SerialUtilities.readStroke(stream);
1055: this.angleGridlinePaint = SerialUtilities.readPaint(stream);
1056: this.radiusGridlineStroke = SerialUtilities.readStroke(stream);
1057: this.radiusGridlinePaint = SerialUtilities.readPaint(stream);
1058: this.angleLabelPaint = SerialUtilities.readPaint(stream);
1059:
1060: if (this.axis != null) {
1061: this.axis.setPlot(this);
1062: this.axis.addChangeListener(this);
1063: }
1064:
1065: if (this.dataset != null) {
1066: this.dataset.addChangeListener(this);
1067: }
1068: }
1069:
1070:
1078: public void zoomDomainAxes(double factor, PlotRenderingInfo state,
1079: Point2D source) {
1080:
1081: }
1082:
1083:
1094: public void zoomDomainAxes(double factor, PlotRenderingInfo state,
1095: Point2D source, boolean useAnchor) {
1096:
1097: }
1098:
1099:
1108: public void zoomDomainAxes(double lowerPercent, double upperPercent,
1109: PlotRenderingInfo state, Point2D source) {
1110:
1111: }
1112:
1113:
1120: public void zoomRangeAxes(double factor, PlotRenderingInfo state,
1121: Point2D source) {
1122: zoom(factor);
1123: }
1124:
1125:
1137: public void zoomRangeAxes(double factor, PlotRenderingInfo info,
1138: Point2D source, boolean useAnchor) {
1139:
1140: if (useAnchor) {
1141:
1142:
1143: double sourceX = source.getX();
1144: double anchorX = this.axis.java2DToValue(sourceX,
1145: info.getDataArea(), RectangleEdge.BOTTOM);
1146: this.axis.resizeRange(factor, anchorX);
1147: }
1148: else {
1149: this.axis.resizeRange(factor);
1150: }
1151:
1152: }
1153:
1154:
1162: public void zoomRangeAxes(double lowerPercent, double upperPercent,
1163: PlotRenderingInfo state, Point2D source) {
1164: zoom((upperPercent + lowerPercent) / 2.0);
1165: }
1166:
1167:
1172: public boolean isDomainZoomable() {
1173: return false;
1174: }
1175:
1176:
1181: public boolean isRangeZoomable() {
1182: return true;
1183: }
1184:
1185:
1190: public PlotOrientation getOrientation() {
1191: return PlotOrientation.HORIZONTAL;
1192: }
1193:
1194:
1199: public double getMaxRadius() {
1200: return this.axis.getUpperBound();
1201: }
1202:
1203:
1214: public Point translateValueThetaRadiusToJava2D(double angleDegrees,
1215: double radius,
1216: Rectangle2D dataArea) {
1217:
1218: double radians = Math.toRadians(angleDegrees - 90.0);
1219:
1220: double minx = dataArea.getMinX() + MARGIN;
1221: double maxx = dataArea.getMaxX() - MARGIN;
1222: double miny = dataArea.getMinY() + MARGIN;
1223: double maxy = dataArea.getMaxY() - MARGIN;
1224:
1225: double lengthX = maxx - minx;
1226: double lengthY = maxy - miny;
1227: double length = Math.min(lengthX, lengthY);
1228:
1229: double midX = minx + lengthX / 2.0;
1230: double midY = miny + lengthY / 2.0;
1231:
1232: double axisMin = this.axis.getLowerBound();
1233: double axisMax = getMaxRadius();
1234: double adjustedRadius = Math.max(radius, axisMin);
1235:
1236: double xv = length / 2.0 * Math.cos(radians);
1237: double yv = length / 2.0 * Math.sin(radians);
1238:
1239: float x = (float) (midX + (xv * (adjustedRadius - axisMin)
1240: / (axisMax - axisMin)));
1241: float y = (float) (midY + (yv * (adjustedRadius - axisMin)
1242: / (axisMax - axisMin)));
1243:
1244: int ix = Math.round(x);
1245: int iy = Math.round(y);
1246:
1247: Point p = new Point(ix, iy);
1248: return p;
1249:
1250: }
1251:
1252: }