1:
46:
47: package ;
48:
49: import ;
50: import ;
51: import ;
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:
71: import ;
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: import ;
85: import ;
86: import ;
87: import ;
88: import ;
89: import ;
90: import ;
91:
92:
93:
99: public class PolarPlot extends Plot implements ValueAxisPlot,
100: Zoomable,
101: RendererChangeListener,
102: Cloneable,
103: Serializable {
104:
105:
106: private static final long serialVersionUID = 3794383185924179525L;
107:
108:
109: private static final int MARGIN = 20;
110:
111:
112: private static final double ANNOTATION_MARGIN = 7.0;
113:
114:
115: public static final Stroke DEFAULT_GRIDLINE_STROKE
116: = new BasicStroke(
117: 0.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL,
118: 0.0f, new float[]{2.0f, 2.0f}, 0.0f
119: );
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:
130:
131:
132: private List angleTicks;
133:
134:
135: private ValueAxis axis;
136:
137:
138: private XYDataset dataset;
139:
140:
144: private PolarItemRenderer renderer;
145:
146:
147: private boolean angleLabelsVisible = true;
148:
149:
150: private Font angleLabelFont = new Font("SansSerif", Font.PLAIN, 12);
151:
152:
153: private Paint angleLabelPaint = Color.black;
154:
155:
156: private boolean angleGridlinesVisible;
157:
158:
159: private transient Stroke angleGridlineStroke;
160:
161:
162: private transient Paint angleGridlinePaint;
163:
164:
165: private boolean radiusGridlinesVisible;
166:
167:
168: private transient Stroke radiusGridlineStroke;
169:
170:
171: private transient Paint radiusGridlinePaint;
172:
173:
174: private List cornerTextItems = new ArrayList();
175:
176:
177:
178:
179:
182: public PolarPlot() {
183: this(null, null, null);
184: }
185:
186:
193: public PolarPlot(XYDataset dataset,
194: ValueAxis radiusAxis,
195: PolarItemRenderer renderer) {
196:
197: super();
198:
199: this.dataset = dataset;
200: if (this.dataset != null) {
201: this.dataset.addChangeListener(this);
202: }
203:
204: this.angleTicks = new java.util.ArrayList();
205: this.angleTicks.add(new NumberTick(new Double(0.0), "0",
206: TextAnchor.CENTER, TextAnchor.CENTER, 0.0));
207: this.angleTicks.add(new NumberTick(new Double(45.0), "45",
208: TextAnchor.CENTER, TextAnchor.CENTER, 0.0));
209: this.angleTicks.add(new NumberTick(new Double(90.0), "90",
210: TextAnchor.CENTER, TextAnchor.CENTER, 0.0));
211: this.angleTicks.add(new NumberTick(new Double(135.0), "135",
212: TextAnchor.CENTER, TextAnchor.CENTER, 0.0));
213: this.angleTicks.add(new NumberTick(new Double(180.0), "180",
214: TextAnchor.CENTER, TextAnchor.CENTER, 0.0));
215: this.angleTicks.add(new NumberTick(new Double(225.0), "225",
216: TextAnchor.CENTER, TextAnchor.CENTER, 0.0));
217: this.angleTicks.add(new NumberTick(new Double(270.0), "270",
218: TextAnchor.CENTER, TextAnchor.CENTER, 0.0));
219: this.angleTicks.add(new NumberTick(new Double(315.0), "315",
220: TextAnchor.CENTER, TextAnchor.CENTER, 0.0));
221:
222: this.axis = radiusAxis;
223: if (this.axis != null) {
224: this.axis.setPlot(this);
225: this.axis.addChangeListener(this);
226: }
227:
228: this.renderer = renderer;
229: if (this.renderer != null) {
230: this.renderer.setPlot(this);
231: this.renderer.addChangeListener(this);
232: }
233:
234: this.angleGridlinesVisible = true;
235: this.angleGridlineStroke = DEFAULT_GRIDLINE_STROKE;
236: this.angleGridlinePaint = DEFAULT_GRIDLINE_PAINT;
237:
238: this.radiusGridlinesVisible = true;
239: this.radiusGridlineStroke = DEFAULT_GRIDLINE_STROKE;
240: this.radiusGridlinePaint = DEFAULT_GRIDLINE_PAINT;
241: }
242:
243:
248: public void addCornerTextItem(String text) {
249: if (text == null) {
250: throw new IllegalArgumentException("Null 'text' argument.");
251: }
252: this.cornerTextItems.add(text);
253: this.notifyListeners(new PlotChangeEvent(this));
254: }
255:
256:
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:
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:
292: public ValueAxis getAxis() {
293: return this.axis;
294: }
295:
296:
302: public void setAxis(ValueAxis axis) {
303: if (axis != null) {
304: axis.setPlot(this);
305: }
306:
307:
308: if (this.axis != null) {
309: this.axis.removeChangeListener(this);
310: }
311:
312: this.axis = axis;
313: if (this.axis != null) {
314: this.axis.configure();
315: this.axis.addChangeListener(this);
316: }
317: notifyListeners(new PlotChangeEvent(this));
318: }
319:
320:
325: public XYDataset getDataset() {
326: return this.dataset;
327: }
328:
329:
335: public void setDataset(XYDataset dataset) {
336:
337:
338: XYDataset existing = this.dataset;
339: if (existing != null) {
340: existing.removeChangeListener(this);
341: }
342:
343:
344: this.dataset = dataset;
345: if (this.dataset != null) {
346: setDatasetGroup(this.dataset.getGroup());
347: this.dataset.addChangeListener(this);
348: }
349:
350:
351: DatasetChangeEvent event = new DatasetChangeEvent(this, this.dataset);
352: datasetChanged(event);
353: }
354:
355:
360: public PolarItemRenderer getRenderer() {
361: return this.renderer;
362: }
363:
364:
372: public void setRenderer(PolarItemRenderer renderer) {
373: if (this.renderer != null) {
374: this.renderer.removeChangeListener(this);
375: }
376:
377: this.renderer = renderer;
378: if (this.renderer != null) {
379: this.renderer.setPlot(this);
380: }
381:
382: notifyListeners(new PlotChangeEvent(this));
383: }
384:
385:
390: public boolean isAngleLabelsVisible() {
391: return this.angleLabelsVisible;
392: }
393:
394:
400: public void setAngleLabelsVisible(boolean visible) {
401: if (this.angleLabelsVisible != visible) {
402: this.angleLabelsVisible = visible;
403: notifyListeners(new PlotChangeEvent(this));
404: }
405: }
406:
407:
412: public Font getAngleLabelFont() {
413: return this.angleLabelFont;
414: }
415:
416:
422: public void setAngleLabelFont(Font font) {
423: if (font == null) {
424: throw new IllegalArgumentException("Null 'font' argument.");
425: }
426: this.angleLabelFont = font;
427: notifyListeners(new PlotChangeEvent(this));
428: }
429:
430:
435: public Paint getAngleLabelPaint() {
436: return this.angleLabelPaint;
437: }
438:
439:
445: public void setAngleLabelPaint(Paint paint) {
446: this.angleLabelPaint = paint;
447: notifyListeners(new PlotChangeEvent(this));
448: }
449:
450:
456: public boolean isAngleGridlinesVisible() {
457: return this.angleGridlinesVisible;
458: }
459:
460:
469: public void setAngleGridlinesVisible(boolean visible) {
470: if (this.angleGridlinesVisible != visible) {
471: this.angleGridlinesVisible = visible;
472: notifyListeners(new PlotChangeEvent(this));
473: }
474: }
475:
476:
482: public Stroke getAngleGridlineStroke() {
483: return this.angleGridlineStroke;
484: }
485:
486:
493: public void setAngleGridlineStroke(Stroke stroke) {
494: this.angleGridlineStroke = stroke;
495: notifyListeners(new PlotChangeEvent(this));
496: }
497:
498:
504: public Paint getAngleGridlinePaint() {
505: return this.angleGridlinePaint;
506: }
507:
508:
515: public void setAngleGridlinePaint(Paint paint) {
516: this.angleGridlinePaint = paint;
517: notifyListeners(new PlotChangeEvent(this));
518: }
519:
520:
526: public boolean isRadiusGridlinesVisible() {
527: return this.radiusGridlinesVisible;
528: }
529:
530:
539: public void setRadiusGridlinesVisible(boolean visible) {
540: if (this.radiusGridlinesVisible != visible) {
541: this.radiusGridlinesVisible = visible;
542: notifyListeners(new PlotChangeEvent(this));
543: }
544: }
545:
546:
552: public Stroke getRadiusGridlineStroke() {
553: return this.radiusGridlineStroke;
554: }
555:
556:
563: public void setRadiusGridlineStroke(Stroke stroke) {
564: this.radiusGridlineStroke = stroke;
565: notifyListeners(new PlotChangeEvent(this));
566: }
567:
568:
574: public Paint getRadiusGridlinePaint() {
575: return this.radiusGridlinePaint;
576: }
577:
578:
585: public void setRadiusGridlinePaint(Paint paint) {
586: this.radiusGridlinePaint = paint;
587: notifyListeners(new PlotChangeEvent(this));
588: }
589:
590:
611: public void draw(Graphics2D g2,
612: Rectangle2D area,
613: Point2D anchor,
614: PlotState parentState,
615: PlotRenderingInfo info) {
616:
617:
618: boolean b1 = (area.getWidth() <= MINIMUM_WIDTH_TO_DRAW);
619: boolean b2 = (area.getHeight() <= MINIMUM_HEIGHT_TO_DRAW);
620: if (b1 || b2) {
621: return;
622: }
623:
624:
625: if (info != null) {
626: info.setPlotArea(area);
627: }
628:
629:
630: RectangleInsets insets = getInsets();
631: insets.trim(area);
632:
633: Rectangle2D dataArea = area;
634: if (info != null) {
635: info.setDataArea(dataArea);
636: }
637:
638:
639: drawBackground(g2, dataArea);
640: double h = Math.min(
641: dataArea.getWidth() / 2.0, dataArea.getHeight() / 2.0
642: ) - MARGIN;
643: Rectangle2D quadrant = new Rectangle2D.Double(
644: dataArea.getCenterX(), dataArea.getCenterY(), h, h
645: );
646: AxisState state = drawAxis(g2, area, quadrant);
647: if (this.renderer != null) {
648: Shape originalClip = g2.getClip();
649: Composite originalComposite = g2.getComposite();
650:
651: g2.clip(dataArea);
652: g2.setComposite(
653: AlphaComposite.getInstance(
654: AlphaComposite.SRC_OVER, getForegroundAlpha()
655: )
656: );
657:
658: drawGridlines(g2, dataArea, this.angleTicks, state.getTicks());
659:
660:
661: render(g2, dataArea, info);
662:
663: g2.setClip(originalClip);
664: g2.setComposite(originalComposite);
665: }
666: drawOutline(g2, dataArea);
667: drawCornerTextItems(g2, dataArea);
668: }
669:
670:
676: protected void drawCornerTextItems(Graphics2D g2, Rectangle2D area) {
677: if (this.cornerTextItems.isEmpty()) {
678: return;
679: }
680:
681: g2.setColor(Color.black);
682: double width = 0.0;
683: double height = 0.0;
684: for (Iterator it = this.cornerTextItems.iterator(); it.hasNext();) {
685: String msg = (String) it.next();
686: FontMetrics fm = g2.getFontMetrics();
687: Rectangle2D bounds = TextUtilities.getTextBounds(msg, g2, fm);
688: width = Math.max(width, bounds.getWidth());
689: height += bounds.getHeight();
690: }
691:
692: double xadj = ANNOTATION_MARGIN * 2.0;
693: double yadj = ANNOTATION_MARGIN;
694: width += xadj;
695: height += yadj;
696:
697: double x = area.getMaxX() - width;
698: double y = area.getMaxY() - height;
699: g2.drawRect((int) x, (int) y, (int) width, (int) height);
700: x += ANNOTATION_MARGIN;
701: for (Iterator it = this.cornerTextItems.iterator(); it.hasNext();) {
702: String msg = (String) it.next();
703: Rectangle2D bounds = TextUtilities.getTextBounds(
704: msg, g2, g2.getFontMetrics()
705: );
706: y += bounds.getHeight();
707: g2.drawString(msg, (int) x, (int) y);
708: }
709: }
710:
711:
720: protected AxisState drawAxis(Graphics2D g2, Rectangle2D plotArea,
721: Rectangle2D dataArea) {
722: return this.axis.draw(
723: g2, dataArea.getMinY(), plotArea, dataArea, RectangleEdge.TOP, null
724: );
725: }
726:
727:
736: protected void render(Graphics2D g2,
737: Rectangle2D dataArea,
738: PlotRenderingInfo info) {
739:
740:
741:
742: if (!DatasetUtilities.isEmptyOrNull(this.dataset)) {
743: int seriesCount = this.dataset.getSeriesCount();
744: for (int series = 0; series < seriesCount; series++) {
745: this.renderer.drawSeries(
746: g2, dataArea, info, this, this.dataset, series
747: );
748: }
749: }
750: else {
751: drawNoDataMessage(g2, dataArea);
752: }
753: }
754:
755:
763: protected void drawGridlines(Graphics2D g2, Rectangle2D dataArea,
764: List angularTicks, List radialTicks) {
765:
766:
767: if (this.renderer == null) {
768: return;
769: }
770:
771:
772: if (isAngleGridlinesVisible()) {
773: Stroke gridStroke = getAngleGridlineStroke();
774: Paint gridPaint = getAngleGridlinePaint();
775: if ((gridStroke != null) && (gridPaint != null)) {
776: this.renderer.drawAngularGridLines(
777: g2, this, angularTicks, dataArea
778: );
779: }
780: }
781:
782:
783: if (isRadiusGridlinesVisible()) {
784: Stroke gridStroke = getRadiusGridlineStroke();
785: Paint gridPaint = getRadiusGridlinePaint();
786: if ((gridStroke != null) && (gridPaint != null)) {
787: this.renderer.drawRadialGridLines(
788: g2, this, this.axis, radialTicks, dataArea
789: );
790: }
791: }
792: }
793:
794:
799: public void zoom(double percent) {
800: if (percent > 0.0) {
801: double radius = getMaxRadius();
802: double scaledRadius = radius * percent;
803: this.axis.setUpperBound(scaledRadius);
804: getAxis().setAutoRange(false);
805: }
806: else {
807: getAxis().setAutoRange(true);
808: }
809: }
810:
811:
818: public Range getDataRange(ValueAxis axis) {
819: Range result = null;
820: if (this.dataset != null) {
821: result = Range.combine(result,
822: DatasetUtilities.findRangeBounds(this.dataset));
823: }
824: return result;
825: }
826:
827:
834: public void datasetChanged(DatasetChangeEvent event) {
835:
836: if (this.axis != null) {
837: this.axis.configure();
838: }
839:
840: if (getParent() != null) {
841: getParent().datasetChanged(event);
842: }
843: else {
844: super.datasetChanged(event);
845: }
846: }
847:
848:
855: public void rendererChanged(RendererChangeEvent event) {
856: notifyListeners(new PlotChangeEvent(this));
857: }
858:
859:
865: public int getSeriesCount() {
866: int result = 0;
867:
868: if (this.dataset != null) {
869: result = this.dataset.getSeriesCount();
870: }
871: return result;
872: }
873:
874:
881: public LegendItemCollection getLegendItems() {
882: LegendItemCollection result = new LegendItemCollection();
883:
884:
885: if (this.dataset != null) {
886: if (this.renderer != null) {
887: int seriesCount = this.dataset.getSeriesCount();
888: for (int i = 0; i < seriesCount; i++) {
889: LegendItem item = this.renderer.getLegendItem(i);
890: result.add(item);
891: }
892: }
893: }
894: return result;
895: }
896:
897:
904: public boolean equals(Object obj) {
905: if (obj == this) {
906: return true;
907: }
908: if (!(obj instanceof PolarPlot)) {
909: return false;
910: }
911: if (!super.equals(obj)) {
912: return false;
913: }
914: PolarPlot that = (PolarPlot) obj;
915: if (!ObjectUtilities.equal(this.axis, that.axis)) {
916: return false;
917: }
918: if (!ObjectUtilities.equal(this.renderer, that.renderer)) {
919: return false;
920: }
921: if (this.angleGridlinesVisible != that.angleGridlinesVisible) {
922: return false;
923: }
924: if (this.angleLabelsVisible != that.angleLabelsVisible) {
925: return false;
926: }
927: if (!this.angleLabelFont.equals(that.angleLabelFont)) {
928: return false;
929: }
930: if (!PaintUtilities.equal(this.angleLabelPaint, that.angleLabelPaint)) {
931: return false;
932: }
933: if (!ObjectUtilities.equal(
934: this.angleGridlineStroke, that.angleGridlineStroke
935: )) {
936: return false;
937: }
938: if (!PaintUtilities.equal(
939: this.angleGridlinePaint, that.angleGridlinePaint
940: )) {
941: return false;
942: }
943: if (this.radiusGridlinesVisible != that.radiusGridlinesVisible) {
944: return false;
945: }
946: if (!ObjectUtilities.equal(
947: this.radiusGridlineStroke, that.radiusGridlineStroke
948: )) {
949: return false;
950: }
951: if (!PaintUtilities.equal(
952: this.radiusGridlinePaint, that.radiusGridlinePaint
953: )) {
954: return false;
955: }
956: return true;
957: }
958:
959:
967: public Object clone() throws CloneNotSupportedException {
968:
969: PolarPlot clone = (PolarPlot) super.clone();
970: if (this.axis != null) {
971: clone.axis = (ValueAxis) ObjectUtilities.clone(this.axis);
972: clone.axis.setPlot(clone);
973: clone.axis.addChangeListener(clone);
974: }
975:
976: if (clone.dataset != null) {
977: clone.dataset.addChangeListener(clone);
978: }
979:
980: if (this.renderer != null) {
981: clone.renderer
982: = (PolarItemRenderer) ObjectUtilities.clone(this.renderer);
983: }
984:
985: return clone;
986: }
987:
988:
995: private void writeObject(ObjectOutputStream stream) throws IOException {
996: stream.defaultWriteObject();
997: SerialUtilities.writeStroke(this.angleGridlineStroke, stream);
998: SerialUtilities.writePaint(this.angleGridlinePaint, stream);
999: SerialUtilities.writeStroke(this.radiusGridlineStroke, stream);
1000: SerialUtilities.writePaint(this.radiusGridlinePaint, stream);
1001: }
1002:
1003:
1011: private void readObject(ObjectInputStream stream)
1012: throws IOException, ClassNotFoundException {
1013:
1014: stream.defaultReadObject();
1015: this.angleGridlineStroke = SerialUtilities.readStroke(stream);
1016: this.angleGridlinePaint = SerialUtilities.readPaint(stream);
1017: this.radiusGridlineStroke = SerialUtilities.readStroke(stream);
1018: this.radiusGridlinePaint = SerialUtilities.readPaint(stream);
1019:
1020: if (this.axis != null) {
1021: this.axis.setPlot(this);
1022: this.axis.addChangeListener(this);
1023: }
1024:
1025: if (this.dataset != null) {
1026: this.dataset.addChangeListener(this);
1027: }
1028: }
1029:
1030:
1038: public void zoomDomainAxes(double factor, PlotRenderingInfo state,
1039: Point2D source) {
1040:
1041: }
1042:
1043:
1052: public void zoomDomainAxes(double lowerPercent, double upperPercent,
1053: PlotRenderingInfo state, Point2D source) {
1054:
1055: }
1056:
1057:
1064: public void zoomRangeAxes(double factor, PlotRenderingInfo state,
1065: Point2D source) {
1066: zoom(factor);
1067: }
1068:
1069:
1077: public void zoomRangeAxes(double lowerPercent, double upperPercent,
1078: PlotRenderingInfo state, Point2D source) {
1079: zoom((upperPercent + lowerPercent) / 2.0);
1080: }
1081:
1082:
1087: public boolean isDomainZoomable() {
1088: return false;
1089: }
1090:
1091:
1096: public boolean isRangeZoomable() {
1097: return true;
1098: }
1099:
1100:
1105: public PlotOrientation getOrientation() {
1106: return PlotOrientation.HORIZONTAL;
1107: }
1108:
1109:
1110:
1111:
1112:
1113:
1114:
1119: public double getMaxRadius() {
1120: return this.axis.getUpperBound();
1121: }
1122:
1123:
1132: public Point translateValueThetaRadiusToJava2D(double angleDegrees,
1133: double radius,
1134: Rectangle2D dataArea) {
1135:
1136: double radians = Math.toRadians(angleDegrees - 90.0);
1137:
1138: double minx = dataArea.getMinX() + MARGIN;
1139: double maxx = dataArea.getMaxX() - MARGIN;
1140: double miny = dataArea.getMinY() + MARGIN;
1141: double maxy = dataArea.getMaxY() - MARGIN;
1142:
1143: double lengthX = maxx - minx;
1144: double lengthY = maxy - miny;
1145: double length = Math.min(lengthX, lengthY);
1146:
1147: double midX = minx + lengthX / 2.0;
1148: double midY = miny + lengthY / 2.0;
1149:
1150: double axisMin = this.axis.getLowerBound();
1151: double axisMax = getMaxRadius();
1152:
1153: double xv = length / 2.0 * Math.cos(radians);
1154: double yv = length / 2.0 * Math.sin(radians);
1155:
1156: float x = (float) (midX + (xv * (radius - axisMin)
1157: / (axisMax - axisMin)));
1158: float y = (float) (midY + (yv * (radius - axisMin)
1159: / (axisMax - axisMin)));
1160:
1161: int ix = Math.round(x);
1162: int iy = Math.round(y);
1163:
1164: Point p = new Point(ix, iy);
1165: return p;
1166:
1167: }
1168:
1169: }