1 108 109 package org.jfree.chart.axis; 110 111 import java.awt.Font ; 112 import java.awt.FontMetrics ; 113 import java.awt.Graphics2D ; 114 import java.awt.font.FontRenderContext ; 115 import java.awt.font.LineMetrics ; 116 import java.awt.geom.Rectangle2D ; 117 import java.io.Serializable ; 118 import java.text.DateFormat ; 119 import java.text.SimpleDateFormat ; 120 import java.util.Calendar ; 121 import java.util.Date ; 122 import java.util.List ; 123 import java.util.TimeZone ; 124 125 import org.jfree.chart.event.AxisChangeEvent; 126 import org.jfree.chart.plot.Plot; 127 import org.jfree.chart.plot.PlotRenderingInfo; 128 import org.jfree.chart.plot.ValueAxisPlot; 129 import org.jfree.data.Range; 130 import org.jfree.data.time.DateRange; 131 import org.jfree.data.time.Month; 132 import org.jfree.data.time.RegularTimePeriod; 133 import org.jfree.data.time.Year; 134 import org.jfree.ui.RectangleEdge; 135 import org.jfree.ui.RectangleInsets; 136 import org.jfree.ui.TextAnchor; 137 import org.jfree.util.ObjectUtilities; 138 139 152 public class DateAxis extends ValueAxis implements Cloneable , Serializable { 153 154 155 private static final long serialVersionUID = -1013460999649007604L; 156 157 158 public static final DateRange DEFAULT_DATE_RANGE = new DateRange(); 159 160 161 public static final double 162 DEFAULT_AUTO_RANGE_MINIMUM_SIZE_IN_MILLISECONDS = 2.0; 163 164 165 public static final DateTickUnit DEFAULT_DATE_TICK_UNIT 166 = new DateTickUnit(DateTickUnit.DAY, 1, new SimpleDateFormat ()); 167 168 169 public static final Date DEFAULT_ANCHOR_DATE = new Date (); 170 171 172 private DateTickUnit tickUnit; 173 174 175 private DateFormat dateFormatOverride; 176 177 181 private DateTickMarkPosition tickMarkPosition = DateTickMarkPosition.START; 182 183 187 private static class DefaultTimeline implements Timeline, Serializable { 188 189 196 public long toTimelineValue(long millisecond) { 197 return millisecond; 198 } 199 200 207 public long toTimelineValue(Date date) { 208 return date.getTime(); 209 } 210 211 219 public long toMillisecond(long value) { 220 return value; 221 } 222 223 231 public boolean containsDomainValue(long millisecond) { 232 return true; 233 } 234 235 243 public boolean containsDomainValue(Date date) { 244 return true; 245 } 246 247 256 public boolean containsDomainRange(long from, long to) { 257 return true; 258 } 259 260 269 public boolean containsDomainRange(Date from, Date to) { 270 return true; 271 } 272 273 280 public boolean equals(Object object) { 281 282 if (object == null) { 283 return false; 284 } 285 286 if (object == this) { 287 return true; 288 } 289 290 if (object instanceof DefaultTimeline) { 291 return true; 292 } 293 294 return false; 295 296 } 297 } 298 299 300 private static final Timeline DEFAULT_TIMELINE = new DefaultTimeline(); 301 302 303 private TimeZone timeZone; 304 305 306 private Timeline timeline; 307 308 311 public DateAxis() { 312 this(null); 313 } 314 315 320 public DateAxis(String label) { 321 this(label, TimeZone.getDefault()); 322 } 323 324 334 public DateAxis(String label, TimeZone zone) { 335 super(label, DateAxis.createStandardDateTickUnits(zone)); 336 setTickUnit(DateAxis.DEFAULT_DATE_TICK_UNIT, false, false); 337 setAutoRangeMinimumSize( 338 DEFAULT_AUTO_RANGE_MINIMUM_SIZE_IN_MILLISECONDS 339 ); 340 setRange(DEFAULT_DATE_RANGE, false, false); 341 this.dateFormatOverride = null; 342 this.timeZone = zone; 343 this.timeline = DEFAULT_TIMELINE; 344 } 345 346 351 public Timeline getTimeline() { 352 return this.timeline; 353 } 354 355 363 public void setTimeline(Timeline timeline) { 364 if (this.timeline != timeline) { 365 this.timeline = timeline; 366 notifyListeners(new AxisChangeEvent(this)); 367 } 368 } 369 370 375 public DateTickUnit getTickUnit() { 376 return this.tickUnit; 377 } 378 379 386 public void setTickUnit(DateTickUnit unit) { 387 setTickUnit(unit, true, true); 388 } 389 390 397 public void setTickUnit(DateTickUnit unit, boolean notify, 398 boolean turnOffAutoSelection) { 399 400 this.tickUnit = unit; 401 if (turnOffAutoSelection) { 402 setAutoTickUnitSelection(false, false); 403 } 404 if (notify) { 405 notifyListeners(new AxisChangeEvent(this)); 406 } 407 408 } 409 410 416 public DateFormat getDateFormatOverride() { 417 return this.dateFormatOverride; 418 } 419 420 426 public void setDateFormatOverride(DateFormat formatter) { 427 this.dateFormatOverride = formatter; 428 notifyListeners(new AxisChangeEvent(this)); 429 } 430 431 438 public void setRange(Range range) { 439 setRange(range, true, true); 440 } 441 442 453 public void setRange(Range range, boolean turnOffAutoRange, 454 boolean notify) { 455 if (range == null) { 456 throw new IllegalArgumentException ("Null 'range' argument."); 457 } 458 if (!(range instanceof DateRange)) { 461 range = new DateRange(range); 462 } 463 super.setRange(range, turnOffAutoRange, notify); 464 } 465 466 473 public void setRange(Date lower, Date upper) { 474 if (lower.getTime() >= upper.getTime()) { 475 throw new IllegalArgumentException ("Requires 'lower' < 'upper'."); 476 } 477 setRange(new DateRange(lower, upper)); 478 } 479 480 487 public void setRange(double lower, double upper) { 488 if (lower >= upper) { 489 throw new IllegalArgumentException ("Requires 'lower' < 'upper'."); 490 } 491 setRange(new DateRange(lower, upper)); 492 } 493 494 499 public Date getMinimumDate() { 500 501 Date result = null; 502 503 Range range = getRange(); 504 if (range instanceof DateRange) { 505 DateRange r = (DateRange) range; 506 result = r.getLowerDate(); 507 } 508 else { 509 result = new Date ((long) range.getLowerBound()); 510 } 511 512 return result; 513 514 } 515 516 522 public void setMinimumDate(Date date) { 523 setRange(new DateRange(date, getMaximumDate()), true, false); 524 notifyListeners(new AxisChangeEvent(this)); 525 } 526 527 532 public Date getMaximumDate() { 533 534 Date result = null; 535 Range range = getRange(); 536 if (range instanceof DateRange) { 537 DateRange r = (DateRange) range; 538 result = r.getUpperDate(); 539 } 540 else { 541 result = new Date ((long) range.getUpperBound()); 542 } 543 return result; 544 545 } 546 547 553 public void setMaximumDate(Date maximumDate) { 554 setRange(new DateRange(getMinimumDate(), maximumDate), true, false); 555 notifyListeners(new AxisChangeEvent(this)); 556 } 557 558 563 public DateTickMarkPosition getTickMarkPosition() { 564 return this.tickMarkPosition; 565 } 566 567 573 public void setTickMarkPosition(DateTickMarkPosition position) { 574 if (position == null) { 575 throw new IllegalArgumentException ("Null 'position' argument."); 576 } 577 this.tickMarkPosition = position; 578 notifyListeners(new AxisChangeEvent(this)); 579 } 580 581 585 public void configure() { 586 if (isAutoRange()) { 587 autoAdjustRange(); 588 } 589 } 590 591 599 public boolean isHiddenValue(long millis) { 600 return (!this.timeline.containsDomainValue(new Date (millis))); 601 } 602 603 614 public double valueToJava2D(double value, Rectangle2D area, 615 RectangleEdge edge) { 616 617 value = this.timeline.toTimelineValue((long) value); 618 619 DateRange range = (DateRange) getRange(); 620 double axisMin = this.timeline.toTimelineValue(range.getLowerDate()); 621 double axisMax = this.timeline.toTimelineValue(range.getUpperDate()); 622 double result = 0.0; 623 if (RectangleEdge.isTopOrBottom(edge)) { 624 double minX = area.getX(); 625 double maxX = area.getMaxX(); 626 if (isInverted()) { 627 result = maxX + ((value - axisMin) / (axisMax - axisMin)) 628 * (minX - maxX); 629 } 630 else { 631 result = minX + ((value - axisMin) / (axisMax - axisMin)) 632 * (maxX - minX); 633 } 634 } 635 else if (RectangleEdge.isLeftOrRight(edge)) { 636 double minY = area.getMinY(); 637 double maxY = area.getMaxY(); 638 if (isInverted()) { 639 result = minY + (((value - axisMin) / (axisMax - axisMin)) 640 * (maxY - minY)); 641 } 642 else { 643 result = maxY - (((value - axisMin) / (axisMax - axisMin)) 644 * (maxY - minY)); 645 } 646 } 647 return result; 648 649 } 650 651 662 public double dateToJava2D(Date date, Rectangle2D area, 663 RectangleEdge edge) { 664 double value = date.getTime(); 665 return valueToJava2D(value, area, edge); 666 } 667 668 680 public double java2DToValue(double java2DValue, Rectangle2D area, 681 RectangleEdge edge) { 682 683 DateRange range = (DateRange) getRange(); 684 double axisMin = this.timeline.toTimelineValue(range.getLowerDate()); 685 double axisMax = this.timeline.toTimelineValue(range.getUpperDate()); 686 687 double min = 0.0; 688 double max = 0.0; 689 if (RectangleEdge.isTopOrBottom(edge)) { 690 min = area.getX(); 691 max = area.getMaxX(); 692 } 693 else if (RectangleEdge.isLeftOrRight(edge)) { 694 min = area.getMaxY(); 695 max = area.getY(); 696 } 697 698 double result; 699 if (isInverted()) { 700 result = axisMax - ((java2DValue - min) / (max - min) 701 * (axisMax - axisMin)); 702 } 703 else { 704 result = axisMin + ((java2DValue - min) / (max - min) 705 * (axisMax - axisMin)); 706 } 707 708 return this.timeline.toMillisecond((long) result); 709 } 710 711 718 public Date calculateLowestVisibleTickValue(DateTickUnit unit) { 719 return nextStandardDate(getMinimumDate(), unit); 720 } 721 722 729 public Date calculateHighestVisibleTickValue(DateTickUnit unit) { 730 return previousStandardDate(getMaximumDate(), unit); 731 } 732 733 741 protected Date previousStandardDate(Date date, DateTickUnit unit) { 742 743 int milliseconds; 744 int seconds; 745 int minutes; 746 int hours; 747 int days; 748 int months; 749 int years; 750 751 Calendar calendar = Calendar.getInstance(this.timeZone); 752 calendar.setTime(date); 753 int count = unit.getCount(); 754 int current = calendar.get(unit.getCalendarField()); 755 int value = count * (current / count); 756 757 switch (unit.getUnit()) { 758 759 case (DateTickUnit.MILLISECOND) : 760 years = calendar.get(Calendar.YEAR); 761 months = calendar.get(Calendar.MONTH); 762 days = calendar.get(Calendar.DATE); 763 hours = calendar.get(Calendar.HOUR_OF_DAY); 764 minutes = calendar.get(Calendar.MINUTE); 765 seconds = calendar.get(Calendar.SECOND); 766 calendar.set(years, months, days, hours, minutes, seconds); 767 calendar.set(Calendar.MILLISECOND, value); 768 return calendar.getTime(); 769 770 case (DateTickUnit.SECOND) : 771 years = calendar.get(Calendar.YEAR); 772 months = calendar.get(Calendar.MONTH); 773 days = calendar.get(Calendar.DATE); 774 hours = calendar.get(Calendar.HOUR_OF_DAY); 775 minutes = calendar.get(Calendar.MINUTE); 776 if (this.tickMarkPosition == DateTickMarkPosition.START) { 777 milliseconds = 0; 778 } 779 else if (this.tickMarkPosition == DateTickMarkPosition.MIDDLE) { 780 milliseconds = 500; 781 } 782 else { 783 milliseconds = 999; 784 } 785 calendar.set(Calendar.MILLISECOND, milliseconds); 786 calendar.set(years, months, days, hours, minutes, value); 787 return calendar.getTime(); 788 789 case (DateTickUnit.MINUTE) : 790 years = calendar.get(Calendar.YEAR); 791 months = calendar.get(Calendar.MONTH); 792 days = calendar.get(Calendar.DATE); 793 hours = calendar.get(Calendar.HOUR_OF_DAY); 794 if (this.tickMarkPosition == DateTickMarkPosition.START) { 795 seconds = 0; 796 } 797 else if (this.tickMarkPosition == DateTickMarkPosition.MIDDLE) { 798 seconds = 30; 799 } 800 else { 801 seconds = 59; 802 } 803 calendar.clear(Calendar.MILLISECOND); 804 calendar.set(years, months, days, hours, value, seconds); 805 return calendar.getTime(); 806 807 case (DateTickUnit.HOUR) : 808 years = calendar.get(Calendar.YEAR); 809 months = calendar.get(Calendar.MONTH); 810 days = calendar.get(Calendar.DATE); 811 if (this.tickMarkPosition == DateTickMarkPosition.START) { 812 minutes = 0; 813 seconds = 0; 814 } 815 else if (this.tickMarkPosition == DateTickMarkPosition.MIDDLE) { 816 minutes = 30; 817 seconds = 0; 818 } 819 else { 820 minutes = 59; 821 seconds = 59; 822 } 823 calendar.clear(Calendar.MILLISECOND); 824 calendar.set(years, months, days, value, minutes, seconds); 825 return calendar.getTime(); 826 827 case (DateTickUnit.DAY) : 828 years = calendar.get(Calendar.YEAR); 829 months = calendar.get(Calendar.MONTH); 830 if (this.tickMarkPosition == DateTickMarkPosition.START) { 831 hours = 0; 832 minutes = 0; 833 seconds = 0; 834 } 835 else if (this.tickMarkPosition == DateTickMarkPosition.MIDDLE) { 836 hours = 12; 837 minutes = 0; 838 seconds = 0; 839 } 840 else { 841 hours = 23; 842 minutes = 59; 843 seconds = 59; 844 } 845 calendar.clear(Calendar.MILLISECOND); 846 calendar.set(years, months, value, hours, 0, 0); 847 long result = calendar.getTime().getTime(); 850 if (result > date.getTime()) { 851 calendar.set(years, months, value - 1, hours, 0, 0); 852 } 853 return calendar.getTime(); 854 855 case (DateTickUnit.MONTH) : 856 years = calendar.get(Calendar.YEAR); 857 calendar.clear(Calendar.MILLISECOND); 858 calendar.set(years, value, 1, 0, 0, 0); 859 Month month = new Month(calendar.getTime()); 860 Date standardDate = calculateDateForPosition( 861 month, this.tickMarkPosition 862 ); 863 long millis = standardDate.getTime(); 864 if (millis > date.getTime()) { 865 month = (Month) month.previous(); 866 standardDate = calculateDateForPosition( 867 month, this.tickMarkPosition 868 ); 869 } 870 return standardDate; 871 872 case(DateTickUnit.YEAR) : 873 if (this.tickMarkPosition == DateTickMarkPosition.START) { 874 months = 0; 875 days = 1; 876 } 877 else if (this.tickMarkPosition == DateTickMarkPosition.MIDDLE) { 878 months = 6; 879 days = 1; 880 } 881 else { 882 months = 11; 883 days = 31; 884 } 885 calendar.clear(Calendar.MILLISECOND); 886 calendar.set(value, months, days, 0, 0, 0); 887 return calendar.getTime(); 888 889 default: return null; 890 891 } 892 893 } 894 895 904 private Date calculateDateForPosition(RegularTimePeriod period, 905 DateTickMarkPosition position) { 906 907 if (position == null) { 908 throw new IllegalArgumentException ("Null 'position' argument."); 909 } 910 Date result = null; 911 if (position == DateTickMarkPosition.START) { 912 result = new Date (period.getFirstMillisecond()); 913 } 914 else if (position == DateTickMarkPosition.MIDDLE) { 915 result = new Date (period.getMiddleMillisecond()); 916 } 917 else if (position == DateTickMarkPosition.END) { 918 result = new Date (period.getLastMillisecond()); 919 } 920 return result; 921 922 } 923 924 933 protected Date nextStandardDate(Date date, DateTickUnit unit) { 934 935 Date previous = previousStandardDate(date, unit); 936 Calendar calendar = Calendar.getInstance(); 937 calendar.setTime(previous); 938 calendar.add(unit.getCalendarField(), unit.getCount()); 939 return calendar.getTime(); 940 941 } 942 943 952 public static TickUnitSource createStandardDateTickUnits() { 953 return createStandardDateTickUnits(TimeZone.getDefault()); 954 } 955 956 967 public static TickUnitSource createStandardDateTickUnits(TimeZone zone) { 968 969 if (zone == null) { 970 throw new IllegalArgumentException ("Null 'zone' argument."); 971 } 972 TickUnits units = new TickUnits(); 973 974 DateFormat f1 = new SimpleDateFormat ("HH:mm:ss.SSS"); 976 DateFormat f2 = new SimpleDateFormat ("HH:mm:ss"); 977 DateFormat f3 = new SimpleDateFormat ("HH:mm"); 978 DateFormat f4 = new SimpleDateFormat ("d-MMM, HH:mm"); 979 DateFormat f5 = new SimpleDateFormat ("d-MMM"); 980 DateFormat f6 = new SimpleDateFormat ("MMM-yyyy"); 981 DateFormat f7 = new SimpleDateFormat ("yyyy"); 982 983 f1.setTimeZone(zone); 984 f2.setTimeZone(zone); 985 f3.setTimeZone(zone); 986 f4.setTimeZone(zone); 987 f5.setTimeZone(zone); 988 f6.setTimeZone(zone); 989 f7.setTimeZone(zone); 990 991 units.add(new DateTickUnit(DateTickUnit.MILLISECOND, 1, f1)); 993 units.add( 994 new DateTickUnit( 995 DateTickUnit.MILLISECOND, 5, DateTickUnit.MILLISECOND, 1, f1 996 ) 997 ); 998 units.add( 999 new DateTickUnit( 1000 DateTickUnit.MILLISECOND, 10, DateTickUnit.MILLISECOND, 1, f1 1001 ) 1002 ); 1003 units.add( 1004 new DateTickUnit( 1005 DateTickUnit.MILLISECOND, 25, DateTickUnit.MILLISECOND, 5, f1 1006 ) 1007 ); 1008 units.add( 1009 new DateTickUnit( 1010 DateTickUnit.MILLISECOND, 50, DateTickUnit.MILLISECOND, 10, f1 1011 ) 1012 ); 1013 units.add( 1014 new DateTickUnit( 1015 DateTickUnit.MILLISECOND, 100, DateTickUnit.MILLISECOND, 10, f1 1016 ) 1017 ); 1018 units.add( 1019 new DateTickUnit( 1020 DateTickUnit.MILLISECOND, 250, DateTickUnit.MILLISECOND, 10, f1 1021 ) 1022 ); 1023 units.add( 1024 new DateTickUnit( 1025 DateTickUnit.MILLISECOND, 500, DateTickUnit.MILLISECOND, 50, f1 1026 ) 1027 ); 1028 1029 units.add( 1031 new DateTickUnit( 1032 DateTickUnit.SECOND, 1, DateTickUnit.MILLISECOND, 50, f2 1033 ) 1034 ); 1035 units.add( 1036 new DateTickUnit( 1037 DateTickUnit.SECOND, 5, DateTickUnit.SECOND, 1, f2 1038 ) 1039 ); 1040 units.add( 1041 new DateTickUnit( 1042 DateTickUnit.SECOND, 10, DateTickUnit.SECOND, 1, f2 1043 ) 1044 ); 1045 units.add( 1046 new DateTickUnit( 1047 DateTickUnit.SECOND, 30, DateTickUnit.SECOND, 5, f2 1048 ) 1049 ); 1050 1051 units.add( 1053 new DateTickUnit(DateTickUnit.MINUTE, 1, DateTickUnit.SECOND, 5, f3) 1054 ); 1055 units.add( 1056 new DateTickUnit( 1057 DateTickUnit.MINUTE, 2, DateTickUnit.SECOND, 10, f3 1058 ) 1059 ); 1060 units.add( 1061 new DateTickUnit(DateTickUnit.MINUTE, 5, DateTickUnit.MINUTE, 1, f3) 1062 ); 1063 units.add( 1064 new DateTickUnit( 1065 DateTickUnit.MINUTE, 10, DateTickUnit.MINUTE, 1, f3 1066 ) 1067 ); 1068 units.add( 1069 new DateTickUnit( 1070 DateTickUnit.MINUTE, 15, DateTickUnit.MINUTE, 5, f3 1071 ) 1072 ); 1073 units.add( 1074 new DateTickUnit( 1075 DateTickUnit.MINUTE, 20, DateTickUnit.MINUTE, 5, f3 1076 ) 1077 ); 1078 units.add( 1079 new DateTickUnit( 1080 DateTickUnit.MINUTE, 30, DateTickUnit.MINUTE, 5, f3 1081 ) 1082 ); 1083 1084 units.add( 1086 new DateTickUnit(DateTickUnit.HOUR, 1, DateTickUnit.MINUTE, 5, f3) 1087 ); 1088 units.add( 1089 new DateTickUnit(DateTickUnit.HOUR, 2, DateTickUnit.MINUTE, 10, f3) 1090 ); 1091 units.add( 1092 new DateTickUnit(DateTickUnit.HOUR, 4, DateTickUnit.MINUTE, 30, f3) 1093 ); 1094 units.add( 1095 new DateTickUnit(DateTickUnit.HOUR, 6, DateTickUnit.HOUR, 1, f3) 1096 ); 1097 units.add( 1098 new DateTickUnit(DateTickUnit.HOUR, 12, DateTickUnit.HOUR, 1, f4) 1099 ); 1100 1101 units.add( 1103 new DateTickUnit(DateTickUnit.DAY, 1, DateTickUnit.HOUR, 1, f5) 1104 ); 1105 units.add( 1106 new DateTickUnit(DateTickUnit.DAY, 2, DateTickUnit.HOUR, 1, f5) 1107 ); 1108 units.add( 1109 new DateTickUnit(DateTickUnit.DAY, 7, DateTickUnit.DAY, 1, f5) 1110 ); 1111 units.add( 1112 new DateTickUnit(DateTickUnit.DAY, 15, DateTickUnit.DAY, 1, f5) 1113 ); 1114 1115 units.add( 1117 new DateTickUnit(DateTickUnit.MONTH, 1, DateTickUnit.DAY, 1, f6) 1118 ); 1119 units.add( 1120 new DateTickUnit(DateTickUnit.MONTH, 2, DateTickUnit.DAY, 1, f6) 1121 ); 1122 units.add( 1123 new DateTickUnit(DateTickUnit.MONTH, 3, DateTickUnit.MONTH, 1, f6) 1124 ); 1125 units.add( 1126 new DateTickUnit(DateTickUnit.MONTH, 4, DateTickUnit.MONTH, 1, f6) 1127 ); 1128 units.add( 1129 new DateTickUnit(DateTickUnit.MONTH, 6, DateTickUnit.MONTH, 1, f6) 1130 ); 1131 1132 units.add( 1134 new DateTickUnit(DateTickUnit.YEAR, 1, DateTickUnit.MONTH, 1, f7) 1135 ); 1136 units.add( 1137 new DateTickUnit(DateTickUnit.YEAR, 2, DateTickUnit.MONTH, 3, f7) 1138 ); 1139 units.add( 1140 new DateTickUnit(DateTickUnit.YEAR, 5, DateTickUnit.YEAR, 1, f7) 1141 ); 1142 units.add( 1143 new DateTickUnit(DateTickUnit.YEAR, 10, DateTickUnit.YEAR, 1, f7) 1144 ); 1145 units.add( 1146 new DateTickUnit(DateTickUnit.YEAR, 25, DateTickUnit.YEAR, 5, f7) 1147 ); 1148 units.add( 1149 new DateTickUnit(DateTickUnit.YEAR, 50, DateTickUnit.YEAR, 10, f7) 1150 ); 1151 units.add( 1152 new DateTickUnit(DateTickUnit.YEAR, 100, DateTickUnit.YEAR, 20, f7) 1153 ); 1154 1155 return units; 1156 1157 } 1158 1159 1162 protected void autoAdjustRange() { 1163 1164 Plot plot = getPlot(); 1165 1166 if (plot == null) { 1167 return; } 1169 1170 if (plot instanceof ValueAxisPlot) { 1171 ValueAxisPlot vap = (ValueAxisPlot) plot; 1172 1173 Range r = vap.getDataRange(this); 1174 if (r == null) { 1175 if (this.timeline instanceof SegmentedTimeline) { 1176 r = new DateRange( 1178 ((SegmentedTimeline) this.timeline).getStartTime(), 1179 ((SegmentedTimeline) this.timeline).getStartTime() + 1 1180 ); 1181 } 1182 else { 1183 r = new DateRange(); 1184 } 1185 } 1186 1187 long upper = this.timeline.toTimelineValue( 1188 (long) r.getUpperBound() 1189 ); 1190 long lower; 1191 long fixedAutoRange = (long) getFixedAutoRange(); 1192 if (fixedAutoRange > 0.0) { 1193 lower = upper - fixedAutoRange; 1194 } 1195 else { 1196 lower = this.timeline.toTimelineValue((long) r.getLowerBound()); 1197 double range = upper - lower; 1198 long minRange = (long) getAutoRangeMinimumSize(); 1199 if (range < minRange) { 1200 long expand = (long) (minRange - range) / 2; 1201 upper = upper + expand; 1202 lower = lower - expand; 1203 } 1204 upper = upper + (long) (range * getUpperMargin()); 1205 lower = lower - (long) (range * getLowerMargin()); 1206 } 1207 1208 upper = this.timeline.toMillisecond(upper); 1209 lower = this.timeline.toMillisecond(lower); 1210 DateRange dr = new DateRange(new Date (lower), new Date (upper)); 1211 setRange(dr, false, false); 1212 } 1213 1214 } 1215 1216 1225 protected void selectAutoTickUnit(Graphics2D g2, 1226 Rectangle2D dataArea, 1227 RectangleEdge edge) { 1228 1229 if (RectangleEdge.isTopOrBottom(edge)) { 1230 selectHorizontalAutoTickUnit(g2, dataArea, edge); 1231 } 1232 else if (RectangleEdge.isLeftOrRight(edge)) { 1233 selectVerticalAutoTickUnit(g2, dataArea, edge); 1234 } 1235 1236 } 1237 1238 1247 protected void selectHorizontalAutoTickUnit(Graphics2D g2, 1248 Rectangle2D dataArea, 1249 RectangleEdge edge) { 1250 1251 long shift = 0; 1252 if (this.timeline instanceof SegmentedTimeline) { 1253 shift = ((SegmentedTimeline) this.timeline).getStartTime(); 1254 } 1255 double zero = valueToJava2D(shift + 0.0, dataArea, edge); 1256 double tickLabelWidth 1257 = estimateMaximumTickLabelWidth(g2, getTickUnit()); 1258 1259 TickUnitSource tickUnits = getStandardTickUnits(); 1261 TickUnit unit1 = tickUnits.getCeilingTickUnit(getTickUnit()); 1262 double x1 = valueToJava2D(shift + unit1.getSize(), dataArea, edge); 1263 double unit1Width = Math.abs(x1 - zero); 1264 1265 double guess = (tickLabelWidth / unit1Width) * unit1.getSize(); 1267 DateTickUnit unit2 = (DateTickUnit) tickUnits.getCeilingTickUnit(guess); 1268 double x2 = valueToJava2D(shift + unit2.getSize(), dataArea, edge); 1269 double unit2Width = Math.abs(x2 - zero); 1270 tickLabelWidth = estimateMaximumTickLabelWidth(g2, unit2); 1271 if (tickLabelWidth > unit2Width) { 1272 unit2 = (DateTickUnit) tickUnits.getLargerTickUnit(unit2); 1273 } 1274 setTickUnit(unit2, false, false); 1275 } 1276 1277 1286 protected void selectVerticalAutoTickUnit(Graphics2D g2, 1287 Rectangle2D dataArea, 1288 RectangleEdge edge) { 1289 1290 TickUnitSource tickUnits = getStandardTickUnits(); 1292 double zero = valueToJava2D(0.0, dataArea, edge); 1293 1294 double estimate1 = getRange().getLength() / 10.0; 1296 DateTickUnit candidate1 1297 = (DateTickUnit) tickUnits.getCeilingTickUnit(estimate1); 1298 double labelHeight1 = estimateMaximumTickLabelHeight(g2, candidate1); 1299 double y1 = valueToJava2D(candidate1.getSize(), dataArea, edge); 1300 double candidate1UnitHeight = Math.abs(y1 - zero); 1301 1302 double estimate2 1304 = (labelHeight1 / candidate1UnitHeight) * candidate1.getSize(); 1305 DateTickUnit candidate2 1306 = (DateTickUnit) tickUnits.getCeilingTickUnit(estimate2); 1307 double labelHeight2 = estimateMaximumTickLabelHeight(g2, candidate2); 1308 double y2 = valueToJava2D(candidate2.getSize(), dataArea, edge); 1309 double unit2Height = Math.abs(y2 - zero); 1310 1311 DateTickUnit finalUnit; 1313 if (labelHeight2 < unit2Height) { 1314 finalUnit = candidate2; 1315 } 1316 else { 1317 finalUnit = (DateTickUnit) tickUnits.getLargerTickUnit(candidate2); 1318 } 1319 setTickUnit(finalUnit, false, false); 1320 1321 } 1322 1323 1336 private double estimateMaximumTickLabelWidth(Graphics2D g2, 1337 DateTickUnit unit) { 1338 1339 RectangleInsets tickLabelInsets = getTickLabelInsets(); 1340 double result = tickLabelInsets.getLeft() + tickLabelInsets.getRight(); 1341 1342 Font tickLabelFont = getTickLabelFont(); 1343 FontRenderContext frc = g2.getFontRenderContext(); 1344 LineMetrics lm = tickLabelFont.getLineMetrics("ABCxyz", frc); 1345 if (isVerticalTickLabels()) { 1346 result += lm.getHeight(); 1349 } 1350 else { 1351 DateRange range = (DateRange) getRange(); 1353 Date lower = range.getLowerDate(); 1354 Date upper = range.getUpperDate(); 1355 String lowerStr = null; 1356 String upperStr = null; 1357 DateFormat formatter = getDateFormatOverride(); 1358 if (formatter != null) { 1359 lowerStr = formatter.format(lower); 1360 upperStr = formatter.format(upper); 1361 } 1362 else { 1363 lowerStr = unit.dateToString(lower); 1364 upperStr = unit.dateToString(upper); 1365 } 1366 FontMetrics fm = g2.getFontMetrics(tickLabelFont); 1367 double w1 = fm.stringWidth(lowerStr); 1368 double w2 = fm.stringWidth(upperStr); 1369 result += Math.max(w1, w2); 1370 } 1371 1372 return result; 1373 1374 } 1375 1376 1389 private double estimateMaximumTickLabelHeight(Graphics2D g2, 1390 DateTickUnit unit) { 1391 1392 RectangleInsets tickLabelInsets = getTickLabelInsets(); 1393 double result = tickLabelInsets.getTop() + tickLabelInsets.getBottom(); 1394 1395 Font tickLabelFont = getTickLabelFont(); 1396 FontRenderContext frc = g2.getFontRenderContext(); 1397 LineMetrics lm = tickLabelFont.getLineMetrics("ABCxyz", frc); 1398 if (!isVerticalTickLabels()) { 1399 result += lm.getHeight(); 1402 } 1403 else { 1404 DateRange range = (DateRange) getRange(); 1406 Date lower = range.getLowerDate(); 1407 Date upper = range.getUpperDate(); 1408 String lowerStr = null; 1409 String upperStr = null; 1410 DateFormat formatter = getDateFormatOverride(); 1411 if (formatter != null) { 1412 lowerStr = formatter.format(lower); 1413 upperStr = formatter.format(upper); 1414 } 1415 else { 1416 lowerStr = unit.dateToString(lower); 1417 upperStr = unit.dateToString(upper); 1418 } 1419 FontMetrics fm = g2.getFontMetrics(tickLabelFont); 1420 double w1 = fm.stringWidth(lowerStr); 1421 double w2 = fm.stringWidth(upperStr); 1422 result += Math.max(w1, w2); 1423 } 1424 1425 return result; 1426 1427 } 1428 1429 1440 public List refreshTicks(Graphics2D g2, 1441 AxisState state, 1442 Rectangle2D dataArea, 1443 RectangleEdge edge) { 1444 1445 List result = null; 1446 if (RectangleEdge.isTopOrBottom(edge)) { 1447 result = refreshTicksHorizontal(g2, dataArea, edge); 1448 } 1449 else if (RectangleEdge.isLeftOrRight(edge)) { 1450 result = refreshTicksVertical(g2, dataArea, edge); 1451 } 1452 return result; 1453 1454 } 1455 1456 1465 protected List refreshTicksHorizontal(Graphics2D g2, 1466 Rectangle2D dataArea, 1467 RectangleEdge edge) { 1468 1469 List result = new java.util.ArrayList (); 1470 1471 Font tickLabelFont = getTickLabelFont(); 1472 g2.setFont(tickLabelFont); 1473 1474 if (isAutoTickUnitSelection()) { 1475 selectAutoTickUnit(g2, dataArea, edge); 1476 } 1477 1478 DateTickUnit unit = getTickUnit(); 1479 Date tickDate = calculateLowestVisibleTickValue(unit); 1480 Date upperDate = getMaximumDate(); 1481 while (tickDate.before(upperDate)) { 1483 1484 if (!isHiddenValue(tickDate.getTime())) { 1485 String tickLabel; 1487 DateFormat formatter = getDateFormatOverride(); 1488 if (formatter != null) { 1489 tickLabel = formatter.format(tickDate); 1490 } 1491 else { 1492 tickLabel = this.tickUnit.dateToString(tickDate); 1493 } 1494 TextAnchor anchor = null; 1495 TextAnchor rotationAnchor = null; 1496 double angle = 0.0; 1497 if (isVerticalTickLabels()) { 1498 anchor = TextAnchor.CENTER_RIGHT; 1499 rotationAnchor = TextAnchor.CENTER_RIGHT; 1500 if (edge == RectangleEdge.TOP) { 1501 angle = Math.PI / 2.0; 1502 } 1503 else { 1504 angle = -Math.PI / 2.0; 1505 } 1506 } 1507 else { 1508 if (edge == RectangleEdge.TOP) { 1509 anchor = TextAnchor.BOTTOM_CENTER; 1510 rotationAnchor = TextAnchor.BOTTOM_CENTER; 1511 } 1512 else { 1513 anchor = TextAnchor.TOP_CENTER; 1514 rotationAnchor = TextAnchor.TOP_CENTER; 1515 } 1516 } 1517 1518 Tick tick = new DateTick( 1519 tickDate, tickLabel, anchor, rotationAnchor, angle 1520 ); 1521 result.add(tick); 1522 tickDate = unit.addToDate(tickDate); 1523 } 1524 else { 1525 tickDate = unit.rollDate(tickDate); 1526 continue; 1527 } 1528 1529 switch (unit.getUnit()) { 1531 1532 case (DateTickUnit.MILLISECOND) : 1533 case (DateTickUnit.SECOND) : 1534 case (DateTickUnit.MINUTE) : 1535 case (DateTickUnit.HOUR) : 1536 case (DateTickUnit.DAY) : 1537 break; 1538 case (DateTickUnit.MONTH) : 1539 tickDate = calculateDateForPosition( 1540 new Month(tickDate), this.tickMarkPosition 1541 ); 1542 break; 1543 case(DateTickUnit.YEAR) : 1544 tickDate = calculateDateForPosition( 1545 new Year(tickDate), this.tickMarkPosition 1546 ); 1547 break; 1548 1549 default: break; 1550 1551 } 1552 1553 } 1554 return result; 1555 1556 } 1557 1558 1567 protected List refreshTicksVertical(Graphics2D g2, 1568 Rectangle2D dataArea, 1569 RectangleEdge edge) { 1570 1571 List result = new java.util.ArrayList (); 1572 1573 Font tickLabelFont = getTickLabelFont(); 1574 g2.setFont(tickLabelFont); 1575 1576 if (isAutoTickUnitSelection()) { 1577 selectAutoTickUnit(g2, dataArea, edge); 1578 } 1579 DateTickUnit unit = getTickUnit(); 1580 Date tickDate = calculateLowestVisibleTickValue(unit); 1581 Date upperDate = getMaximumDate(); 1583 while (tickDate.before(upperDate)) { 1584 1585 if (!isHiddenValue(tickDate.getTime())) { 1586 String tickLabel; 1588 DateFormat formatter = getDateFormatOverride(); 1589 if (formatter != null) { 1590 tickLabel = formatter.format(tickDate); 1591 } 1592 else { 1593 tickLabel = this.tickUnit.dateToString(tickDate); 1594 } 1595 TextAnchor anchor = null; 1596 TextAnchor rotationAnchor = null; 1597 double angle = 0.0; 1598 if (isVerticalTickLabels()) { 1599 anchor = TextAnchor.BOTTOM_CENTER; 1600 rotationAnchor = TextAnchor.BOTTOM_CENTER; 1601 if (edge == RectangleEdge.LEFT) { 1602 angle = -Math.PI / 2.0; 1603 } 1604 else { 1605 angle = Math.PI / 2.0; 1606 } 1607 } 1608 else { 1609 if (edge == RectangleEdge.LEFT) { 1610 anchor = TextAnchor.CENTER_RIGHT; 1611 rotationAnchor = TextAnchor.CENTER_RIGHT; 1612 } 1613 else { 1614 anchor = TextAnchor.CENTER_LEFT; 1615 rotationAnchor = TextAnchor.CENTER_LEFT; 1616 } 1617 } 1618 1619 Tick tick = new DateTick( 1620 tickDate, tickLabel, anchor, rotationAnchor, angle 1621 ); 1622 result.add(tick); 1623 tickDate = unit.addToDate(tickDate); 1624 } 1625 else { 1626 tickDate = unit.rollDate(tickDate); 1627 } 1628 } 1629 return result; 1630 } 1631 1632 1648 public AxisState draw(Graphics2D g2, 1649 double cursor, 1650 Rectangle2D plotArea, 1651 Rectangle2D dataArea, 1652 RectangleEdge edge, 1653 PlotRenderingInfo plotState) { 1654 1655 if (!isVisible()) { 1657 AxisState state = new AxisState(cursor); 1658 List ticks = refreshTicks(g2, state, dataArea, edge); 1661 state.setTicks(ticks); 1662 return state; 1663 } 1664 1665 AxisState state = drawTickMarksAndLabels( 1667 g2, cursor, plotArea, dataArea, edge 1668 ); 1669 1670 state = drawLabel(getLabel(), g2, plotArea, dataArea, edge, state); 1673 1674 return state; 1675 1676 } 1677 1678 1684 public void zoomRange(double lowerPercent, double upperPercent) { 1685 double start = this.timeline.toTimelineValue( 1686 (long) getRange().getLowerBound() 1687 ); 1688 double length = (this.timeline.toTimelineValue( 1689 (long) getRange().getUpperBound()) 1690 - this.timeline.toTimelineValue( 1691 (long) getRange().getLowerBound() 1692 )); 1693 Range adjusted = null; 1694 if (isInverted()) { 1695 adjusted = new DateRange( 1696 this.timeline.toMillisecond( 1697 (long) (start + (length * (1 - upperPercent))) 1698 ), 1699 this.timeline.toMillisecond( 1700 (long) (start + (length * (1 - lowerPercent))) 1701 ) 1702 ); 1703 } 1704 else { 1705 adjusted = new DateRange(this.timeline.toMillisecond( 1706 (long) (start + length * lowerPercent)), 1707 this.timeline.toMillisecond( 1708 (long) (start + length * upperPercent) 1709 ) 1710 ); 1711 } 1712 setRange(adjusted); 1713 } 1714 1715 1722 public boolean equals(Object obj) { 1723 if (obj == this) { 1724 return true; 1725 } 1726 if (!(obj instanceof DateAxis)) { 1727 return false; 1728 } 1729 DateAxis that = (DateAxis) obj; 1730 if (!ObjectUtilities.equal(this.tickUnit, that.tickUnit)) { 1731 return false; 1732 } 1733 if (!ObjectUtilities.equal( 1734 this.dateFormatOverride, that.dateFormatOverride) 1735 ) { 1736 return false; 1737 } 1738 if (!ObjectUtilities.equal( 1739 this.tickMarkPosition, that.tickMarkPosition 1740 )) { 1741 return false; 1742 } 1743 if (!ObjectUtilities.equal(this.timeline, that.timeline)) { 1744 return false; 1745 } 1746 return true; 1747 } 1748 1749 1754 public int hashCode() { 1755 if (getLabel() != null) { 1756 return getLabel().hashCode(); 1757 } 1758 else { 1759 return 0; 1760 } 1761 } 1762 1763 1771 public Object clone() throws CloneNotSupportedException { 1772 1773 DateAxis clone = (DateAxis) super.clone(); 1774 1775 if (this.dateFormatOverride != null) { 1777 clone.dateFormatOverride 1778 = (DateFormat ) this.dateFormatOverride.clone(); 1779 } 1780 1782 return clone; 1783 1784 } 1785 1786} 1787 | Popular Tags |