| 1 98 99 package org.jfree.chart.axis; 100 101 import java.awt.Font ; 102 import java.awt.FontMetrics ; 103 import java.awt.Graphics2D ; 104 import java.awt.Polygon ; 105 import java.awt.Shape ; 106 import java.awt.font.LineMetrics ; 107 import java.awt.geom.AffineTransform ; 108 import java.awt.geom.Line2D ; 109 import java.awt.geom.Rectangle2D ; 110 import java.io.IOException ; 111 import java.io.ObjectInputStream ; 112 import java.io.ObjectOutputStream ; 113 import java.io.Serializable ; 114 import java.util.Iterator ; 115 import java.util.List ; 116 117 import org.jfree.chart.event.AxisChangeEvent; 118 import org.jfree.chart.plot.Plot; 119 import org.jfree.data.Range; 120 import org.jfree.io.SerialUtilities; 121 import org.jfree.text.TextUtilities; 122 import org.jfree.ui.RectangleEdge; 123 import org.jfree.ui.RectangleInsets; 124 import org.jfree.util.ObjectUtilities; 125 import org.jfree.util.PublicCloneable; 126 127 132 public abstract class ValueAxis extends Axis 133 implements Cloneable , PublicCloneable, 134 Serializable { 135 136 137 private static final long serialVersionUID = 3698345477322391456L; 138 139 140 public static final Range DEFAULT_RANGE = new Range(0.0, 1.0); 141 142 143 public static final boolean DEFAULT_AUTO_RANGE = true; 144 145 146 public static final boolean DEFAULT_INVERTED = false; 147 148 149 public static final double DEFAULT_AUTO_RANGE_MINIMUM_SIZE = 0.00000001; 150 151 152 public static final double DEFAULT_LOWER_MARGIN = 0.05; 153 154 155 public static final double DEFAULT_UPPER_MARGIN = 0.05; 156 157 158 public static final double DEFAULT_LOWER_BOUND = 0.0; 159 160 161 public static final double DEFAULT_UPPER_BOUND = 1.0; 162 163 164 public static final boolean DEFAULT_AUTO_TICK_UNIT_SELECTION = true; 165 166 167 public static final int MAXIMUM_TICK_COUNT = 500; 168 169 173 private boolean positiveArrowVisible; 174 175 179 private boolean negativeArrowVisible; 180 181 182 private transient Shape upArrow; 183 184 185 private transient Shape downArrow; 186 187 188 private transient Shape leftArrow; 189 190 191 private transient Shape rightArrow; 192 193 194 private boolean inverted; 195 196 197 private Range range; 198 199 203 private boolean autoRange; 204 205 206 private double autoRangeMinimumSize; 207 208 213 private double upperMargin; 214 215 220 private double lowerMargin; 221 222 227 private double fixedAutoRange; 228 229 233 private boolean autoTickUnitSelection; 234 235 236 private TickUnitSource standardTickUnits; 237 238 239 private int autoTickIndex; 240 241 242 private boolean verticalTickLabels; 243 244 251 protected ValueAxis(String label, TickUnitSource standardTickUnits) { 252 253 super(label); 254 255 this.positiveArrowVisible = false; 256 this.negativeArrowVisible = false; 257 258 this.range = DEFAULT_RANGE; 259 this.autoRange = DEFAULT_AUTO_RANGE; 260 261 this.inverted = DEFAULT_INVERTED; 262 this.autoRangeMinimumSize = DEFAULT_AUTO_RANGE_MINIMUM_SIZE; 263 264 this.lowerMargin = DEFAULT_LOWER_MARGIN; 265 this.upperMargin = DEFAULT_UPPER_MARGIN; 266 267 this.fixedAutoRange = 0.0; 268 269 this.autoTickUnitSelection = DEFAULT_AUTO_TICK_UNIT_SELECTION; 270 this.standardTickUnits = standardTickUnits; 271 272 Polygon p1 = new Polygon (); 273 p1.addPoint(0, 0); 274 p1.addPoint(-2, 2); 275 p1.addPoint(2, 2); 276 277 this.upArrow = p1; 278 279 Polygon p2 = new Polygon (); 280 p2.addPoint(0, 0); 281 p2.addPoint(-2, -2); 282 p2.addPoint(2, -2); 283 284 this.downArrow = p2; 285 286 Polygon p3 = new Polygon (); 287 p3.addPoint(0, 0); 288 p3.addPoint(-2, -2); 289 p3.addPoint(-2, 2); 290 291 this.rightArrow = p3; 292 293 Polygon p4 = new Polygon (); 294 p4.addPoint(0, 0); 295 p4.addPoint(2, -2); 296 p4.addPoint(2, 2); 297 298 this.leftArrow = p4; 299 300 this.verticalTickLabels = false; 301 302 } 303 304 310 public boolean isVerticalTickLabels() { 311 return this.verticalTickLabels; 312 } 313 314 322 public void setVerticalTickLabels(boolean flag) { 323 if (this.verticalTickLabels != flag) { 324 this.verticalTickLabels = flag; 325 notifyListeners(new AxisChangeEvent(this)); 326 } 327 } 328 329 335 public boolean isPositiveArrowVisible() { 336 return this.positiveArrowVisible; 337 } 338 339 346 public void setPositiveArrowVisible(boolean visible) { 347 this.positiveArrowVisible = visible; 348 notifyListeners(new AxisChangeEvent(this)); 349 } 350 351 357 public boolean isNegativeArrowVisible() { 358 return this.negativeArrowVisible; 359 } 360 361 368 public void setNegativeArrowVisible(boolean visible) { 369 this.negativeArrowVisible = visible; 370 notifyListeners(new AxisChangeEvent(this)); 371 } 372 373 379 public Shape getUpArrow() { 380 return this.upArrow; 381 } 382 383 390 public void setUpArrow(Shape arrow) { 391 if (arrow == null) { 392 throw new IllegalArgumentException ("Null 'arrow' argument."); 393 } 394 this.upArrow = arrow; 395 notifyListeners(new AxisChangeEvent(this)); 396 } 397 398 404 public Shape getDownArrow() { 405 return this.downArrow; 406 } 407 408 415 public void setDownArrow(Shape arrow) { 416 if (arrow == null) { 417 throw new IllegalArgumentException ("Null 'arrow' argument."); 418 } 419 this.downArrow = arrow; 420 notifyListeners(new AxisChangeEvent(this)); 421 } 422 423 429 public Shape getLeftArrow() { 430 return this.leftArrow; 431 } 432 433 440 public void setLeftArrow(Shape arrow) { 441 if (arrow == null) { 442 throw new IllegalArgumentException ("Null 'arrow' argument."); 443 } 444 this.leftArrow = arrow; 445 notifyListeners(new AxisChangeEvent(this)); 446 } 447 448 454 public Shape getRightArrow() { 455 return this.rightArrow; 456 } 457 458 465 public void setRightArrow(Shape arrow) { 466 if (arrow == null) { 467 throw new IllegalArgumentException ("Null 'arrow' argument."); 468 } 469 this.rightArrow = arrow; 470 notifyListeners(new AxisChangeEvent(this)); 471 } 472 473 481 protected void drawAxisLine(Graphics2D g2, double cursor, 482 Rectangle2D dataArea, RectangleEdge edge) { 483 Line2D axisLine = null; 484 if (edge == RectangleEdge.TOP) { 485 axisLine = new Line2D.Double ( 486 dataArea.getX(), cursor, dataArea.getMaxX(), cursor 487 ); 488 } 489 else if (edge == RectangleEdge.BOTTOM) { 490 axisLine = new Line2D.Double ( 491 dataArea.getX(), cursor, dataArea.getMaxX(), cursor 492 ); 493 } 494 else if (edge == RectangleEdge.LEFT) { 495 axisLine = new Line2D.Double ( 496 cursor, dataArea.getY(), cursor, dataArea.getMaxY() 497 ); 498 } 499 else if (edge == RectangleEdge.RIGHT) { 500 axisLine = new Line2D.Double ( 501 cursor, dataArea.getY(), cursor, dataArea.getMaxY() 502 ); 503 } 504 g2.setPaint(getAxisLinePaint()); 505 g2.setStroke(getAxisLineStroke()); 506 g2.draw(axisLine); 507 508 boolean drawUpOrRight = false; 509 boolean drawDownOrLeft = false; 510 if (this.positiveArrowVisible) { 511 if (this.inverted) { 512 drawDownOrLeft = true; 513 } 514 else { 515 drawUpOrRight = true; 516 } 517 } 518 if (this.negativeArrowVisible) { 519 if (this.inverted) { 520 drawUpOrRight = true; 521 } 522 else { 523 drawDownOrLeft = true; 524 } 525 } 526 if (drawUpOrRight) { 527 double x = 0.0; 528 double y = 0.0; 529 Shape arrow = null; 530 if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) { 531 x = dataArea.getMaxX(); 532 y = cursor; 533 arrow = this.rightArrow; 534 } 535 else if (edge == RectangleEdge.LEFT 536 || edge == RectangleEdge.RIGHT) { 537 x = cursor; 538 y = dataArea.getMinY(); 539 arrow = this.upArrow; 540 } 541 542 AffineTransform transformer = new AffineTransform (); 544 transformer.setToTranslation(x, y); 545 Shape shape = transformer.createTransformedShape(arrow); 546 g2.fill(shape); 547 g2.draw(shape); 548 } 549 550 if (drawDownOrLeft) { 551 double x = 0.0; 552 double y = 0.0; 553 Shape arrow = null; 554 if (edge == RectangleEdge.TOP || edge == RectangleEdge.BOTTOM) { 555 x = dataArea.getMinX(); 556 y = cursor; 557 arrow = this.leftArrow; 558 } 559 else if (edge == RectangleEdge.LEFT 560 || edge == RectangleEdge.RIGHT) { 561 x = cursor; 562 y = dataArea.getMaxY(); 563 arrow = this.downArrow; 564 } 565 566 AffineTransform transformer = new AffineTransform (); 568 transformer.setToTranslation(x, y); 569 Shape shape = transformer.createTransformedShape(arrow); 570 g2.fill(shape); 571 g2.draw(shape); 572 } 573 574 } 575 576 586 protected float[] calculateAnchorPoint(ValueTick tick, 587 double cursor, 588 Rectangle2D dataArea, 589 RectangleEdge edge) { 590 591 RectangleInsets insets = getTickLabelInsets(); 592 float[] result = new float[2]; 593 if (edge == RectangleEdge.TOP) { 594 result[0] = (float) valueToJava2D(tick.getValue(), dataArea, edge); 595 result[1] = (float) (cursor - insets.getBottom() - 2.0); 596 } 597 else if (edge == RectangleEdge.BOTTOM) { 598 result[0] = (float) valueToJava2D(tick.getValue(), dataArea, edge); 599 result[1] = (float) (cursor + insets.getTop() + 2.0); 600 } 601 else if (edge == RectangleEdge.LEFT) { 602 result[0] = (float) (cursor - insets.getLeft() - 2.0); 603 result[1] = (float) valueToJava2D(tick.getValue(), dataArea, edge); 604 } 605 else if (edge == RectangleEdge.RIGHT) { 606 result[0] = (float) (cursor + insets.getRight() + 2.0); 607 result[1] = (float) valueToJava2D(tick.getValue(), dataArea, edge); 608 } 609 return result; 610 } 611 612 623 protected AxisState drawTickMarksAndLabels(Graphics2D g2, 624 double cursor, 625 Rectangle2D plotArea, 626 Rectangle2D dataArea, 627 RectangleEdge edge) { 628 629 AxisState state = new AxisState(cursor); 630 631 if (isAxisLineVisible()) { 632 drawAxisLine(g2, cursor, dataArea, edge); 633 } 634 635 double ol = getTickMarkOutsideLength(); 636 double il = getTickMarkInsideLength(); 637 638 List ticks = refreshTicks(g2, state, dataArea, edge); 639 state.setTicks(ticks); 640 g2.setFont(getTickLabelFont()); 641 Iterator iterator = ticks.iterator(); 642 while (iterator.hasNext()) { 643 ValueTick tick = (ValueTick) iterator.next(); 644 if (isTickLabelsVisible()) { 645 g2.setPaint(getTickLabelPaint()); 646 float[] anchorPoint = calculateAnchorPoint( 647 tick, cursor, dataArea, edge 648 ); 649 TextUtilities.drawRotatedString( 650 tick.getText(), g2, 651 anchorPoint[0], anchorPoint[1], 652 tick.getTextAnchor(), 653 tick.getAngle(), 654 tick.getRotationAnchor() 655 ); 656 } 657 658 if (isTickMarksVisible()) { 659 float xx = (float) valueToJava2D( 660 tick.getValue(), dataArea, edge 661 ); 662 Line2D mark = null; 663 g2.setStroke(getTickMarkStroke()); 664 g2.setPaint(getTickMarkPaint()); 665 if (edge == RectangleEdge.LEFT) { 666 mark = new Line2D.Double (cursor - ol, xx, cursor + il, xx); 667 } 668 else if (edge == RectangleEdge.RIGHT) { 669 mark = new Line2D.Double (cursor + ol, xx, cursor - il, xx); 670 } 671 else if (edge == RectangleEdge.TOP) { 672 mark = new Line2D.Double (xx, cursor - ol, xx, cursor + il); 673 } 674 else if (edge == RectangleEdge.BOTTOM) { 675 mark = new Line2D.Double (xx, cursor + ol, xx, cursor - il); 676 } 677 g2.draw(mark); 678 } 679 } 680 681 double used = 0.0; 684 if (isTickLabelsVisible()) { 685 if (edge == RectangleEdge.LEFT) { 686 used += findMaximumTickLabelWidth( 687 ticks, g2, plotArea, isVerticalTickLabels() 688 ); 689 state.cursorLeft(used); 690 } 691 else if (edge == RectangleEdge.RIGHT) { 692 used = findMaximumTickLabelWidth( 693 ticks, g2, plotArea, isVerticalTickLabels() 694 ); 695 state.cursorRight(used); 696 } 697 else if (edge == RectangleEdge.TOP) { 698 used = findMaximumTickLabelHeight( 699 ticks, g2, plotArea, isVerticalTickLabels() 700 ); 701 state.cursorUp(used); 702 } 703 else if (edge == RectangleEdge.BOTTOM) { 704 used = findMaximumTickLabelHeight( 705 ticks, g2, plotArea, isVerticalTickLabels() 706 ); 707 state.cursorDown(used); 708 } 709 } 710 711 return state; 712 } 713 714 726 public AxisSpace reserveSpace(Graphics2D g2, Plot plot, 727 Rectangle2D plotArea, 728 RectangleEdge edge, AxisSpace space) { 729 730 if (space == null) { 732 space = new AxisSpace(); 733 } 734 735 if (!isVisible()) { 737 return space; 738 } 739 740 double dimension = getFixedDimension(); 742 if (dimension > 0.0) { 743 space.ensureAtLeast(dimension, edge); 744 } 745 746 double tickLabelHeight = 0.0; 748 double tickLabelWidth = 0.0; 749 if (isTickLabelsVisible()) { 750 g2.setFont(getTickLabelFont()); 751 List ticks = refreshTicks(g2, new AxisState(), plotArea, edge); 752 if (RectangleEdge.isTopOrBottom(edge)) { 753 tickLabelHeight = findMaximumTickLabelHeight( 754 ticks, g2, plotArea, isVerticalTickLabels() 755 ); 756 } 757 else if (RectangleEdge.isLeftOrRight(edge)) { 758 tickLabelWidth = findMaximumTickLabelWidth( 759 ticks, g2, plotArea, isVerticalTickLabels() 760 ); 761 } 762 } 763 764 Rectangle2D labelEnclosure = getLabelEnclosure(g2, edge); 766 double labelHeight = 0.0; 767 double labelWidth = 0.0; 768 if (RectangleEdge.isTopOrBottom(edge)) { 769 labelHeight = labelEnclosure.getHeight(); 770 space.add(labelHeight + tickLabelHeight, edge); 771 } 772 else if (RectangleEdge.isLeftOrRight(edge)) { 773 labelWidth = labelEnclosure.getWidth(); 774 space.add(labelWidth + tickLabelWidth, edge); 775 } 776 777 return space; 778 779 } 780 781 792 protected double findMaximumTickLabelHeight(List ticks, 793 Graphics2D g2, 794 Rectangle2D drawArea, 795 boolean vertical) { 796 797 RectangleInsets insets = getTickLabelInsets(); 798 Font font = getTickLabelFont(); 799 double maxHeight = 0.0; 800 if (vertical) { 801 FontMetrics fm = g2.getFontMetrics(font); 802 Iterator iterator = ticks.iterator(); 803 while (iterator.hasNext()) { 804 Tick tick = (Tick) iterator.next(); 805 Rectangle2D labelBounds = TextUtilities.getTextBounds( 806 tick.getText(), g2, fm 807 ); 808 if (labelBounds.getWidth() + insets.getTop() 809 + insets.getBottom() > maxHeight) { 810 maxHeight = labelBounds.getWidth() 811 + insets.getTop() + insets.getBottom(); 812 } 813 } 814 } 815 else { 816 LineMetrics metrics = font.getLineMetrics( 817 "ABCxyz", g2.getFontRenderContext() 818 ); 819 maxHeight = metrics.getHeight() 820 + insets.getTop() + insets.getBottom(); 821 } 822 return maxHeight; 823 824 } 825 826 837 protected double findMaximumTickLabelWidth(List ticks, 838  
|