1 73 74 package org.jfree.chart.axis; 75 76 import java.awt.Graphics2D ; 77 import java.awt.Shape ; 78 import java.awt.geom.Point2D ; 79 import java.awt.geom.Rectangle2D ; 80 import java.io.IOException ; 81 import java.io.ObjectInputStream ; 82 import java.io.ObjectOutputStream ; 83 import java.io.Serializable ; 84 import java.util.HashMap ; 85 import java.util.Iterator ; 86 import java.util.List ; 87 import java.util.Map ; 88 89 import org.jfree.chart.entity.EntityCollection; 90 import org.jfree.chart.entity.TickLabelEntity; 91 import org.jfree.chart.event.AxisChangeEvent; 92 import org.jfree.chart.plot.CategoryPlot; 93 import org.jfree.chart.plot.Plot; 94 import org.jfree.chart.plot.PlotRenderingInfo; 95 import org.jfree.text.G2TextMeasurer; 96 import org.jfree.text.TextBlock; 97 import org.jfree.text.TextUtilities; 98 import org.jfree.ui.RectangleAnchor; 99 import org.jfree.ui.RectangleEdge; 100 import org.jfree.ui.RectangleInsets; 101 import org.jfree.ui.Size2D; 102 import org.jfree.util.ObjectUtilities; 103 import org.jfree.util.ShapeUtilities; 104 105 108 public class CategoryAxis extends Axis implements Cloneable , Serializable { 109 110 111 private static final long serialVersionUID = 5886554608114265863L; 112 113 116 public static final double DEFAULT_AXIS_MARGIN = 0.05; 117 118 122 public static final double DEFAULT_CATEGORY_MARGIN = 0.20; 123 124 125 private double lowerMargin; 126 127 128 private double upperMargin; 129 130 131 private double categoryMargin; 132 133 134 private int maximumCategoryLabelLines; 135 136 140 private float maximumCategoryLabelWidthRatio; 141 142 143 private int categoryLabelPositionOffset; 144 145 149 private CategoryLabelPositions categoryLabelPositions; 150 151 152 private Map categoryLabelToolTips; 153 154 157 public CategoryAxis() { 158 this(null); 159 } 160 161 166 public CategoryAxis(String label) { 167 168 super(label); 169 170 this.lowerMargin = DEFAULT_AXIS_MARGIN; 171 this.upperMargin = DEFAULT_AXIS_MARGIN; 172 this.categoryMargin = DEFAULT_CATEGORY_MARGIN; 173 this.maximumCategoryLabelLines = 1; 174 this.maximumCategoryLabelWidthRatio = 0.0f; 175 176 setTickMarksVisible(false); 178 this.categoryLabelPositionOffset = 4; 179 this.categoryLabelPositions = CategoryLabelPositions.STANDARD; 180 this.categoryLabelToolTips = new HashMap (); 181 182 } 183 184 189 public double getLowerMargin() { 190 return this.lowerMargin; 191 } 192 193 200 public void setLowerMargin(double margin) { 201 this.lowerMargin = margin; 202 notifyListeners(new AxisChangeEvent(this)); 203 } 204 205 210 public double getUpperMargin() { 211 return this.upperMargin; 212 } 213 214 221 public void setUpperMargin(double margin) { 222 this.upperMargin = margin; 223 notifyListeners(new AxisChangeEvent(this)); 224 } 225 226 231 public double getCategoryMargin() { 232 return this.categoryMargin; 233 } 234 235 243 public void setCategoryMargin(double margin) { 244 this.categoryMargin = margin; 245 notifyListeners(new AxisChangeEvent(this)); 246 } 247 248 253 public int getMaximumCategoryLabelLines() { 254 return this.maximumCategoryLabelLines; 255 } 256 257 263 public void setMaximumCategoryLabelLines(int lines) { 264 this.maximumCategoryLabelLines = lines; 265 notifyListeners(new AxisChangeEvent(this)); 266 } 267 268 273 public float getMaximumCategoryLabelWidthRatio() { 274 return this.maximumCategoryLabelWidthRatio; 275 } 276 277 283 public void setMaximumCategoryLabelWidthRatio(float ratio) { 284 this.maximumCategoryLabelWidthRatio = ratio; 285 notifyListeners(new AxisChangeEvent(this)); 286 } 287 288 294 public int getCategoryLabelPositionOffset() { 295 return this.categoryLabelPositionOffset; 296 } 297 298 304 public void setCategoryLabelPositionOffset(int offset) { 305 this.categoryLabelPositionOffset = offset; 306 notifyListeners(new AxisChangeEvent(this)); 307 } 308 309 315 public CategoryLabelPositions getCategoryLabelPositions() { 316 return this.categoryLabelPositions; 317 } 318 319 325 public void setCategoryLabelPositions(CategoryLabelPositions positions) { 326 if (positions == null) { 327 throw new IllegalArgumentException ("Null 'positions' argument."); 328 } 329 this.categoryLabelPositions = positions; 330 notifyListeners(new AxisChangeEvent(this)); 331 } 332 333 340 public void addCategoryLabelToolTip(Comparable category, String tooltip) { 341 if (category == null) { 342 throw new IllegalArgumentException ("Null 'category' argument."); 343 } 344 this.categoryLabelToolTips.put(category, tooltip); 345 notifyListeners(new AxisChangeEvent(this)); 346 } 347 348 354 public void removeCategoryLabelToolTip(Comparable category) { 355 if (category == null) { 356 throw new IllegalArgumentException ("Null 'category' argument."); 357 } 358 this.categoryLabelToolTips.remove(category); 359 notifyListeners(new AxisChangeEvent(this)); 360 } 361 362 366 public void clearCategoryLabelToolTips() { 367 this.categoryLabelToolTips.clear(); 368 notifyListeners(new AxisChangeEvent(this)); 369 } 370 371 382 public double getCategoryJava2DCoordinate(CategoryAnchor anchor, 383 int category, 384 int categoryCount, 385 Rectangle2D area, 386 RectangleEdge edge) { 387 388 double result = 0.0; 389 if (anchor == CategoryAnchor.START) { 390 result = getCategoryStart(category, categoryCount, area, edge); 391 } 392 else if (anchor == CategoryAnchor.MIDDLE) { 393 result = getCategoryMiddle(category, categoryCount, area, edge); 394 } 395 else if (anchor == CategoryAnchor.END) { 396 result = getCategoryEnd(category, categoryCount, area, edge); 397 } 398 return result; 399 400 } 401 402 412 public double getCategoryStart(int category, int categoryCount, 413 Rectangle2D area, 414 RectangleEdge edge) { 415 416 double result = 0.0; 417 if ((edge == RectangleEdge.TOP) || (edge == RectangleEdge.BOTTOM)) { 418 result = area.getX() + area.getWidth() * getLowerMargin(); 419 } 420 else if ((edge == RectangleEdge.LEFT) 421 || (edge == RectangleEdge.RIGHT)) { 422 result = area.getMinY() + area.getHeight() * getLowerMargin(); 423 } 424 425 double categorySize = calculateCategorySize(categoryCount, area, edge); 426 double categoryGapWidth = calculateCategoryGapSize( 427 categoryCount, area, edge 428 ); 429 430 result = result + category * (categorySize + categoryGapWidth); 431 432 return result; 433 } 434 435 445 public double getCategoryMiddle(int category, int categoryCount, 446 Rectangle2D area, RectangleEdge edge) { 447 448 return getCategoryStart(category, categoryCount, area, edge) 449 + calculateCategorySize(categoryCount, area, edge) / 2; 450 451 } 452 453 463 public double getCategoryEnd(int category, int categoryCount, 464 Rectangle2D area, RectangleEdge edge) { 465 466 return getCategoryStart(category, categoryCount, area, edge) 467 + calculateCategorySize(categoryCount, area, edge); 468 469 } 470 471 481 protected double calculateCategorySize(int categoryCount, Rectangle2D area, 482 RectangleEdge edge) { 483 484 double result = 0.0; 485 double available = 0.0; 486 487 if ((edge == RectangleEdge.TOP) || (edge == RectangleEdge.BOTTOM)) { 488 available = area.getWidth(); 489 } 490 else if ((edge == RectangleEdge.LEFT) 491 || (edge == RectangleEdge.RIGHT)) { 492 available = area.getHeight(); 493 } 494 if (categoryCount > 1) { 495 result = available * (1 - getLowerMargin() - getUpperMargin() 496 - getCategoryMargin()); 497 result = result / categoryCount; 498 } 499 else { 500 result = available * (1 - getLowerMargin() - getUpperMargin()); 501 } 502 return result; 503 504 } 505 506 516 protected double calculateCategoryGapSize(int categoryCount, 517 Rectangle2D area, 518 RectangleEdge edge) { 519 520 double result = 0.0; 521 double available = 0.0; 522 523 if ((edge == RectangleEdge.TOP) || (edge == RectangleEdge.BOTTOM)) { 524 available = area.getWidth(); 525 } 526 else if ((edge == RectangleEdge.LEFT) 527 || (edge == RectangleEdge.RIGHT)) { 528 available = area.getHeight(); 529 } 530 531 if (categoryCount > 1) { 532 result = available * getCategoryMargin() / (categoryCount - 1); 533 } 534 535 return result; 536 537 } 538 539 550 public AxisSpace reserveSpace(Graphics2D g2, Plot plot, 551 Rectangle2D plotArea, 552 RectangleEdge edge, AxisSpace space) { 553 554 if (space == null) { 556 space = new AxisSpace(); 557 } 558 559 if (!isVisible()) { 561 return space; 562 } 563 564 double tickLabelHeight = 0.0; 566 double tickLabelWidth = 0.0; 567 if (isTickLabelsVisible()) { 568 g2.setFont(getTickLabelFont()); 569 AxisState state = new AxisState(); 570 refreshTicks(g2, state, plotArea, edge); 571 if (edge == RectangleEdge.TOP) { 572 tickLabelHeight = state.getMax(); 573 } 574 else if (edge == RectangleEdge.BOTTOM) { 575 tickLabelHeight = state.getMax(); 576 } 577 else if (edge == RectangleEdge.LEFT) { 578 tickLabelWidth = state.getMax(); 579 } 580 else if (edge == RectangleEdge.RIGHT) { 581 tickLabelWidth = state.getMax(); 582 } 583 } 584 585 Rectangle2D labelEnclosure = getLabelEnclosure(g2, edge); 587 double labelHeight = 0.0; 588 double labelWidth = 0.0; 589 if (RectangleEdge.isTopOrBottom(edge)) { 590 labelHeight = labelEnclosure.getHeight(); 591 space.add( 592 labelHeight + tickLabelHeight 593 + this.categoryLabelPositionOffset, edge 594 ); 595 } 596 else if (RectangleEdge.isLeftOrRight(edge)) { 597 labelWidth = labelEnclosure.getWidth(); 598 space.add( 599 labelWidth + tickLabelWidth + this.categoryLabelPositionOffset, 600 edge 601 ); 602 } 603 return space; 604 605 } 606 607 610 public void configure() { 611 } 613 614 630 public AxisState draw(Graphics2D g2, 631 double cursor, 632 Rectangle2D plotArea, 633 Rectangle2D dataArea, 634 RectangleEdge edge, 635 PlotRenderingInfo plotState) { 636 637 if (!isVisible()) { 639 return new AxisState(cursor); 640 } 641 642 if (isAxisLineVisible()) { 643 drawAxisLine(g2, cursor, dataArea, edge); 644 } 645 646 AxisState state = new AxisState(cursor); 648 state = drawCategoryLabels(g2, dataArea, edge, state, plotState); 649 state = drawLabel(getLabel(), g2, plotArea, dataArea, edge, state); 650 651 return state; 652 653 } 654 655 668 protected AxisState drawCategoryLabels(Graphics2D g2, 669 Rectangle2D dataArea, 670 RectangleEdge edge, 671 AxisState state, 672 PlotRenderingInfo plotState) { 673 674 if (state == null) { 675 throw new IllegalArgumentException ("Null 'state' argument."); 676 } 677 678 if (isTickLabelsVisible()) { 679 g2.setFont(getTickLabelFont()); 680 g2.setPaint(getTickLabelPaint()); 681 List ticks = refreshTicks(g2, state, dataArea, edge); 682 state.setTicks(ticks); 683 684 int categoryIndex = 0; 685 Iterator iterator = ticks.iterator(); 686 while (iterator.hasNext()) { 687 688 CategoryTick tick = (CategoryTick) iterator.next(); 689 g2.setPaint(getTickLabelPaint()); 690 691 CategoryLabelPosition position 692 = this.categoryLabelPositions.getLabelPosition(edge); 693 double x0 = 0.0; 694 double x1 = 0.0; 695 double y0 = 0.0; 696 double y1 = 0.0; 697 if (edge == RectangleEdge.TOP) { 698 x0 = getCategoryStart( 699 categoryIndex, ticks.size(), dataArea, edge 700 ); 701 x1 = getCategoryEnd( 702 categoryIndex, ticks.size(), dataArea, edge 703 ); 704 y1 = state.getCursor() - this.categoryLabelPositionOffset; 705 y0 = y1 - state.getMax(); 706 } 707 else if (edge == RectangleEdge.BOTTOM) { 708 x0 = getCategoryStart( 709 categoryIndex, ticks.size(), dataArea, edge 710 ); 711 x1 = getCategoryEnd( 712 categoryIndex, ticks.size(), dataArea, edge 713 ); 714 y0 = state.getCursor() + this.categoryLabelPositionOffset; 715 y1 = y0 + state.getMax(); 716 } 717 else if (edge == RectangleEdge.LEFT) { 718 y0 = getCategoryStart( 719 categoryIndex, ticks.size(), dataArea, edge 720 ); 721 y1 = getCategoryEnd( 722 categoryIndex, ticks.size(), dataArea, edge 723 ); 724 x1 = state.getCursor() - this.categoryLabelPositionOffset; 725 x0 = x1 - state.getMax(); 726 } 727 else if (edge == RectangleEdge.RIGHT) { 728 y0 = getCategoryStart( 729 categoryIndex, ticks.size(), dataArea, edge 730 ); 731 y1 = getCategoryEnd( 732 categoryIndex, ticks.size(), dataArea, edge 733 ); 734 x0 = state.getCursor() + this.categoryLabelPositionOffset; 735 x1 = x0 - state.getMax(); 736 } 737 Rectangle2D area = new Rectangle2D.Double ( 738 x0, y0, (x1 - x0), (y1 - y0) 739 ); 740 Point2D anchorPoint = RectangleAnchor.coordinates( 741 area, position.getCategoryAnchor() 742 ); 743 TextBlock block = tick.getLabel(); 744 block.draw( 745 g2, 746 (float) anchorPoint.getX(), (float) anchorPoint.getY(), 747 position.getLabelAnchor(), 748 (float) anchorPoint.getX(), (float) anchorPoint.getY(), 749 position.getAngle() 750 ); 751 Shape bounds = block.calculateBounds( 752 g2, (float) anchorPoint.getX(), (float) anchorPoint.getY(), 753 position.getLabelAnchor(), 754 (float) anchorPoint.getX(), (float) anchorPoint.getY(), 755 position.getAngle() 756 ); 757 if (plotState != null) { 758 EntityCollection entities 759 = plotState.getOwner().getEntityCollection(); 760 if (entities != null) { 761 String tooltip 762 = (String ) this.categoryLabelToolTips.get( 763 tick.getCategory() 764 ); 765 entities.add( 766 new TickLabelEntity(bounds, tooltip, null) 767 ); 768 } 769 } 770 categoryIndex++; 771 } 772 773 if (edge.equals(RectangleEdge.TOP)) { 774 double h = state.getMax(); 775 state.cursorUp(h); 776 } 777 else if (edge.equals(RectangleEdge.BOTTOM)) { 778 double h = state.getMax(); 779 state.cursorDown(h); 780 } 781 else if (edge == RectangleEdge.LEFT) { 782 double w = state.getMax(); 783 state.cursorLeft(w); 784 } 785 else if (edge == RectangleEdge.RIGHT) { 786 double w = state.getMax(); 787 state.cursorRight(w); 788 } 789 } 790 return state; 791 } 792 793 803 public List refreshTicks(Graphics2D g2, 804 AxisState state, 805 Rectangle2D dataArea, 806 RectangleEdge edge) { 807 808 List ticks = new java.util.ArrayList (); 809 810 if (dataArea.getHeight() <= 0.0 || dataArea.getWidth() < 0.0) { 812 return ticks; 813 } 814 815 CategoryPlot plot = (CategoryPlot) getPlot(); 816 List categories = plot.getCategories(); 817 double max = 0.0; 818 819 if (categories != null) { 820 CategoryLabelPosition position 821 = this.categoryLabelPositions.getLabelPosition(edge); 822 float r = this.maximumCategoryLabelWidthRatio; 823 if (r <= 0.0) { 824 r = position.getWidthRatio(); 825 } 826 827 float l = 0.0f; 828 if (position.getWidthType() == CategoryLabelWidthType.CATEGORY) { 829 l = (float) calculateCategorySize( 830 categories.size(), dataArea, edge 831 ); 832 } 833 else { 834 if (RectangleEdge.isLeftOrRight(edge)) { 835 l = (float) dataArea.getWidth(); 836 } 837 else { 838 l = (float) dataArea.getHeight(); 839 } 840 } 841 int categoryIndex = 0; 842 Iterator iterator = categories.iterator(); 843 while (iterator.hasNext()) { 844 Comparable category = (Comparable ) iterator.next(); 845 TextBlock label = createLabel(category, l * r, edge, g2); 846 if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) { 847 max = Math.max( 848 max, calculateTextBlockHeight(label, position, g2) 849 ); 850 } 851 else if (edge == RectangleEdge.LEFT 852 || edge == RectangleEdge.RIGHT) { 853 max = Math.max( 854 max, calculateTextBlockWidth(label, position, g2) 855 ); 856 } 857 Tick tick = new CategoryTick( 858 category, label, 859 position.getLabelAnchor(), position.getRotationAnchor(), 860 position.getAngle() 861 ); 862 ticks.add(tick); 863 categoryIndex = categoryIndex + 1; 864 } 865 } 866 state.setMax(max); 867 return ticks; 868 869 } 870 871 881 protected TextBlock createLabel(Comparable category, float width, 882 RectangleEdge edge, Graphics2D g2) { 883 TextBlock label = TextUtilities.createTextBlock( 884 category.toString(), getTickLabelFont(), getTickLabelPaint(), 885 width, this.maximumCategoryLabelLines, new G2TextMeasurer(g2) 886 ); 887 return label; 888 } 889 890 899 protected double calculateTextBlockWidth(TextBlock block, 900 CategoryLabelPosition position, 901 Graphics2D g2) { 902 903 RectangleInsets insets = getTickLabelInsets(); 904 Size2D size = block.calculateDimensions(g2); 905 Rectangle2D box = new Rectangle2D.Double ( 906 0.0, 0.0, size.getWidth(), size.getHeight() 907 ); 908 Shape rotatedBox = ShapeUtilities.rotateShape( 909 box, position.getAngle(), 0.0f, 0.0f 910 ); 911 double w = rotatedBox.getBounds2D().getWidth() 912 + insets.getTop() + insets.getBottom(); 913 return w; 914 915 } 916 917 926 protected double calculateTextBlockHeight(TextBlock block, 927 CategoryLabelPosition position, 928 Graphics2D g2) { 929 930 RectangleInsets insets = getTickLabelInsets(); 931 Size2D size = block.calculateDimensions(g2); 932 Rectangle2D box = new Rectangle2D.Double ( 933 0.0, 0.0, size.getWidth(), size.getHeight() 934 ); 935 Shape rotatedBox = ShapeUtilities.rotateShape( 936 box, position.getAngle(), 0.0f, 0.0f 937 ); 938 double h = rotatedBox.getBounds2D().getHeight() 939 + insets.getTop() + insets.getBottom(); 940 return h; 941 942 } 943 944 952 public Object clone() throws CloneNotSupportedException { 953 954 Object clone = super.clone(); 955 return clone; 956 957 } 958 959 966 public boolean equals(Object obj) { 967 if (obj == this) { 968 return true; 969 } 970 if (!(obj instanceof CategoryAxis)) { 971 return false; 972 } 973 if (!super.equals(obj)) { 974 return false; 975 } 976 CategoryAxis that = (CategoryAxis) obj; 977 if (that.lowerMargin != this.lowerMargin) { 978 return false; 979 } 980 if (that.upperMargin != this.upperMargin) { 981 return false; 982 } 983 if (that.categoryMargin != this.categoryMargin) { 984 return false; 985 } 986 if (that.maximumCategoryLabelWidthRatio 987 != this.maximumCategoryLabelWidthRatio) { 988 return false; 989 } 990 if (that.categoryLabelPositionOffset 991 != this.categoryLabelPositionOffset) { 992 return false; 993 } 994 if (!ObjectUtilities.equal( 995 that.categoryLabelPositions, this.categoryLabelPositions 996 )) { 997 return false; 998 } 999 if (!ObjectUtilities.equal( 1000 that.categoryLabelToolTips, this.categoryLabelToolTips 1001 )) { 1002 return false; 1003 } 1004 return true; 1005 } 1006 1007 1012 public int hashCode() { 1013 if (getLabel() != null) { 1014 return getLabel().hashCode(); 1015 } 1016 else { 1017 return 0; 1018 } 1019 } 1020 1021 1028 private void writeObject(ObjectOutputStream stream) throws IOException { 1029 stream.defaultWriteObject(); 1030 } 1031 1032 1040 private void readObject(ObjectInputStream stream) 1041 throws IOException , ClassNotFoundException { 1042 stream.defaultReadObject(); 1043 } 1044 1045} 1046 | Popular Tags |