1 46 47 package org.jfree.chart.plot; 48 49 import java.awt.AlphaComposite ; 50 import java.awt.BasicStroke ; 51 import java.awt.Color ; 52 import java.awt.Composite ; 53 import java.awt.Font ; 54 import java.awt.FontMetrics ; 55 import java.awt.Graphics2D ; 56 import java.awt.Paint ; 57 import java.awt.Point ; 58 import java.awt.Shape ; 59 import java.awt.Stroke ; 60 import java.awt.geom.Point2D ; 61 import java.awt.geom.Rectangle2D ; 62 import java.io.IOException ; 63 import java.io.ObjectInputStream ; 64 import java.io.ObjectOutputStream ; 65 import java.io.Serializable ; 66 import java.util.ArrayList ; 67 import java.util.Iterator ; 68 import java.util.List ; 69 import java.util.ResourceBundle ; 70 71 import org.jfree.chart.LegendItem; 72 import org.jfree.chart.LegendItemCollection; 73 import org.jfree.chart.axis.AxisState; 74 import org.jfree.chart.axis.NumberTick; 75 import org.jfree.chart.axis.ValueAxis; 76 import org.jfree.chart.event.PlotChangeEvent; 77 import org.jfree.chart.event.RendererChangeEvent; 78 import org.jfree.chart.event.RendererChangeListener; 79 import org.jfree.chart.renderer.PolarItemRenderer; 80 import org.jfree.data.Range; 81 import org.jfree.data.general.DatasetChangeEvent; 82 import org.jfree.data.general.DatasetUtilities; 83 import org.jfree.data.xy.XYDataset; 84 import org.jfree.io.SerialUtilities; 85 import org.jfree.text.TextUtilities; 86 import org.jfree.ui.RectangleEdge; 87 import org.jfree.ui.RectangleInsets; 88 import org.jfree.ui.TextAnchor; 89 import org.jfree.util.ObjectUtilities; 90 import org.jfree.util.PaintUtilities; 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 = new BasicStroke ( 116 0.5f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 117 0.0f, new float[]{2.0f, 2.0f}, 0.0f); 118 119 120 public static final Paint DEFAULT_GRIDLINE_PAINT = Color.gray; 121 122 123 protected static ResourceBundle localizationResources 124 = ResourceBundle.getBundle("org.jfree.chart.plot.LocalizationBundle"); 125 126 130 private List angleTicks; 131 132 133 private ValueAxis axis; 134 135 136 private XYDataset dataset; 137 138 142 private PolarItemRenderer renderer; 143 144 145 private boolean angleLabelsVisible = true; 146 147 148 private Font angleLabelFont = new Font ("SansSerif", Font.PLAIN, 12); 149 150 151 private Paint angleLabelPaint = Color.black; 152 153 154 private boolean angleGridlinesVisible; 155 156 157 private transient Stroke angleGridlineStroke; 158 159 160 private transient Paint angleGridlinePaint; 161 162 163 private boolean radiusGridlinesVisible; 164 165 166 private transient Stroke radiusGridlineStroke; 167 168 169 private transient Paint radiusGridlinePaint; 170 171 172 private List cornerTextItems = new ArrayList (); 173 174 180 public PolarPlot() { 181 this(null, null, null); 182 } 183 184 191 public PolarPlot(XYDataset dataset, 192 ValueAxis radiusAxis, 193 PolarItemRenderer renderer) { 194 195 super(); 196 197 this.dataset = dataset; 198 if (this.dataset != null) { 199 this.dataset.addChangeListener(this); 200 } 201 202 this.angleTicks = new java.util.ArrayList (); 203 this.angleTicks.add(new NumberTick(new Double (0.0), "0", 204 TextAnchor.CENTER, TextAnchor.CENTER, 0.0)); 205 this.angleTicks.add(new NumberTick(new Double (45.0), "45", 206 TextAnchor.CENTER, TextAnchor.CENTER, 0.0)); 207 this.angleTicks.add(new NumberTick(new Double (90.0), "90", 208 TextAnchor.CENTER, TextAnchor.CENTER, 0.0)); 209 this.angleTicks.add(new NumberTick(new Double (135.0), "135", 210 TextAnchor.CENTER, TextAnchor.CENTER, 0.0)); 211 this.angleTicks.add(new NumberTick(new Double (180.0), "180", 212 TextAnchor.CENTER, TextAnchor.CENTER, 0.0)); 213 this.angleTicks.add(new NumberTick(new Double (225.0), "225", 214 TextAnchor.CENTER, TextAnchor.CENTER, 0.0)); 215 this.angleTicks.add(new NumberTick(new Double (270.0), "270", 216 TextAnchor.CENTER, TextAnchor.CENTER, 0.0)); 217 this.angleTicks.add(new NumberTick(new Double (315.0), "315", 218 TextAnchor.CENTER, TextAnchor.CENTER, 0.0)); 219 220 this.axis = radiusAxis; 221 if (this.axis != null) { 222 this.axis.setPlot(this); 223 this.axis.addChangeListener(this); 224 } 225 226 this.renderer = renderer; 227 if (this.renderer != null) { 228 this.renderer.setPlot(this); 229 this.renderer.addChangeListener(this); 230 } 231 232 this.angleGridlinesVisible = true; 233 this.angleGridlineStroke = DEFAULT_GRIDLINE_STROKE; 234 this.angleGridlinePaint = DEFAULT_GRIDLINE_PAINT; 235 236 this.radiusGridlinesVisible = true; 237 this.radiusGridlineStroke = DEFAULT_GRIDLINE_STROKE; 238 this.radiusGridlinePaint = DEFAULT_GRIDLINE_PAINT; 239 } 240 241 246 public void addCornerTextItem(String text) { 247 if (text == null) { 248 throw new IllegalArgumentException ("Null 'text' argument."); 249 } 250 this.cornerTextItems.add(text); 251 this.notifyListeners(new PlotChangeEvent(this)); 252 } 253 254 259 public void removeCornerTextItem(String text) { 260 boolean removed = this.cornerTextItems.remove(text); 261 if (removed) { 262 this.notifyListeners(new PlotChangeEvent(this)); 263 } 264 } 265 266 269 public void clearCornerTextItems() { 270 if (this.cornerTextItems.size() > 0) { 271 this.cornerTextItems.clear(); 272 this.notifyListeners(new PlotChangeEvent(this)); 273 } 274 } 275 276 281 public String getPlotType() { 282 return PolarPlot.localizationResources.getString("Polar_Plot"); 283 } 284 285 290 public ValueAxis getAxis() { 291 return this.axis; 292 } 293 294 300 public void setAxis(ValueAxis axis) { 301 if (axis != null) { 302 axis.setPlot(this); 303 } 304 305 if (this.axis != null) { 307 this.axis.removeChangeListener(this); 308 } 309 310 this.axis = axis; 311 if (this.axis != null) { 312 this.axis.configure(); 313 this.axis.addChangeListener(this); 314 } 315 notifyListeners(new PlotChangeEvent(this)); 316 } 317 318 323 public XYDataset getDataset() { 324 return this.dataset; 325 } 326 327 333 public void setDataset(XYDataset dataset) { 334 XYDataset existing = this.dataset; 337 if (existing != null) { 338 existing.removeChangeListener(this); 339 } 340 341 this.dataset = dataset; 343 if (this.dataset != null) { 344 setDatasetGroup(this.dataset.getGroup()); 345 this.dataset.addChangeListener(this); 346 } 347 348 DatasetChangeEvent event = new DatasetChangeEvent(this, this.dataset); 350 datasetChanged(event); 351 } 352 353 358 public PolarItemRenderer getRenderer() { 359 return this.renderer; 360 } 361 362 370 public void setRenderer(PolarItemRenderer renderer) { 371 if (this.renderer != null) { 372 this.renderer.removeChangeListener(this); 373 } 374 375 this.renderer = renderer; 376 if (this.renderer != null) { 377 this.renderer.setPlot(this); 378 } 379 380 notifyListeners(new PlotChangeEvent(this)); 381 } 382 383 388 public boolean isAngleLabelsVisible() { 389 return this.angleLabelsVisible; 390 } 391 392 398 public void setAngleLabelsVisible(boolean visible) { 399 if (this.angleLabelsVisible != visible) { 400 this.angleLabelsVisible = visible; 401 notifyListeners(new PlotChangeEvent(this)); 402 } 403 } 404 405 410 public Font getAngleLabelFont() { 411 return this.angleLabelFont; 412 } 413 414 420 public void setAngleLabelFont(Font font) { 421 if (font == null) { 422 throw new IllegalArgumentException ("Null 'font' argument."); 423 } 424 this.angleLabelFont = font; 425 notifyListeners(new PlotChangeEvent(this)); 426 } 427 428 433 public Paint getAngleLabelPaint() { 434 return this.angleLabelPaint; 435 } 436 437 443 public void setAngleLabelPaint(Paint paint) { 444 this.angleLabelPaint = paint; 445 notifyListeners(new PlotChangeEvent(this)); 446 } 447 448 454 public boolean isAngleGridlinesVisible() { 455 return this.angleGridlinesVisible; 456 } 457 458 467 public void setAngleGridlinesVisible(boolean visible) { 468 if (this.angleGridlinesVisible != visible) { 469 this.angleGridlinesVisible = visible; 470 notifyListeners(new PlotChangeEvent(this)); 471 } 472 } 473 474 480 public Stroke getAngleGridlineStroke() { 481 return this.angleGridlineStroke; 482 } 483 484 491 public void setAngleGridlineStroke(Stroke stroke) { 492 this.angleGridlineStroke = stroke; 493 notifyListeners(new PlotChangeEvent(this)); 494 } 495 496 502 public Paint getAngleGridlinePaint() { 503 return this.angleGridlinePaint; 504 } 505 506 513 public void setAngleGridlinePaint(Paint paint) { 514 this.angleGridlinePaint = paint; 515 notifyListeners(new PlotChangeEvent(this)); 516 } 517 518 524 public boolean isRadiusGridlinesVisible() { 525 return this.radiusGridlinesVisible; 526 } 527 528 537 public void setRadiusGridlinesVisible(boolean visible) { 538 if (this.radiusGridlinesVisible != visible) { 539 this.radiusGridlinesVisible = visible; 540 notifyListeners(new PlotChangeEvent(this)); 541 } 542 } 543 544 550 public Stroke getRadiusGridlineStroke() { 551 return this.radiusGridlineStroke; 552 } 553 554 561 public void setRadiusGridlineStroke(Stroke stroke) { 562 this.radiusGridlineStroke = stroke; 563 notifyListeners(new PlotChangeEvent(this)); 564 } 565 566 572 public Paint getRadiusGridlinePaint() { 573 return this.radiusGridlinePaint; 574 } 575 576 583 public void setRadiusGridlinePaint(Paint paint) { 584 this.radiusGridlinePaint = paint; 585 notifyListeners(new PlotChangeEvent(this)); 586 } 587 588 609 public void draw(Graphics2D g2, 610 Rectangle2D area, 611 Point2D anchor, 612 PlotState parentState, 613 PlotRenderingInfo info) { 614 615 boolean b1 = (area.getWidth() <= MINIMUM_WIDTH_TO_DRAW); 617 boolean b2 = (area.getHeight() <= MINIMUM_HEIGHT_TO_DRAW); 618 if (b1 || b2) { 619 return; 620 } 621 622 if (info != null) { 624 info.setPlotArea(area); 625 } 626 627 RectangleInsets insets = getInsets(); 629 insets.trim(area); 630 631 Rectangle2D dataArea = area; 632 if (info != null) { 633 info.setDataArea(dataArea); 634 } 635 636 drawBackground(g2, dataArea); 638 double h = Math.min(dataArea.getWidth() / 2.0, 639 dataArea.getHeight() / 2.0) - MARGIN; 640 Rectangle2D quadrant = new Rectangle2D.Double (dataArea.getCenterX(), 641 dataArea.getCenterY(), h, h); 642 AxisState state = drawAxis(g2, area, quadrant); 643 if (this.renderer != null) { 644 Shape originalClip = g2.getClip(); 645 Composite originalComposite = g2.getComposite(); 646 647 g2.clip(dataArea); 648 g2.setComposite(AlphaComposite.getInstance( 649 AlphaComposite.SRC_OVER, getForegroundAlpha())); 650 651 drawGridlines(g2, dataArea, this.angleTicks, state.getTicks()); 652 653 render(g2, dataArea, info); 655 656 g2.setClip(originalClip); 657 g2.setComposite(originalComposite); 658 } 659 drawOutline(g2, dataArea); 660 drawCornerTextItems(g2, dataArea); 661 } 662 663 669 protected void drawCornerTextItems(Graphics2D g2, Rectangle2D area) { 670 if (this.cornerTextItems.isEmpty()) { 671 return; 672 } 673 674 g2.setColor(Color.black); 675 double width = 0.0; 676 double height = 0.0; 677 for (Iterator it = this.cornerTextItems.iterator(); it.hasNext();) { 678 String msg = (String ) it.next(); 679 FontMetrics fm = g2.getFontMetrics(); 680 Rectangle2D bounds = TextUtilities.getTextBounds(msg, g2, fm); 681 width = Math.max(width, bounds.getWidth()); 682 height += bounds.getHeight(); 683 } 684 685 double xadj = ANNOTATION_MARGIN * 2.0; 686 double yadj = ANNOTATION_MARGIN; 687 width += xadj; 688 height += yadj; 689 690 double x = area.getMaxX() - width; 691 double y = area.getMaxY() - height; 692 g2.drawRect((int) x, (int) y, (int) width, (int) height); 693 x += ANNOTATION_MARGIN; 694 for (Iterator it = this.cornerTextItems.iterator(); it.hasNext();) { 695 String msg = (String ) it.next(); 696 Rectangle2D bounds = TextUtilities.getTextBounds(msg, g2, 697 g2.getFontMetrics()); 698 y += bounds.getHeight(); 699 g2.drawString(msg, (int) x, (int) y); 700 } 701 } 702 703 712 protected AxisState drawAxis(Graphics2D g2, Rectangle2D plotArea, 713 Rectangle2D dataArea) { 714 return this.axis.draw(g2, dataArea.getMinY(), plotArea, dataArea, 715 RectangleEdge.TOP, null); 716 } 717 718 727 protected void render(Graphics2D g2, 728 Rectangle2D dataArea, 729 PlotRenderingInfo info) { 730 731 if (!DatasetUtilities.isEmptyOrNull(this.dataset)) { 734 int seriesCount = this.dataset.getSeriesCount(); 735 for (int series = 0; series < seriesCount; series++) { 736 this.renderer.drawSeries(g2, dataArea, info, this, 737 this.dataset, series); 738 } 739 } 740 else { 741 drawNoDataMessage(g2, dataArea); 742 } 743 } 744 745 753 protected void drawGridlines(Graphics2D g2, Rectangle2D dataArea, 754 List angularTicks, List radialTicks) { 755 756 if (this.renderer == null) { 758 return; 759 } 760 761 if (isAngleGridlinesVisible()) { 763 Stroke gridStroke = getAngleGridlineStroke(); 764 Paint gridPaint = getAngleGridlinePaint(); 765 if ((gridStroke != null) && (gridPaint != null)) { 766 this.renderer.drawAngularGridLines(g2, this, angularTicks, 767 dataArea); 768 } 769 } 770 771 if (isRadiusGridlinesVisible()) { 773 Stroke gridStroke = getRadiusGridlineStroke(); 774 Paint gridPaint = getRadiusGridlinePaint(); 775 if ((gridStroke != null) && (gridPaint != null)) { 776 this.renderer.drawRadialGridLines(g2, this, this.axis, 777 radialTicks, dataArea); 778 } 779 } 780 } 781 782 787 public void zoom(double percent) { 788 if (percent > 0.0) { 789 double radius = getMaxRadius(); 790 double scaledRadius = radius * percent; 791 this.axis.setUpperBound(scaledRadius); 792 getAxis().setAutoRange(false); 793 } 794 else { 795 getAxis().setAutoRange(true); 796 } 797 } 798 799 806 public Range getDataRange(ValueAxis axis) { 807 Range result = null; 808 if (this.dataset != null) { 809 result = Range.combine(result, 810 DatasetUtilities.findRangeBounds(this.dataset)); 811 } 812 return result; 813 } 814 815 822 public void datasetChanged(DatasetChangeEvent event) { 823 824 if (this.axis != null) { 825 this.axis.configure(); 826 } 827 828 if (getParent() != null) { 829 getParent().datasetChanged(event); 830 } 831 else { 832 super.datasetChanged(event); 833 } 834 } 835 836 843 public void rendererChanged(RendererChangeEvent event) { 844 notifyListeners(new PlotChangeEvent(this)); 845 } 846 847 853 public int getSeriesCount() { 854 int result = 0; 855 856 if (this.dataset != null) { 857 result = this.dataset.getSeriesCount(); 858 } 859 return result; 860 } 861 862 869 public LegendItemCollection getLegendItems() { 870 LegendItemCollection result = new LegendItemCollection(); 871 872 if (this.dataset != null) { 874 if (this.renderer != null) { 875 int seriesCount = this.dataset.getSeriesCount(); 876 for (int i = 0; i < seriesCount; i++) { 877 LegendItem item = this.renderer.getLegendItem(i); 878 result.add(item); 879 } 880 } 881 } 882 return result; 883 } 884 885 892 public boolean equals(Object obj) { 893 if (obj == this) { 894 return true; 895 } 896 if (!(obj instanceof PolarPlot)) { 897 return false; 898 } 899 if (!super.equals(obj)) { 900 return false; 901 } 902 PolarPlot that = (PolarPlot) obj; 903 if (!ObjectUtilities.equal(this.axis, that.axis)) { 904 return false; 905 } 906 if (!ObjectUtilities.equal(this.renderer, that.renderer)) { 907 return false; 908 } 909 if (this.angleGridlinesVisible != that.angleGridlinesVisible) { 910 return false; 911 } 912 if (this.angleLabelsVisible != that.angleLabelsVisible) { 913 return false; 914 } 915 if (!this.angleLabelFont.equals(that.angleLabelFont)) { 916 return false; 917 } 918 if (!PaintUtilities.equal(this.angleLabelPaint, that.angleLabelPaint)) { 919 return false; 920 } 921 if (!ObjectUtilities.equal(this.angleGridlineStroke, 922 that.angleGridlineStroke)) { 923 return false; 924 } 925 if (!PaintUtilities.equal( 926 this.angleGridlinePaint, that.angleGridlinePaint 927 )) { 928 return false; 929 } 930 if (this.radiusGridlinesVisible != that.radiusGridlinesVisible) { 931 return false; 932 } 933 if (!ObjectUtilities.equal(this.radiusGridlineStroke, 934 that.radiusGridlineStroke)) { 935 return false; 936 } 937 if (!PaintUtilities.equal(this.radiusGridlinePaint, 938 that.radiusGridlinePaint)) { 939 return false; 940 } 941 return true; 942 } 943 944 952 public Object clone() throws CloneNotSupportedException { 953 954 PolarPlot clone = (PolarPlot) super.clone(); 955 if (this.axis != null) { 956 clone.axis = (ValueAxis) ObjectUtilities.clone(this.axis); 957 clone.axis.setPlot(clone); 958 clone.axis.addChangeListener(clone); 959 } 960 961 if (clone.dataset != null) { 962 clone.dataset.addChangeListener(clone); 963 } 964 965 if (this.renderer != null) { 966 clone.renderer 967 = (PolarItemRenderer) ObjectUtilities.clone(this.renderer); 968 } 969 970 return clone; 971 } 972 973 980 private void writeObject(ObjectOutputStream stream) throws IOException { 981 stream.defaultWriteObject(); 982 SerialUtilities.writeStroke(this.angleGridlineStroke, stream); 983 SerialUtilities.writePaint(this.angleGridlinePaint, stream); 984 SerialUtilities.writeStroke(this.radiusGridlineStroke, stream); 985 SerialUtilities.writePaint(this.radiusGridlinePaint, stream); 986 } 987 988 996 private void readObject(ObjectInputStream stream) 997 throws IOException , ClassNotFoundException { 998 999 stream.defaultReadObject(); 1000 this.angleGridlineStroke = SerialUtilities.readStroke(stream); 1001 this.angleGridlinePaint = SerialUtilities.readPaint(stream); 1002 this.radiusGridlineStroke = SerialUtilities.readStroke(stream); 1003 this.radiusGridlinePaint = SerialUtilities.readPaint(stream); 1004 1005 if (this.axis != null) { 1006 this.axis.setPlot(this); 1007 this.axis.addChangeListener(this); 1008 } 1009 1010 if (this.dataset != null) { 1011 this.dataset.addChangeListener(this); 1012 } 1013 } 1014 1015 1023 public void zoomDomainAxes(double factor, PlotRenderingInfo state, 1024 Point2D source) { 1025 } 1027 1028 1037 public void zoomDomainAxes(double lowerPercent, double upperPercent, 1038 PlotRenderingInfo state, Point2D source) { 1039 } 1041 1042 1049 public void zoomRangeAxes(double factor, PlotRenderingInfo state, 1050 Point2D source) { 1051 zoom(factor); 1052 } 1053 1054 1062 public void zoomRangeAxes(double lowerPercent, double upperPercent, 1063 PlotRenderingInfo state, Point2D source) { 1064 zoom((upperPercent + lowerPercent) / 2.0); 1065 } 1066 1067 1072 public boolean isDomainZoomable() { 1073 return false; 1074 } 1075 1076 1081 public boolean isRangeZoomable() { 1082 return true; 1083 } 1084 1085 1090 public PlotOrientation getOrientation() { 1091 return PlotOrientation.HORIZONTAL; 1092 } 1093 1094 1099 public double getMaxRadius() { 1100 return this.axis.getUpperBound(); 1101 } 1102 1103 1112 public Point translateValueThetaRadiusToJava2D(double angleDegrees, 1113 double radius, 1114 Rectangle2D dataArea) { 1115 1116 double radians = Math.toRadians(angleDegrees - 90.0); 1117 1118 double minx = dataArea.getMinX() + MARGIN; 1119 double maxx = dataArea.getMaxX() - MARGIN; 1120 double miny = dataArea.getMinY() + MARGIN; 1121 double maxy = dataArea.getMaxY() - MARGIN; 1122 1123 double lengthX = maxx - minx; 1124 double lengthY = maxy - miny; 1125 double length = Math.min(lengthX, lengthY); 1126 1127 double midX = minx + lengthX / 2.0; 1128 double midY = miny + lengthY / 2.0; 1129 1130 double axisMin = this.axis.getLowerBound(); 1131 double axisMax = getMaxRadius(); 1132 1133 double xv = length / 2.0 * Math.cos(radians); 1134 double yv = length / 2.0 * Math.sin(radians); 1135 1136 float x = (float) (midX + (xv * (radius - axisMin) 1137 / (axisMax - axisMin))); 1138 float y = (float) (midY + (yv * (radius - axisMin) 1139 / (axisMax - axisMin))); 1140 1141 int ix = Math.round(x); 1142 int iy = Math.round(y); 1143 1144 Point p = new Point (ix, iy); 1145 return p; 1146 1147 } 1148 1149} 1150 | Popular Tags |