| 1 54 55 package org.jfree.chart.axis; 56 57 import java.awt.BasicStroke ; 58 import java.awt.Color ; 59 import java.awt.FontMetrics ; 60 import java.awt.Graphics2D ; 61 import java.awt.Paint ; 62 import java.awt.Stroke ; 63 import java.awt.geom.Line2D ; 64 import java.awt.geom.Rectangle2D ; 65 import java.io.IOException ; 66 import java.io.ObjectInputStream ; 67 import java.io.ObjectOutputStream ; 68 import java.io.Serializable ; 69 import java.lang.reflect.Constructor ; 70 import java.text.DateFormat ; 71 import java.text.SimpleDateFormat ; 72 import java.util.ArrayList ; 73 import java.util.Arrays ; 74 import java.util.Calendar ; 75 import java.util.Collections ; 76 import java.util.Date ; 77 import java.util.List ; 78 import java.util.TimeZone ; 79 80 import org.jfree.chart.event.AxisChangeEvent; 81 import org.jfree.chart.plot.Plot; 82 import org.jfree.chart.plot.PlotRenderingInfo; 83 import org.jfree.chart.plot.ValueAxisPlot; 84 import org.jfree.data.Range; 85 import org.jfree.data.time.Day; 86 import org.jfree.data.time.Month; 87 import org.jfree.data.time.RegularTimePeriod; 88 import org.jfree.data.time.Year; 89 import org.jfree.io.SerialUtilities; 90 import org.jfree.text.TextUtilities; 91 import org.jfree.ui.RectangleEdge; 92 import org.jfree.ui.TextAnchor; 93 import org.jfree.util.PublicCloneable; 94 95 101 public class PeriodAxis extends ValueAxis 102 implements Cloneable , PublicCloneable, Serializable { 103 104 105 private static final long serialVersionUID = 8353295532075872069L; 106 107 108 private RegularTimePeriod first; 109 110 111 private RegularTimePeriod last; 112 113 117 private TimeZone timeZone; 118 119 122 private Calendar calendar; 123 124 128 private Class autoRangeTimePeriodClass; 129 130 134 private Class majorTickTimePeriodClass; 135 136 140 private boolean minorTickMarksVisible; 141 142 146 private Class minorTickTimePeriodClass; 147 148 149 private float minorTickMarkInsideLength = 0.0f; 150 151 152 private float minorTickMarkOutsideLength = 2.0f; 153 154 155 private transient Stroke minorTickMarkStroke = new BasicStroke (0.5f); 156 157 158 private transient Paint minorTickMarkPaint = Color.black; 159 160 161 private PeriodAxisLabelInfo[] labelInfo; 162 163 168 public PeriodAxis(String label) { 169 this(label, new Day(), new Day()); 170 } 171 172 181 public PeriodAxis(String label, 182 RegularTimePeriod first, RegularTimePeriod last) { 183 this(label, first, last, TimeZone.getDefault()); 184 } 185 186 196 public PeriodAxis(String label, 197 RegularTimePeriod first, RegularTimePeriod last, 198 TimeZone timeZone) { 199 200 super(label, null); 201 this.first = first; 202 this.last = last; 203 this.timeZone = timeZone; 204 this.calendar = Calendar.getInstance(timeZone); 205 this.autoRangeTimePeriodClass = first.getClass(); 206 this.majorTickTimePeriodClass = first.getClass(); 207 this.minorTickMarksVisible = false; 208 this.minorTickTimePeriodClass = RegularTimePeriod.downsize( 209 this.majorTickTimePeriodClass); 210 setAutoRange(true); 211 this.labelInfo = new PeriodAxisLabelInfo[2]; 212 this.labelInfo[0] = new PeriodAxisLabelInfo(Month.class, 213 new SimpleDateFormat ("MMM")); 214 this.labelInfo[1] = new PeriodAxisLabelInfo(Year.class, 215 new SimpleDateFormat ("yyyy")); 216 217 } 218 219 224 public RegularTimePeriod getFirst() { 225 return this.first; 226 } 227 228 234 public void setFirst(RegularTimePeriod first) { 235 if (first == null) { 236 throw new IllegalArgumentException ("Null 'first' argument."); 237 } 238 this.first = first; 239 notifyListeners(new AxisChangeEvent(this)); 240 } 241 242 247 public RegularTimePeriod getLast() { 248 return this.last; 249 } 250 251 257 public void setLast(RegularTimePeriod last) { 258 if (last == null) { 259 throw new IllegalArgumentException ("Null 'last' argument."); 260 } 261 this.last = last; 262 notifyListeners(new AxisChangeEvent(this)); 263 } 264 265 271 public TimeZone getTimeZone() { 272 return this.timeZone; 273 } 274 275 281 public void setTimeZone(TimeZone zone) { 282 if (zone == null) { 283 throw new IllegalArgumentException ("Null 'zone' argument."); 284 } 285 this.timeZone = zone; 286 this.calendar = Calendar.getInstance(zone); 287 notifyListeners(new AxisChangeEvent(this)); 288 } 289 290 296 public Class getAutoRangeTimePeriodClass() { 297 return this.autoRangeTimePeriodClass; 298 } 299 300 307 public void setAutoRangeTimePeriodClass(Class c) { 308 if (c == null) { 309 throw new IllegalArgumentException ("Null 'c' argument."); 310 } 311 this.autoRangeTimePeriodClass = c; 312 notifyListeners(new AxisChangeEvent(this)); 313 } 314 315 320 public Class getMajorTickTimePeriodClass() { 321 return this.majorTickTimePeriodClass; 322 } 323 324 331 public void setMajorTickTimePeriodClass(Class c) { 332 if (c == null) { 333 throw new IllegalArgumentException ("Null 'c' argument."); 334 } 335 this.majorTickTimePeriodClass = c; 336 notifyListeners(new AxisChangeEvent(this)); 337 } 338 339 345 public boolean isMinorTickMarksVisible() { 346 return this.minorTickMarksVisible; 347 } 348 349 356 public void setMinorTickMarksVisible(boolean visible) { 357 this.minorTickMarksVisible = visible; 358 notifyListeners(new AxisChangeEvent(this)); 359 } 360 361 366 public Class getMinorTickTimePeriodClass() { 367 return this.minorTickTimePeriodClass; 368 } 369 370 377 public void setMinorTickTimePeriodClass(Class c) { 378 if (c == null) { 379 throw new IllegalArgumentException ("Null 'c' argument."); 380 } 381 this.minorTickTimePeriodClass = c; 382 notifyListeners(new AxisChangeEvent(this)); 383 } 384 385 391 public Stroke getMinorTickMarkStroke() { 392 return this.minorTickMarkStroke; 393 } 394 395 402 public void setMinorTickMarkStroke(Stroke stroke) { 403 if (stroke == null) { 404 throw new IllegalArgumentException ("Null 'stroke' argument."); 405 } 406 this.minorTickMarkStroke = stroke; 407 notifyListeners(new AxisChangeEvent(this)); 408 } 409 410 416 public Paint getMinorTickMarkPaint() { 417 return this.minorTickMarkPaint; 418 } 419 420 427 public void setMinorTickMarkPaint(Paint paint) { 428 if (paint == null) { 429 throw new IllegalArgumentException ("Null 'paint' argument."); 430 } 431 this.minorTickMarkPaint = paint; 432 notifyListeners(new AxisChangeEvent(this)); 433 } 434 435 440 public float getMinorTickMarkInsideLength() { 441 return this.minorTickMarkInsideLength; 442 } 443 444 450 public void setMinorTickMarkInsideLength(float length) { 451 this.minorTickMarkInsideLength = length; 452 notifyListeners(new AxisChangeEvent(this)); 453 } 454 455 460 public float getMinorTickMarkOutsideLength() { 461 return this.minorTickMarkOutsideLength; 462 } 463 464 470 public void setMinorTickMarkOutsideLength(float length) { 471 this.minorTickMarkOutsideLength = length; 472 notifyListeners(new AxisChangeEvent(this)); 473 } 474 475 480 public PeriodAxisLabelInfo[] getLabelInfo() { 481 return this.labelInfo; 482 } 483 484 489 public void setLabelInfo(PeriodAxisLabelInfo[] info) { 490 this.labelInfo = info; 491 } 492 493 498 public Range getRange() { 499 return new Range(this.first.getFirstMillisecond(this.calendar), 501 this.last.getLastMillisecond(this.calendar)); 502 } 503 504 515 public void setRange(Range range, boolean turnOffAutoRange, 516 boolean notify) { 517 super.setRange(range, turnOffAutoRange, false); 518 long upper = Math.round(range.getUpperBound()); 519 long lower = Math.round(range.getLowerBound()); 520 this.first = createInstance(this.autoRangeTimePeriodClass, 521 new Date (lower), this.timeZone); 522 this.last = createInstance(this.autoRangeTimePeriodClass, 523 new Date (upper), this.timeZone); 524 } 525 526 530 public void configure() { 531 if (this.isAutoRange()) { 532 autoAdjustRange(); 533 } 534 } 535 536 549 public AxisSpace reserveSpace(Graphics2D g2, Plot plot, 550 Rectangle2D plotArea, RectangleEdge edge, 551 AxisSpace space) { 552 if (space == null) { 554 space = new AxisSpace(); 555 } 556 557 if (!isVisible()) { 559 return space; 560 } 561 562 double dimension = getFixedDimension(); 564 if (dimension > 0.0) { 565 space.ensureAtLeast(dimension, edge); 566 } 567 568 Rectangle2D labelEnclosure = getLabelEnclosure(g2, edge); 570 double labelHeight = 0.0; 571 double labelWidth = 0.0; 572 double tickLabelBandsDimension = 0.0; 573 574 for (int i = 0; i < this.labelInfo.length; i++) { 575 PeriodAxisLabelInfo info = this.labelInfo[i]; 576 FontMetrics fm = g2.getFontMetrics(info.getLabelFont()); 577 tickLabelBandsDimension 578 += info.getPadding().extendHeight(fm.getHeight()); 579 } 580 581 if (RectangleEdge.isTopOrBottom(edge)) { 582 labelHeight = labelEnclosure.getHeight(); 583 space.add(labelHeight + tickLabelBandsDimension, edge); 584 } 585 else if (RectangleEdge.isLeftOrRight(edge)) { 586 labelWidth = labelEnclosure.getWidth(); 587 space.add(labelWidth + tickLabelBandsDimension, edge); 588 } 589 590 double tickMarkSpace = 0.0; 592 if (isTickMarksVisible()) { 593 tickMarkSpace = getTickMarkOutsideLength(); 594 } 595 if (this.minorTickMarksVisible) { 596 tickMarkSpace = Math.max(tickMarkSpace, 597 this.minorTickMarkOutsideLength); 598 } 599 space.add(tickMarkSpace, edge); 600 return space; 601 } 602 603 617 public AxisState draw(Graphics2D g2, 618 double cursor, 619 Rectangle2D plotArea, 620 Rectangle2D dataArea, 621 RectangleEdge edge, 622 PlotRenderingInfo plotState) { 623 624 AxisState axisState = new AxisState(cursor); 625 if (isAxisLineVisible()) { 626 drawAxisLine(g2, cursor, dataArea, edge); 627 } 628 drawTickMarks(g2, axisState, dataArea, edge); 629 for (int band = 0; band < this.labelInfo.length; band++) { 630 axisState = drawTickLabels(band, g2, axisState, dataArea, edge); 631 } 632 633 axisState = drawLabel(getLabel(), g2, plotArea, dataArea, edge, 636 axisState); 637 return axisState; 638 639 } 640 641 649 protected void drawTickMarks(Graphics2D g2, AxisState state, 650 Rectangle2D dataArea, 651 RectangleEdge edge) { 652 if (RectangleEdge.isTopOrBottom(edge)) { 653 drawTickMarksHorizontal(g2, state, dataArea, edge); 654 } 655 else if (RectangleEdge.isLeftOrRight(edge)) { 656 drawTickMarksVertical(g2, state, dataArea, edge); 657 } 658 } 659 660 669 protected void drawTickMarksHorizontal(Graphics2D g2, AxisState state, 670 Rectangle2D dataArea, 671 RectangleEdge edge) { 672 List ticks = new ArrayList (); 673 double x0 = dataArea.getX(); 674 double y0 = state.getCursor(); 675 double insideLength = getTickMarkInsideLength(); 676 double outsideLength = getTickMarkOutsideLength(); 677 RegularTimePeriod t = RegularTimePeriod.createInstance( 678 this.majorTickTimePeriodClass, this.first.getStart(), 679 getTimeZone()); 680 long t0 = t.getFirstMillisecond(this.calendar); 681 Line2D inside = null; 682 Line2D outside = null; 683 long firstOnAxis = getFirst().getFirstMillisecond(this.calendar); 684 long lastOnAxis = getLast().getLastMillisecond(this.calendar); 685 while (t0 <= lastOnAxis) { 686 ticks.add(new NumberTick(new Double (t0), "", TextAnchor.CENTER, 687 TextAnchor.CENTER, 0.0)); 688 x0 = valueToJava2D(t0, dataArea, edge); 689 if (edge == RectangleEdge.TOP) { 690 inside = new Line2D.Double (x0, y0, x0, y0 + insideLength); 691 outside = new Line2D.Double (x0, y0, x0, y0 - outsideLength); 692 } 693 else if (edge == RectangleEdge.BOTTOM) { 694 inside = new Line2D.Double (x0, y0, x0, y0 - insideLength); 695 outside = new Line2D.Double (x0, y0, x0, y0 + outsideLength); 696 } 697 if (t0 > firstOnAxis) { 698 g2.setPaint(getTickMarkPaint()); 699 g2.setStroke(getTickMarkStroke()); 700 g2.draw(inside); 701 g2.draw(outside); 702 } 703 if (this.minorTickMarksVisible) { 705 RegularTimePeriod tminor = RegularTimePeriod.createInstance( 706 this.minorTickTimePeriodClass, new Date (t0), 707 getTimeZone()); 708 long tt0 = tminor.getFirstMillisecond(this.calendar); 709 while (tt0 < t.getLastMillisecond(this.calendar) 710 && tt0 < lastOnAxis) { 711 double xx0 = valueToJava2D(tt0, dataArea, edge); 712 if (edge == RectangleEdge.TOP) { 713 inside = new Line2D.Double (xx0, y0, xx0, 714 y0 + this.minorTickMarkInsideLength); 715 outside = new Line2D.Double (xx0, y0, xx0, 716 y0 - this.minorTickMarkOutsideLength); 717 } 718 else if (edge == RectangleEdge.BOTTOM) { 719 inside = new Line2D.Double (xx0, y0, xx0, 720 y0 - this.minorTickMarkInsideLength); 721 outside = new Line2D.Double (xx0, y0, xx0, 722 y0 + this.minorTickMarkOutsideLength); 723 } 724 if (tt0 >= firstOnAxis) { 725 g2.setPaint(this.minorTickMarkPaint); 726 g2.setStroke(this.minorTickMarkStroke); 727 g2.draw(inside); 728 g2.draw(outside); 729 } 730 tminor = tminor.next(); 731 tt0 = tminor.getFirstMillisecond(this.calendar); 732 } 733 } 734 t = t.next(); 735 t0 = t.getFirstMillisecond(this.calendar); 736 } 737 if (edge == RectangleEdge.TOP) { 738 state.cursorUp(Math.max(outsideLength, 739 this.minorTickMarkOutsideLength)); 740 } 741 else if (edge == RectangleEdge.BOTTOM) { 742 state.cursorDown(Math.max(outsideLength, 743 this.minorTickMarkOutsideLength)); 744 } 745 state.setTicks(ticks); 746 } 747 748 756 protected void drawTickMarksVertical(Graphics2D g2, AxisState state, 757 Rectangle2D dataArea, 758 RectangleEdge edge) { 759 760 } 761 762 773 protected AxisState drawTickLabels(int band, Graphics2D g2, AxisState state, 774 Rectangle2D dataArea, 775 RectangleEdge edge) { 776 777 double delta1 = 0.0; 779 FontMetrics fm = g2.getFontMetrics(this.labelInfo[band].getLabelFont()); 780 if (edge == RectangleEdge.BOTTOM) { 781 delta1 = this.labelInfo[band].getPadding().calculateTopOutset( 782 fm.getHeight()); 783 } 784 else if (edge == RectangleEdge.TOP) { 785 delta1 = this.labelInfo[band].getPadding().calculateBottomOutset( 786 fm.getHeight()); 787 } 788 state.moveCursor(delta1, edge); 789 long axisMin = this.first.getFirstMillisecond(this.calendar); 790 long axisMax = this.last.getLastMillisecond(this.calendar); 791 g2.setFont(this.labelInfo[band].getLabelFont()); 792 g2.setPaint(this.labelInfo[band].getLabelPaint()); 793 794 RegularTimePeriod p1 = this.labelInfo[band].createInstance( 796 new Date (axisMin), this.timeZone); 797 RegularTimePeriod p2 = this.labelInfo[band].createInstance( 798 new Date (axisMax), this.timeZone); 799 String label1 = this.labelInfo[band].getDateFormat().format( 800 new Date (p1.getMiddleMillisecond(this.calendar))); 801 String label2 = this.labelInfo[band].getDateFormat().format( 802 new Date (p2.getMiddleMillisecond(this.calendar))); 803 Rectangle2D b1 = TextUtilities.getTextBounds(label1, g2, 804 g2.getFontMetrics()); 805 Rectangle2D b2 = TextUtilities.getTextBounds(label2, g2, 806 g2.getFontMetrics()); 807 double w = Math.max(b1.getWidth(), b2.getWidth()); 808 long ww = Math.round(java2DToValue(dataArea.getX() + w + 5.0, 809 dataArea, edge)) - axisMin; 810 long length = p1.getLastMillisecond(this.calendar) 811 - p1.getFi
|