1:
112:
113: package ;
114:
115: import ;
116: import ;
117: import ;
118: import ;
119: import ;
120: import ;
121: import ;
122: import ;
123: import ;
124: import ;
125: import ;
126: import ;
127: import ;
128:
129: import ;
130: import ;
131: import ;
132: import ;
133: import ;
134: import ;
135: import ;
136: import ;
137: import ;
138: import ;
139: import ;
140: import ;
141: import ;
142:
143:
156: public class DateAxis extends ValueAxis implements Cloneable, Serializable {
157:
158:
159: private static final long serialVersionUID = -1013460999649007604L;
160:
161:
162: public static final DateRange DEFAULT_DATE_RANGE = new DateRange();
163:
164:
165: public static final double
166: DEFAULT_AUTO_RANGE_MINIMUM_SIZE_IN_MILLISECONDS = 2.0;
167:
168:
169: public static final DateTickUnit DEFAULT_DATE_TICK_UNIT
170: = new DateTickUnit(DateTickUnit.DAY, 1, new SimpleDateFormat());
171:
172:
173: public static final Date DEFAULT_ANCHOR_DATE = new Date();
174:
175:
176: private DateTickUnit tickUnit;
177:
178:
179: private DateFormat dateFormatOverride;
180:
181:
185: private DateTickMarkPosition tickMarkPosition = DateTickMarkPosition.START;
186:
187:
191: private static class DefaultTimeline implements Timeline, Serializable {
192:
193:
200: public long toTimelineValue(long millisecond) {
201: return millisecond;
202: }
203:
204:
211: public long toTimelineValue(Date date) {
212: return date.getTime();
213: }
214:
215:
223: public long toMillisecond(long value) {
224: return value;
225: }
226:
227:
235: public boolean containsDomainValue(long millisecond) {
236: return true;
237: }
238:
239:
247: public boolean containsDomainValue(Date date) {
248: return true;
249: }
250:
251:
260: public boolean containsDomainRange(long from, long to) {
261: return true;
262: }
263:
264:
273: public boolean containsDomainRange(Date from, Date to) {
274: return true;
275: }
276:
277:
284: public boolean equals(Object object) {
285:
286: if (object == null) {
287: return false;
288: }
289:
290: if (object == this) {
291: return true;
292: }
293:
294: if (object instanceof DefaultTimeline) {
295: return true;
296: }
297:
298: return false;
299:
300: }
301: }
302:
303:
304: private static final Timeline DEFAULT_TIMELINE = new DefaultTimeline();
305:
306:
307: private TimeZone timeZone;
308:
309:
310: private Timeline timeline;
311:
312:
315: public DateAxis() {
316: this(null);
317: }
318:
319:
324: public DateAxis(String label) {
325: this(label, TimeZone.getDefault());
326: }
327:
328:
338: public DateAxis(String label, TimeZone zone) {
339: super(label, DateAxis.createStandardDateTickUnits(zone));
340: setTickUnit(DateAxis.DEFAULT_DATE_TICK_UNIT, false, false);
341: setAutoRangeMinimumSize(
342: DEFAULT_AUTO_RANGE_MINIMUM_SIZE_IN_MILLISECONDS
343: );
344: setRange(DEFAULT_DATE_RANGE, false, false);
345: this.dateFormatOverride = null;
346: this.timeZone = zone;
347: this.timeline = DEFAULT_TIMELINE;
348: }
349:
350:
355: public Timeline getTimeline() {
356: return this.timeline;
357: }
358:
359:
367: public void setTimeline(Timeline timeline) {
368: if (this.timeline != timeline) {
369: this.timeline = timeline;
370: notifyListeners(new AxisChangeEvent(this));
371: }
372: }
373:
374:
387: public DateTickUnit getTickUnit() {
388: return this.tickUnit;
389: }
390:
391:
401: public void setTickUnit(DateTickUnit unit) {
402: setTickUnit(unit, true, true);
403: }
404:
405:
414: public void setTickUnit(DateTickUnit unit, boolean notify,
415: boolean turnOffAutoSelection) {
416:
417: this.tickUnit = unit;
418: if (turnOffAutoSelection) {
419: setAutoTickUnitSelection(false, false);
420: }
421: if (notify) {
422: notifyListeners(new AxisChangeEvent(this));
423: }
424:
425: }
426:
427:
433: public DateFormat getDateFormatOverride() {
434: return this.dateFormatOverride;
435: }
436:
437:
443: public void setDateFormatOverride(DateFormat formatter) {
444: this.dateFormatOverride = formatter;
445: notifyListeners(new AxisChangeEvent(this));
446: }
447:
448:
455: public void setRange(Range range) {
456: setRange(range, true, true);
457: }
458:
459:
470: public void setRange(Range range, boolean turnOffAutoRange,
471: boolean notify) {
472: if (range == null) {
473: throw new IllegalArgumentException("Null 'range' argument.");
474: }
475:
476:
477: if (!(range instanceof DateRange)) {
478: range = new DateRange(range);
479: }
480: super.setRange(range, turnOffAutoRange, notify);
481: }
482:
483:
490: public void setRange(Date lower, Date upper) {
491: if (lower.getTime() >= upper.getTime()) {
492: throw new IllegalArgumentException("Requires 'lower' < 'upper'.");
493: }
494: setRange(new DateRange(lower, upper));
495: }
496:
497:
504: public void setRange(double lower, double upper) {
505: if (lower >= upper) {
506: throw new IllegalArgumentException("Requires 'lower' < 'upper'.");
507: }
508: setRange(new DateRange(lower, upper));
509: }
510:
511:
516: public Date getMinimumDate() {
517:
518: Date result = null;
519:
520: Range range = getRange();
521: if (range instanceof DateRange) {
522: DateRange r = (DateRange) range;
523: result = r.getLowerDate();
524: }
525: else {
526: result = new Date((long) range.getLowerBound());
527: }
528:
529: return result;
530:
531: }
532:
533:
539: public void setMinimumDate(Date date) {
540: setRange(new DateRange(date, getMaximumDate()), true, false);
541: notifyListeners(new AxisChangeEvent(this));
542: }
543:
544:
549: public Date getMaximumDate() {
550:
551: Date result = null;
552: Range range = getRange();
553: if (range instanceof DateRange) {
554: DateRange r = (DateRange) range;
555: result = r.getUpperDate();
556: }
557: else {
558: result = new Date((long) range.getUpperBound());
559: }
560: return result;
561:
562: }
563:
564:
570: public void setMaximumDate(Date maximumDate) {
571: setRange(new DateRange(getMinimumDate(), maximumDate), true, false);
572: notifyListeners(new AxisChangeEvent(this));
573: }
574:
575:
580: public DateTickMarkPosition getTickMarkPosition() {
581: return this.tickMarkPosition;
582: }
583:
584:
590: public void setTickMarkPosition(DateTickMarkPosition position) {
591: if (position == null) {
592: throw new IllegalArgumentException("Null 'position' argument.");
593: }
594: this.tickMarkPosition = position;
595: notifyListeners(new AxisChangeEvent(this));
596: }
597:
598:
602: public void configure() {
603: if (isAutoRange()) {
604: autoAdjustRange();
605: }
606: }
607:
608:
616: public boolean isHiddenValue(long millis) {
617: return (!this.timeline.containsDomainValue(new Date(millis)));
618: }
619:
620:
631: public double valueToJava2D(double value, Rectangle2D area,
632: RectangleEdge edge) {
633:
634: value = this.timeline.toTimelineValue((long) value);
635:
636: DateRange range = (DateRange) getRange();
637: double axisMin = this.timeline.toTimelineValue(range.getLowerDate());
638: double axisMax = this.timeline.toTimelineValue(range.getUpperDate());
639: double result = 0.0;
640: if (RectangleEdge.isTopOrBottom(edge)) {
641: double minX = area.getX();
642: double maxX = area.getMaxX();
643: if (isInverted()) {
644: result = maxX + ((value - axisMin) / (axisMax - axisMin))
645: * (minX - maxX);
646: }
647: else {
648: result = minX + ((value - axisMin) / (axisMax - axisMin))
649: * (maxX - minX);
650: }
651: }
652: else if (RectangleEdge.isLeftOrRight(edge)) {
653: double minY = area.getMinY();
654: double maxY = area.getMaxY();
655: if (isInverted()) {
656: result = minY + (((value - axisMin) / (axisMax - axisMin))
657: * (maxY - minY));
658: }
659: else {
660: result = maxY - (((value - axisMin) / (axisMax - axisMin))
661: * (maxY - minY));
662: }
663: }
664: return result;
665:
666: }
667:
668:
679: public double dateToJava2D(Date date, Rectangle2D area,
680: RectangleEdge edge) {
681: double value = date.getTime();
682: return valueToJava2D(value, area, edge);
683: }
684:
685:
697: public double java2DToValue(double java2DValue, Rectangle2D area,
698: RectangleEdge edge) {
699:
700: DateRange range = (DateRange) getRange();
701: double axisMin = this.timeline.toTimelineValue(range.getLowerDate());
702: double axisMax = this.timeline.toTimelineValue(range.getUpperDate());
703:
704: double min = 0.0;
705: double max = 0.0;
706: if (RectangleEdge.isTopOrBottom(edge)) {
707: min = area.getX();
708: max = area.getMaxX();
709: }
710: else if (RectangleEdge.isLeftOrRight(edge)) {
711: min = area.getMaxY();
712: max = area.getY();
713: }
714:
715: double result;
716: if (isInverted()) {
717: result = axisMax - ((java2DValue - min) / (max - min)
718: * (axisMax - axisMin));
719: }
720: else {
721: result = axisMin + ((java2DValue - min) / (max - min)
722: * (axisMax - axisMin));
723: }
724:
725: return this.timeline.toMillisecond((long) result);
726: }
727:
728:
735: public Date calculateLowestVisibleTickValue(DateTickUnit unit) {
736: return nextStandardDate(getMinimumDate(), unit);
737: }
738:
739:
746: public Date calculateHighestVisibleTickValue(DateTickUnit unit) {
747: return previousStandardDate(getMaximumDate(), unit);
748: }
749:
750:
758: protected Date previousStandardDate(Date date, DateTickUnit unit) {
759:
760: int milliseconds;
761: int seconds;
762: int minutes;
763: int hours;
764: int days;
765: int months;
766: int years;
767:
768: Calendar calendar = Calendar.getInstance(this.timeZone);
769: calendar.setTime(date);
770: int count = unit.getCount();
771: int current = calendar.get(unit.getCalendarField());
772: int value = count * (current / count);
773:
774: switch (unit.getUnit()) {
775:
776: case (DateTickUnit.MILLISECOND) :
777: years = calendar.get(Calendar.YEAR);
778: months = calendar.get(Calendar.MONTH);
779: days = calendar.get(Calendar.DATE);
780: hours = calendar.get(Calendar.HOUR_OF_DAY);
781: minutes = calendar.get(Calendar.MINUTE);
782: seconds = calendar.get(Calendar.SECOND);
783: calendar.set(years, months, days, hours, minutes, seconds);
784: calendar.set(Calendar.MILLISECOND, value);
785: return calendar.getTime();
786:
787: case (DateTickUnit.SECOND) :
788: years = calendar.get(Calendar.YEAR);
789: months = calendar.get(Calendar.MONTH);
790: days = calendar.get(Calendar.DATE);
791: hours = calendar.get(Calendar.HOUR_OF_DAY);
792: minutes = calendar.get(Calendar.MINUTE);
793: if (this.tickMarkPosition == DateTickMarkPosition.START) {
794: milliseconds = 0;
795: }
796: else if (this.tickMarkPosition == DateTickMarkPosition.MIDDLE) {
797: milliseconds = 500;
798: }
799: else {
800: milliseconds = 999;
801: }
802: calendar.set(Calendar.MILLISECOND, milliseconds);
803: calendar.set(years, months, days, hours, minutes, value);
804: return calendar.getTime();
805:
806: case (DateTickUnit.MINUTE) :
807: years = calendar.get(Calendar.YEAR);
808: months = calendar.get(Calendar.MONTH);
809: days = calendar.get(Calendar.DATE);
810: hours = calendar.get(Calendar.HOUR_OF_DAY);
811: if (this.tickMarkPosition == DateTickMarkPosition.START) {
812: seconds = 0;
813: }
814: else if (this.tickMarkPosition == DateTickMarkPosition.MIDDLE) {
815: seconds = 30;
816: }
817: else {
818: seconds = 59;
819: }
820: calendar.clear(Calendar.MILLISECOND);
821: calendar.set(years, months, days, hours, value, seconds);
822: return calendar.getTime();
823:
824: case (DateTickUnit.HOUR) :
825: years = calendar.get(Calendar.YEAR);
826: months = calendar.get(Calendar.MONTH);
827: days = calendar.get(Calendar.DATE);
828: if (this.tickMarkPosition == DateTickMarkPosition.START) {
829: minutes = 0;
830: seconds = 0;
831: }
832: else if (this.tickMarkPosition == DateTickMarkPosition.MIDDLE) {
833: minutes = 30;
834: seconds = 0;
835: }
836: else {
837: minutes = 59;
838: seconds = 59;
839: }
840: calendar.clear(Calendar.MILLISECOND);
841: calendar.set(years, months, days, value, minutes, seconds);
842: return calendar.getTime();
843:
844: case (DateTickUnit.DAY) :
845: years = calendar.get(Calendar.YEAR);
846: months = calendar.get(Calendar.MONTH);
847: if (this.tickMarkPosition == DateTickMarkPosition.START) {
848: hours = 0;
849: minutes = 0;
850: seconds = 0;
851: }
852: else if (this.tickMarkPosition == DateTickMarkPosition.MIDDLE) {
853: hours = 12;
854: minutes = 0;
855: seconds = 0;
856: }
857: else {
858: hours = 23;
859: minutes = 59;
860: seconds = 59;
861: }
862: calendar.clear(Calendar.MILLISECOND);
863: calendar.set(years, months, value, hours, 0, 0);
864:
865:
866: long result = calendar.getTime().getTime();
867: if (result > date.getTime()) {
868: calendar.set(years, months, value - 1, hours, 0, 0);
869: }
870: return calendar.getTime();
871:
872: case (DateTickUnit.MONTH) :
873: years = calendar.get(Calendar.YEAR);
874: calendar.clear(Calendar.MILLISECOND);
875: calendar.set(years, value, 1, 0, 0, 0);
876: Month month = new Month(calendar.getTime());
877: Date standardDate = calculateDateForPosition(
878: month, this.tickMarkPosition
879: );
880: long millis = standardDate.getTime();
881: if (millis > date.getTime()) {
882: month = (Month) month.previous();
883: standardDate = calculateDateForPosition(
884: month, this.tickMarkPosition
885: );
886: }
887: return standardDate;
888:
889: case(DateTickUnit.YEAR) :
890: if (this.tickMarkPosition == DateTickMarkPosition.START) {
891: months = 0;
892: days = 1;
893: }
894: else if (this.tickMarkPosition == DateTickMarkPosition.MIDDLE) {
895: months = 6;
896: days = 1;
897: }
898: else {
899: months = 11;
900: days = 31;
901: }
902: calendar.clear(Calendar.MILLISECOND);
903: calendar.set(value, months, days, 0, 0, 0);
904: return calendar.getTime();
905:
906: default: return null;
907:
908: }
909:
910: }
911:
912:
921: private Date calculateDateForPosition(RegularTimePeriod period,
922: DateTickMarkPosition position) {
923:
924: if (position == null) {
925: throw new IllegalArgumentException("Null 'position' argument.");
926: }
927: Date result = null;
928: if (position == DateTickMarkPosition.START) {
929: result = new Date(period.getFirstMillisecond());
930: }
931: else if (position == DateTickMarkPosition.MIDDLE) {
932: result = new Date(period.getMiddleMillisecond());
933: }
934: else if (position == DateTickMarkPosition.END) {
935: result = new Date(period.getLastMillisecond());
936: }
937: return result;
938:
939: }
940:
941:
950: protected Date nextStandardDate(Date date, DateTickUnit unit) {
951:
952: Date previous = previousStandardDate(date, unit);
953: Calendar calendar = Calendar.getInstance();
954: calendar.setTime(previous);
955: calendar.add(unit.getCalendarField(), unit.getCount());
956: return calendar.getTime();
957:
958: }
959:
960:
969: public static TickUnitSource createStandardDateTickUnits() {
970: return createStandardDateTickUnits(TimeZone.getDefault());
971: }
972:
973:
984: public static TickUnitSource createStandardDateTickUnits(TimeZone zone) {
985:
986: if (zone == null) {
987: throw new IllegalArgumentException("Null 'zone' argument.");
988: }
989: TickUnits units = new TickUnits();
990:
991:
992: DateFormat f1 = new SimpleDateFormat("HH:mm:ss.SSS");
993: DateFormat f2 = new SimpleDateFormat("HH:mm:ss");
994: DateFormat f3 = new SimpleDateFormat("HH:mm");
995: DateFormat f4 = new SimpleDateFormat("d-MMM, HH:mm");
996: DateFormat f5 = new SimpleDateFormat("d-MMM");
997: DateFormat f6 = new SimpleDateFormat("MMM-yyyy");
998: DateFormat f7 = new SimpleDateFormat("yyyy");
999:
1000: f1.setTimeZone(zone);
1001: f2.setTimeZone(zone);
1002: f3.setTimeZone(zone);
1003: f4.setTimeZone(zone);
1004: f5.setTimeZone(zone);
1005: f6.setTimeZone(zone);
1006: f7.setTimeZone(zone);
1007:
1008:
1009: units.add(new DateTickUnit(DateTickUnit.MILLISECOND, 1, f1));
1010: units.add(
1011: new DateTickUnit(
1012: DateTickUnit.MILLISECOND, 5, DateTickUnit.MILLISECOND, 1, f1
1013: )
1014: );
1015: units.add(
1016: new DateTickUnit(
1017: DateTickUnit.MILLISECOND, 10, DateTickUnit.MILLISECOND, 1, f1
1018: )
1019: );
1020: units.add(
1021: new DateTickUnit(
1022: DateTickUnit.MILLISECOND, 25, DateTickUnit.MILLISECOND, 5, f1
1023: )
1024: );
1025: units.add(
1026: new DateTickUnit(
1027: DateTickUnit.MILLISECOND, 50, DateTickUnit.MILLISECOND, 10, f1
1028: )
1029: );
1030: units.add(
1031: new DateTickUnit(
1032: DateTickUnit.MILLISECOND, 100, DateTickUnit.MILLISECOND, 10, f1
1033: )
1034: );
1035: units.add(
1036: new DateTickUnit(
1037: DateTickUnit.MILLISECOND, 250, DateTickUnit.MILLISECOND, 10, f1
1038: )
1039: );
1040: units.add(
1041: new DateTickUnit(
1042: DateTickUnit.MILLISECOND, 500, DateTickUnit.MILLISECOND, 50, f1
1043: )
1044: );
1045:
1046:
1047: units.add(
1048: new DateTickUnit(
1049: DateTickUnit.SECOND, 1, DateTickUnit.MILLISECOND, 50, f2
1050: )
1051: );
1052: units.add(
1053: new DateTickUnit(
1054: DateTickUnit.SECOND, 5, DateTickUnit.SECOND, 1, f2
1055: )
1056: );
1057: units.add(
1058: new DateTickUnit(
1059: DateTickUnit.SECOND, 10, DateTickUnit.SECOND, 1, f2
1060: )
1061: );
1062: units.add(
1063: new DateTickUnit(
1064: DateTickUnit.SECOND, 30, DateTickUnit.SECOND, 5, f2
1065: )
1066: );
1067:
1068:
1069: units.add(
1070: new DateTickUnit(DateTickUnit.MINUTE, 1, DateTickUnit.SECOND, 5, f3)
1071: );
1072: units.add(
1073: new DateTickUnit(
1074: DateTickUnit.MINUTE, 2, DateTickUnit.SECOND, 10, f3
1075: )
1076: );
1077: units.add(
1078: new DateTickUnit(DateTickUnit.MINUTE, 5, DateTickUnit.MINUTE, 1, f3)
1079: );
1080: units.add(
1081: new DateTickUnit(
1082: DateTickUnit.MINUTE, 10, DateTickUnit.MINUTE, 1, f3
1083: )
1084: );
1085: units.add(
1086: new DateTickUnit(
1087: DateTickUnit.MINUTE, 15, DateTickUnit.MINUTE, 5, f3
1088: )
1089: );
1090: units.add(
1091: new DateTickUnit(
1092: DateTickUnit.MINUTE, 20, DateTickUnit.MINUTE, 5, f3
1093: )
1094: );
1095: units.add(
1096: new DateTickUnit(
1097: DateTickUnit.MINUTE, 30, DateTickUnit.MINUTE, 5, f3
1098: )
1099: );
1100:
1101:
1102: units.add(
1103: new DateTickUnit(DateTickUnit.HOUR, 1, DateTickUnit.MINUTE, 5, f3)
1104: );
1105: units.add(
1106: new DateTickUnit(DateTickUnit.HOUR, 2, DateTickUnit.MINUTE, 10, f3)
1107: );
1108: units.add(
1109: new DateTickUnit(DateTickUnit.HOUR, 4, DateTickUnit.MINUTE, 30, f3)
1110: );
1111: units.add(
1112: new DateTickUnit(DateTickUnit.HOUR, 6, DateTickUnit.HOUR, 1, f3)
1113: );
1114: units.add(
1115: new DateTickUnit(DateTickUnit.HOUR, 12, DateTickUnit.HOUR, 1, f4)
1116: );
1117:
1118:
1119: units.add(
1120: new DateTickUnit(DateTickUnit.DAY, 1, DateTickUnit.HOUR, 1, f5)
1121: );
1122: units.add(
1123: new DateTickUnit(DateTickUnit.DAY, 2, DateTickUnit.HOUR, 1, f5)
1124: );
1125: units.add(
1126: new DateTickUnit(DateTickUnit.DAY, 7, DateTickUnit.DAY, 1, f5)
1127: );
1128: units.add(
1129: new DateTickUnit(DateTickUnit.DAY, 15, DateTickUnit.DAY, 1, f5)
1130: );
1131:
1132:
1133: units.add(
1134: new DateTickUnit(DateTickUnit.MONTH, 1, DateTickUnit.DAY, 1, f6)
1135: );
1136: units.add(
1137: new DateTickUnit(DateTickUnit.MONTH, 2, DateTickUnit.DAY, 1, f6)
1138: );
1139: units.add(
1140: new DateTickUnit(DateTickUnit.MONTH, 3, DateTickUnit.MONTH, 1, f6)
1141: );
1142: units.add(
1143: new DateTickUnit(DateTickUnit.MONTH, 4, DateTickUnit.MONTH, 1, f6)
1144: );
1145: units.add(
1146: new DateTickUnit(DateTickUnit.MONTH, 6, DateTickUnit.MONTH, 1, f6)
1147: );
1148:
1149:
1150: units.add(
1151: new DateTickUnit(DateTickUnit.YEAR, 1, DateTickUnit.MONTH, 1, f7)
1152: );
1153: units.add(
1154: new DateTickUnit(DateTickUnit.YEAR, 2, DateTickUnit.MONTH, 3, f7)
1155: );
1156: units.add(
1157: new DateTickUnit(DateTickUnit.YEAR, 5, DateTickUnit.YEAR, 1, f7)
1158: );
1159: units.add(
1160: new DateTickUnit(DateTickUnit.YEAR, 10, DateTickUnit.YEAR, 1, f7)
1161: );
1162: units.add(
1163: new DateTickUnit(DateTickUnit.YEAR, 25, DateTickUnit.YEAR, 5, f7)
1164: );
1165: units.add(
1166: new DateTickUnit(DateTickUnit.YEAR, 50, DateTickUnit.YEAR, 10, f7)
1167: );
1168: units.add(
1169: new DateTickUnit(DateTickUnit.YEAR, 100, DateTickUnit.YEAR, 20, f7)
1170: );
1171:
1172: return units;
1173:
1174: }
1175:
1176:
1179: protected void autoAdjustRange() {
1180:
1181: Plot plot = getPlot();
1182:
1183: if (plot == null) {
1184: return;
1185: }
1186:
1187: if (plot instanceof ValueAxisPlot) {
1188: ValueAxisPlot vap = (ValueAxisPlot) plot;
1189:
1190: Range r = vap.getDataRange(this);
1191: if (r == null) {
1192: if (this.timeline instanceof SegmentedTimeline) {
1193:
1194: r = new DateRange(
1195: ((SegmentedTimeline) this.timeline).getStartTime(),
1196: ((SegmentedTimeline) this.timeline).getStartTime() + 1
1197: );
1198: }
1199: else {
1200: r = new DateRange();
1201: }
1202: }
1203:
1204: long upper = this.timeline.toTimelineValue(
1205: (long) r.getUpperBound()
1206: );
1207: long lower;
1208: long fixedAutoRange = (long) getFixedAutoRange();
1209: if (fixedAutoRange > 0.0) {
1210: lower = upper - fixedAutoRange;
1211: }
1212: else {
1213: lower = this.timeline.toTimelineValue((long) r.getLowerBound());
1214: double range = upper - lower;
1215: long minRange = (long) getAutoRangeMinimumSize();
1216: if (range < minRange) {
1217: long expand = (long) (minRange - range) / 2;
1218: upper = upper + expand;
1219: lower = lower - expand;
1220: }
1221: upper = upper + (long) (range * getUpperMargin());
1222: lower = lower - (long) (range * getLowerMargin());
1223: }
1224:
1225: upper = this.timeline.toMillisecond(upper);
1226: lower = this.timeline.toMillisecond(lower);
1227: DateRange dr = new DateRange(new Date(lower), new Date(upper));
1228: setRange(dr, false, false);
1229: }
1230:
1231: }
1232:
1233:
1242: protected void selectAutoTickUnit(Graphics2D g2,
1243: Rectangle2D dataArea,
1244: RectangleEdge edge) {
1245:
1246: if (RectangleEdge.isTopOrBottom(edge)) {
1247: selectHorizontalAutoTickUnit(g2, dataArea, edge);
1248: }
1249: else if (RectangleEdge.isLeftOrRight(edge)) {
1250: selectVerticalAutoTickUnit(g2, dataArea, edge);
1251: }
1252:
1253: }
1254:
1255:
1264: protected void selectHorizontalAutoTickUnit(Graphics2D g2,
1265: Rectangle2D dataArea,
1266: RectangleEdge edge) {
1267:
1268: long shift = 0;
1269: if (this.timeline instanceof SegmentedTimeline) {
1270: shift = ((SegmentedTimeline) this.timeline).getStartTime();
1271: }
1272: double zero = valueToJava2D(shift + 0.0, dataArea, edge);
1273: double tickLabelWidth
1274: = estimateMaximumTickLabelWidth(g2, getTickUnit());
1275:
1276:
1277: TickUnitSource tickUnits = getStandardTickUnits();
1278: TickUnit unit1 = tickUnits.getCeilingTickUnit(getTickUnit());
1279: double x1 = valueToJava2D(shift + unit1.getSize(), dataArea, edge);
1280: double unit1Width = Math.abs(x1 - zero);
1281:
1282:
1283: double guess = (tickLabelWidth / unit1Width) * unit1.getSize();
1284: DateTickUnit unit2 = (DateTickUnit) tickUnits.getCeilingTickUnit(guess);
1285: double x2 = valueToJava2D(shift + unit2.getSize(), dataArea, edge);
1286: double unit2Width = Math.abs(x2 - zero);
1287: tickLabelWidth = estimateMaximumTickLabelWidth(g2, unit2);
1288: if (tickLabelWidth > unit2Width) {
1289: unit2 = (DateTickUnit) tickUnits.getLargerTickUnit(unit2);
1290: }
1291: setTickUnit(unit2, false, false);
1292: }
1293:
1294:
1303: protected void selectVerticalAutoTickUnit(Graphics2D g2,
1304: Rectangle2D dataArea,
1305: RectangleEdge edge) {
1306:
1307:
1308: TickUnitSource tickUnits = getStandardTickUnits();
1309: double zero = valueToJava2D(0.0, dataArea, edge);
1310:
1311:
1312: double estimate1 = getRange().getLength() / 10.0;
1313: DateTickUnit candidate1
1314: = (DateTickUnit) tickUnits.getCeilingTickUnit(estimate1);
1315: double labelHeight1 = estimateMaximumTickLabelHeight(g2, candidate1);
1316: double y1 = valueToJava2D(candidate1.getSize(), dataArea, edge);
1317: double candidate1UnitHeight = Math.abs(y1 - zero);
1318:
1319:
1320: double estimate2
1321: = (labelHeight1 / candidate1UnitHeight) * candidate1.getSize();
1322: DateTickUnit candidate2
1323: = (DateTickUnit) tickUnits.getCeilingTickUnit(estimate2);
1324: double labelHeight2 = estimateMaximumTickLabelHeight(g2, candidate2);
1325: double y2 = valueToJava2D(candidate2.getSize(), dataArea, edge);
1326: double unit2Height = Math.abs(y2 - zero);
1327:
1328:
1329: DateTickUnit finalUnit;
1330: if (labelHeight2 < unit2Height) {
1331: finalUnit = candidate2;
1332: }
1333: else {
1334: finalUnit = (DateTickUnit) tickUnits.getLargerTickUnit(candidate2);
1335: }
1336: setTickUnit(finalUnit, false, false);
1337:
1338: }
1339:
1340:
1353: private double estimateMaximumTickLabelWidth(Graphics2D g2,
1354: DateTickUnit unit) {
1355:
1356: RectangleInsets tickLabelInsets = getTickLabelInsets();
1357: double result = tickLabelInsets.getLeft() + tickLabelInsets.getRight();
1358:
1359: Font tickLabelFont = getTickLabelFont();
1360: FontRenderContext frc = g2.getFontRenderContext();
1361: LineMetrics lm = tickLabelFont.getLineMetrics("ABCxyz", frc);
1362: if (isVerticalTickLabels()) {
1363:
1364:
1365: result += lm.getHeight();
1366: }
1367: else {
1368:
1369: DateRange range = (DateRange) getRange();
1370: Date lower = range.getLowerDate();
1371: Date upper = range.getUpperDate();
1372: String lowerStr = null;
1373: String upperStr = null;
1374: DateFormat formatter = getDateFormatOverride();
1375: if (formatter != null) {
1376: lowerStr = formatter.format(lower);
1377: upperStr = formatter.format(upper);
1378: }
1379: else {
1380: lowerStr = unit.dateToString(lower);
1381: upperStr = unit.dateToString(upper);
1382: }
1383: FontMetrics fm = g2.getFontMetrics(tickLabelFont);
1384: double w1 = fm.stringWidth(lowerStr);
1385: double w2 = fm.stringWidth(upperStr);
1386: result += Math.max(w1, w2);
1387: }
1388:
1389: return result;
1390:
1391: }
1392:
1393:
1406: private double estimateMaximumTickLabelHeight(Graphics2D g2,
1407: DateTickUnit unit) {
1408:
1409: RectangleInsets tickLabelInsets = getTickLabelInsets();
1410: double result = tickLabelInsets.getTop() + tickLabelInsets.getBottom();
1411:
1412: Font tickLabelFont = getTickLabelFont();
1413: FontRenderContext frc = g2.getFontRenderContext();
1414: LineMetrics lm = tickLabelFont.getLineMetrics("ABCxyz", frc);
1415: if (!isVerticalTickLabels()) {
1416:
1417:
1418: result += lm.getHeight();
1419: }
1420: else {
1421:
1422: DateRange range = (DateRange) getRange();
1423: Date lower = range.getLowerDate();
1424: Date upper = range.getUpperDate();
1425: String lowerStr = null;
1426: String upperStr = null;
1427: DateFormat formatter = getDateFormatOverride();
1428: if (formatter != null) {
1429: lowerStr = formatter.format(lower);
1430: upperStr = formatter.format(upper);
1431: }
1432: else {
1433: lowerStr = unit.dateToString(lower);
1434: upperStr = unit.dateToString(upper);
1435: }
1436: FontMetrics fm = g2.getFontMetrics(tickLabelFont);
1437: double w1 = fm.stringWidth(lowerStr);
1438: double w2 = fm.stringWidth(upperStr);
1439: result += Math.max(w1, w2);
1440: }
1441:
1442: return result;
1443:
1444: }
1445:
1446:
1457: public List refreshTicks(Graphics2D g2,
1458: AxisState state,
1459: Rectangle2D dataArea,
1460: RectangleEdge edge) {
1461:
1462: List result = null;
1463: if (RectangleEdge.isTopOrBottom(edge)) {
1464: result = refreshTicksHorizontal(g2, dataArea, edge);
1465: }
1466: else if (RectangleEdge.isLeftOrRight(edge)) {
1467: result = refreshTicksVertical(g2, dataArea, edge);
1468: }
1469: return result;
1470:
1471: }
1472:
1473:
1482: protected List refreshTicksHorizontal(Graphics2D g2,
1483: Rectangle2D dataArea,
1484: RectangleEdge edge) {
1485:
1486: List result = new java.util.ArrayList();
1487:
1488: Font tickLabelFont = getTickLabelFont();
1489: g2.setFont(tickLabelFont);
1490:
1491: if (isAutoTickUnitSelection()) {
1492: selectAutoTickUnit(g2, dataArea, edge);
1493: }
1494:
1495: DateTickUnit unit = getTickUnit();
1496: Date tickDate = calculateLowestVisibleTickValue(unit);
1497: Date upperDate = getMaximumDate();
1498:
1499: while (tickDate.before(upperDate)) {
1500:
1501: if (!isHiddenValue(tickDate.getTime())) {
1502:
1503: String tickLabel;
1504: DateFormat formatter = getDateFormatOverride();
1505: if (formatter != null) {
1506: tickLabel = formatter.format(tickDate);
1507: }
1508: else {
1509: tickLabel = this.tickUnit.dateToString(tickDate);
1510: }
1511: TextAnchor anchor = null;
1512: TextAnchor rotationAnchor = null;
1513: double angle = 0.0;
1514: if (isVerticalTickLabels()) {
1515: anchor = TextAnchor.CENTER_RIGHT;
1516: rotationAnchor = TextAnchor.CENTER_RIGHT;
1517: if (edge == RectangleEdge.TOP) {
1518: angle = Math.PI / 2.0;
1519: }
1520: else {
1521: angle = -Math.PI / 2.0;
1522: }
1523: }
1524: else {
1525: if (edge == RectangleEdge.TOP) {
1526: anchor = TextAnchor.BOTTOM_CENTER;
1527: rotationAnchor = TextAnchor.BOTTOM_CENTER;
1528: }
1529: else {
1530: anchor = TextAnchor.TOP_CENTER;
1531: rotationAnchor = TextAnchor.TOP_CENTER;
1532: }
1533: }
1534:
1535: Tick tick = new DateTick(
1536: tickDate, tickLabel, anchor, rotationAnchor, angle
1537: );
1538: result.add(tick);
1539: tickDate = unit.addToDate(tickDate);
1540: }
1541: else {
1542: tickDate = unit.rollDate(tickDate);
1543: continue;
1544: }
1545:
1546:
1547: switch (unit.getUnit()) {
1548:
1549: case (DateTickUnit.MILLISECOND) :
1550: case (DateTickUnit.SECOND) :
1551: case (DateTickUnit.MINUTE) :
1552: case (DateTickUnit.HOUR) :
1553: case (DateTickUnit.DAY) :
1554: break;
1555: case (DateTickUnit.MONTH) :
1556: tickDate = calculateDateForPosition(
1557: new Month(tickDate), this.tickMarkPosition
1558: );
1559: break;
1560: case(DateTickUnit.YEAR) :
1561: tickDate = calculateDateForPosition(
1562: new Year(tickDate), this.tickMarkPosition
1563: );
1564: break;
1565:
1566: default: break;
1567:
1568: }
1569:
1570: }
1571: return result;
1572:
1573: }
1574:
1575:
1584: protected List refreshTicksVertical(Graphics2D g2,
1585: Rectangle2D dataArea,
1586: RectangleEdge edge) {
1587:
1588: List result = new java.util.ArrayList();
1589:
1590: Font tickLabelFont = getTickLabelFont();
1591: g2.setFont(tickLabelFont);
1592:
1593: if (isAutoTickUnitSelection()) {
1594: selectAutoTickUnit(g2, dataArea, edge);
1595: }
1596: DateTickUnit unit = getTickUnit();
1597: Date tickDate = calculateLowestVisibleTickValue(unit);
1598:
1599: Date upperDate = getMaximumDate();
1600: while (tickDate.before(upperDate)) {
1601:
1602: if (!isHiddenValue(tickDate.getTime())) {
1603:
1604: String tickLabel;
1605: DateFormat formatter = getDateFormatOverride();
1606: if (formatter != null) {
1607: tickLabel = formatter.format(tickDate);
1608: }
1609: else {
1610: tickLabel = this.tickUnit.dateToString(tickDate);
1611: }
1612: TextAnchor anchor = null;
1613: TextAnchor rotationAnchor = null;
1614: double angle = 0.0;
1615: if (isVerticalTickLabels()) {
1616: anchor = TextAnchor.BOTTOM_CENTER;
1617: rotationAnchor = TextAnchor.BOTTOM_CENTER;
1618: if (edge == RectangleEdge.LEFT) {
1619: angle = -Math.PI / 2.0;
1620: }
1621: else {
1622: angle = Math.PI / 2.0;
1623: }
1624: }
1625: else {
1626: if (edge == RectangleEdge.LEFT) {
1627: anchor = TextAnchor.CENTER_RIGHT;
1628: rotationAnchor = TextAnchor.CENTER_RIGHT;
1629: }
1630: else {
1631: anchor = TextAnchor.CENTER_LEFT;
1632: rotationAnchor = TextAnchor.CENTER_LEFT;
1633: }
1634: }
1635:
1636: Tick tick = new DateTick(
1637: tickDate, tickLabel, anchor, rotationAnchor, angle
1638: );
1639: result.add(tick);
1640: tickDate = unit.addToDate(tickDate);
1641: }
1642: else {
1643: tickDate = unit.rollDate(tickDate);
1644: }
1645: }
1646: return result;
1647: }
1648:
1649:
1665: public AxisState draw(Graphics2D g2,
1666: double cursor,
1667: Rectangle2D plotArea,
1668: Rectangle2D dataArea,
1669: RectangleEdge edge,
1670: PlotRenderingInfo plotState) {
1671:
1672:
1673: if (!isVisible()) {
1674: AxisState state = new AxisState(cursor);
1675:
1676:
1677: List ticks = refreshTicks(g2, state, dataArea, edge);
1678: state.setTicks(ticks);
1679: return state;
1680: }
1681:
1682:
1683: AxisState state = drawTickMarksAndLabels(
1684: g2, cursor, plotArea, dataArea, edge
1685: );
1686:
1687:
1688:
1689: state = drawLabel(getLabel(), g2, plotArea, dataArea, edge, state);
1690:
1691: return state;
1692:
1693: }
1694:
1695:
1701: public void zoomRange(double lowerPercent, double upperPercent) {
1702: double start = this.timeline.toTimelineValue(
1703: (long) getRange().getLowerBound()
1704: );
1705: double length = (this.timeline.toTimelineValue(
1706: (long) getRange().getUpperBound())
1707: - this.timeline.toTimelineValue(
1708: (long) getRange().getLowerBound()
1709: ));
1710: Range adjusted = null;
1711: if (isInverted()) {
1712: adjusted = new DateRange(
1713: this.timeline.toMillisecond(
1714: (long) (start + (length * (1 - upperPercent)))
1715: ),
1716: this.timeline.toMillisecond(
1717: (long) (start + (length * (1 - lowerPercent)))
1718: )
1719: );
1720: }
1721: else {
1722: adjusted = new DateRange(this.timeline.toMillisecond(
1723: (long) (start + length * lowerPercent)),
1724: this.timeline.toMillisecond(
1725: (long) (start + length * upperPercent)
1726: )
1727: );
1728: }
1729: setRange(adjusted);
1730: }
1731:
1732:
1739: public boolean equals(Object obj) {
1740: if (obj == this) {
1741: return true;
1742: }
1743: if (!(obj instanceof DateAxis)) {
1744: return false;
1745: }
1746: DateAxis that = (DateAxis) obj;
1747: if (!ObjectUtilities.equal(this.tickUnit, that.tickUnit)) {
1748: return false;
1749: }
1750: if (!ObjectUtilities.equal(this.dateFormatOverride,
1751: that.dateFormatOverride)) {
1752: return false;
1753: }
1754: if (!ObjectUtilities.equal(this.tickMarkPosition,
1755: that.tickMarkPosition)) {
1756: return false;
1757: }
1758: if (!ObjectUtilities.equal(this.timeline, that.timeline)) {
1759: return false;
1760: }
1761: if (!super.equals(obj)) {
1762: return false;
1763: }
1764: return true;
1765: }
1766:
1767:
1772: public int hashCode() {
1773: if (getLabel() != null) {
1774: return getLabel().hashCode();
1775: }
1776: else {
1777: return 0;
1778: }
1779: }
1780:
1781:
1789: public Object clone() throws CloneNotSupportedException {
1790:
1791: DateAxis clone = (DateAxis) super.clone();
1792:
1793:
1794: if (this.dateFormatOverride != null) {
1795: clone.dateFormatOverride
1796: = (DateFormat) this.dateFormatOverride.clone();
1797: }
1798:
1799:
1800: return clone;
1801:
1802: }
1803:
1804: }