1:
116:
117: package ;
118:
119: import ;
120: import ;
121: import ;
122: import ;
123: import ;
124: import ;
125: import ;
126: import ;
127: import ;
128: import ;
129: import ;
130: import ;
131: import ;
132:
133: import ;
134: import ;
135: import ;
136: import ;
137: import ;
138: import ;
139: import ;
140: import ;
141: import ;
142: import ;
143: import ;
144: import ;
145: import ;
146:
147:
160: public class DateAxis extends ValueAxis implements Cloneable, Serializable {
161:
162:
163: private static final long serialVersionUID = -1013460999649007604L;
164:
165:
166: public static final DateRange DEFAULT_DATE_RANGE = new DateRange();
167:
168:
169: public static final double
170: DEFAULT_AUTO_RANGE_MINIMUM_SIZE_IN_MILLISECONDS = 2.0;
171:
172:
173: public static final DateTickUnit DEFAULT_DATE_TICK_UNIT
174: = new DateTickUnit(DateTickUnit.DAY, 1, new SimpleDateFormat());
175:
176:
177: public static final Date DEFAULT_ANCHOR_DATE = new Date();
178:
179:
180: private DateTickUnit tickUnit;
181:
182:
183: private DateFormat dateFormatOverride;
184:
185:
189: private DateTickMarkPosition tickMarkPosition = DateTickMarkPosition.START;
190:
191:
195: private static class DefaultTimeline implements Timeline, Serializable {
196:
197:
204: public long toTimelineValue(long millisecond) {
205: return millisecond;
206: }
207:
208:
215: public long toTimelineValue(Date date) {
216: return date.getTime();
217: }
218:
219:
227: public long toMillisecond(long value) {
228: return value;
229: }
230:
231:
239: public boolean containsDomainValue(long millisecond) {
240: return true;
241: }
242:
243:
251: public boolean containsDomainValue(Date date) {
252: return true;
253: }
254:
255:
264: public boolean containsDomainRange(long from, long to) {
265: return true;
266: }
267:
268:
277: public boolean containsDomainRange(Date from, Date to) {
278: return true;
279: }
280:
281:
288: public boolean equals(Object object) {
289: if (object == null) {
290: return false;
291: }
292: if (object == this) {
293: return true;
294: }
295: if (object instanceof DefaultTimeline) {
296: return true;
297: }
298: return false;
299: }
300: }
301:
302:
303: private static final Timeline DEFAULT_TIMELINE = new DefaultTimeline();
304:
305:
306: private TimeZone timeZone;
307:
308:
309: private Timeline timeline;
310:
311:
314: public DateAxis() {
315: this(null);
316: }
317:
318:
323: public DateAxis(String label) {
324: this(label, TimeZone.getDefault());
325: }
326:
327:
337: public DateAxis(String label, TimeZone zone) {
338: super(label, DateAxis.createStandardDateTickUnits(zone));
339: setTickUnit(DateAxis.DEFAULT_DATE_TICK_UNIT, false, false);
340: setAutoRangeMinimumSize(
341: DEFAULT_AUTO_RANGE_MINIMUM_SIZE_IN_MILLISECONDS);
342: setRange(DEFAULT_DATE_RANGE, false, false);
343: this.dateFormatOverride = null;
344: this.timeZone = zone;
345: this.timeline = DEFAULT_TIMELINE;
346: }
347:
348:
356: public TimeZone getTimeZone() {
357: return this.timeZone;
358: }
359:
360:
369: public void setTimeZone(TimeZone zone) {
370: if (!this.timeZone.equals(zone)) {
371: this.timeZone = zone;
372: setStandardTickUnits(createStandardDateTickUnits(zone));
373: notifyListeners(new AxisChangeEvent(this));
374: }
375: }
376:
377:
382: public Timeline getTimeline() {
383: return this.timeline;
384: }
385:
386:
394: public void setTimeline(Timeline timeline) {
395: if (this.timeline != timeline) {
396: this.timeline = timeline;
397: notifyListeners(new AxisChangeEvent(this));
398: }
399: }
400:
401:
414: public DateTickUnit getTickUnit() {
415: return this.tickUnit;
416: }
417:
418:
428: public void setTickUnit(DateTickUnit unit) {
429: setTickUnit(unit, true, true);
430: }
431:
432:
441: public void setTickUnit(DateTickUnit unit, boolean notify,
442: boolean turnOffAutoSelection) {
443:
444: this.tickUnit = unit;
445: if (turnOffAutoSelection) {
446: setAutoTickUnitSelection(false, false);
447: }
448: if (notify) {
449: notifyListeners(new AxisChangeEvent(this));
450: }
451:
452: }
453:
454:
460: public DateFormat getDateFormatOverride() {
461: return this.dateFormatOverride;
462: }
463:
464:
470: public void setDateFormatOverride(DateFormat formatter) {
471: this.dateFormatOverride = formatter;
472: notifyListeners(new AxisChangeEvent(this));
473: }
474:
475:
482: public void setRange(Range range) {
483: setRange(range, true, true);
484: }
485:
486:
497: public void setRange(Range range, boolean turnOffAutoRange,
498: boolean notify) {
499: if (range == null) {
500: throw new IllegalArgumentException("Null 'range' argument.");
501: }
502:
503:
504: if (!(range instanceof DateRange)) {
505: range = new DateRange(range);
506: }
507: super.setRange(range, turnOffAutoRange, notify);
508: }
509:
510:
517: public void setRange(Date lower, Date upper) {
518: if (lower.getTime() >= upper.getTime()) {
519: throw new IllegalArgumentException("Requires 'lower' < 'upper'.");
520: }
521: setRange(new DateRange(lower, upper));
522: }
523:
524:
531: public void setRange(double lower, double upper) {
532: if (lower >= upper) {
533: throw new IllegalArgumentException("Requires 'lower' < 'upper'.");
534: }
535: setRange(new DateRange(lower, upper));
536: }
537:
538:
543: public Date getMinimumDate() {
544: Date result = null;
545: Range range = getRange();
546: if (range instanceof DateRange) {
547: DateRange r = (DateRange) range;
548: result = r.getLowerDate();
549: }
550: else {
551: result = new Date((long) range.getLowerBound());
552: }
553: return result;
554: }
555:
556:
562: public void setMinimumDate(Date date) {
563: setRange(new DateRange(date, getMaximumDate()), true, false);
564: notifyListeners(new AxisChangeEvent(this));
565: }
566:
567:
572: public Date getMaximumDate() {
573:
574: Date result = null;
575: Range range = getRange();
576: if (range instanceof DateRange) {
577: DateRange r = (DateRange) range;
578: result = r.getUpperDate();
579: }
580: else {
581: result = new Date((long) range.getUpperBound());
582: }
583: return result;
584:
585: }
586:
587:
593: public void setMaximumDate(Date maximumDate) {
594: setRange(new DateRange(getMinimumDate(), maximumDate), true, false);
595: notifyListeners(new AxisChangeEvent(this));
596: }
597:
598:
603: public DateTickMarkPosition getTickMarkPosition() {
604: return this.tickMarkPosition;
605: }
606:
607:
613: public void setTickMarkPosition(DateTickMarkPosition position) {
614: if (position == null) {
615: throw new IllegalArgumentException("Null 'position' argument.");
616: }
617: this.tickMarkPosition = position;
618: notifyListeners(new AxisChangeEvent(this));
619: }
620:
621:
625: public void configure() {
626: if (isAutoRange()) {
627: autoAdjustRange();
628: }
629: }
630:
631:
639: public boolean isHiddenValue(long millis) {
640: return (!this.timeline.containsDomainValue(new Date(millis)));
641: }
642:
643:
654: public double valueToJava2D(double value, Rectangle2D area,
655: RectangleEdge edge) {
656:
657: value = this.timeline.toTimelineValue((long) value);
658:
659: DateRange range = (DateRange) getRange();
660: double axisMin = this.timeline.toTimelineValue(range.getLowerDate());
661: double axisMax = this.timeline.toTimelineValue(range.getUpperDate());
662: double result = 0.0;
663: if (RectangleEdge.isTopOrBottom(edge)) {
664: double minX = area.getX();
665: double maxX = area.getMaxX();
666: if (isInverted()) {
667: result = maxX + ((value - axisMin) / (axisMax - axisMin))
668: * (minX - maxX);
669: }
670: else {
671: result = minX + ((value - axisMin) / (axisMax - axisMin))
672: * (maxX - minX);
673: }
674: }
675: else if (RectangleEdge.isLeftOrRight(edge)) {
676: double minY = area.getMinY();
677: double maxY = area.getMaxY();
678: if (isInverted()) {
679: result = minY + (((value - axisMin) / (axisMax - axisMin))
680: * (maxY - minY));
681: }
682: else {
683: result = maxY - (((value - axisMin) / (axisMax - axisMin))
684: * (maxY - minY));
685: }
686: }
687: return result;
688:
689: }
690:
691:
702: public double dateToJava2D(Date date, Rectangle2D area,
703: RectangleEdge edge) {
704: double value = date.getTime();
705: return valueToJava2D(value, area, edge);
706: }
707:
708:
720: public double java2DToValue(double java2DValue, Rectangle2D area,
721: RectangleEdge edge) {
722:
723: DateRange range = (DateRange) getRange();
724: double axisMin = this.timeline.toTimelineValue(range.getLowerDate());
725: double axisMax = this.timeline.toTimelineValue(range.getUpperDate());
726:
727: double min = 0.0;
728: double max = 0.0;
729: if (RectangleEdge.isTopOrBottom(edge)) {
730: min = area.getX();
731: max = area.getMaxX();
732: }
733: else if (RectangleEdge.isLeftOrRight(edge)) {
734: min = area.getMaxY();
735: max = area.getY();
736: }
737:
738: double result;
739: if (isInverted()) {
740: result = axisMax - ((java2DValue - min) / (max - min)
741: * (axisMax - axisMin));
742: }
743: else {
744: result = axisMin + ((java2DValue - min) / (max - min)
745: * (axisMax - axisMin));
746: }
747:
748: return this.timeline.toMillisecond((long) result);
749: }
750:
751:
758: public Date calculateLowestVisibleTickValue(DateTickUnit unit) {
759: return nextStandardDate(getMinimumDate(), unit);
760: }
761:
762:
769: public Date calculateHighestVisibleTickValue(DateTickUnit unit) {
770: return previousStandardDate(getMaximumDate(), unit);
771: }
772:
773:
781: protected Date previousStandardDate(Date date, DateTickUnit unit) {
782:
783: int milliseconds;
784: int seconds;
785: int minutes;
786: int hours;
787: int days;
788: int months;
789: int years;
790:
791: Calendar calendar = Calendar.getInstance(this.timeZone);
792: calendar.setTime(date);
793: int count = unit.getCount();
794: int current = calendar.get(unit.getCalendarField());
795: int value = count * (current / count);
796:
797: switch (unit.getUnit()) {
798:
799: case (DateTickUnit.MILLISECOND) :
800: years = calendar.get(Calendar.YEAR);
801: months = calendar.get(Calendar.MONTH);
802: days = calendar.get(Calendar.DATE);
803: hours = calendar.get(Calendar.HOUR_OF_DAY);
804: minutes = calendar.get(Calendar.MINUTE);
805: seconds = calendar.get(Calendar.SECOND);
806: calendar.set(years, months, days, hours, minutes, seconds);
807: calendar.set(Calendar.MILLISECOND, value);
808: return calendar.getTime();
809:
810: case (DateTickUnit.SECOND) :
811: years = calendar.get(Calendar.YEAR);
812: months = calendar.get(Calendar.MONTH);
813: days = calendar.get(Calendar.DATE);
814: hours = calendar.get(Calendar.HOUR_OF_DAY);
815: minutes = calendar.get(Calendar.MINUTE);
816: if (this.tickMarkPosition == DateTickMarkPosition.START) {
817: milliseconds = 0;
818: }
819: else if (this.tickMarkPosition == DateTickMarkPosition.MIDDLE) {
820: milliseconds = 500;
821: }
822: else {
823: milliseconds = 999;
824: }
825: calendar.set(Calendar.MILLISECOND, milliseconds);
826: calendar.set(years, months, days, hours, minutes, value);
827: return calendar.getTime();
828:
829: case (DateTickUnit.MINUTE) :
830: years = calendar.get(Calendar.YEAR);
831: months = calendar.get(Calendar.MONTH);
832: days = calendar.get(Calendar.DATE);
833: hours = calendar.get(Calendar.HOUR_OF_DAY);
834: if (this.tickMarkPosition == DateTickMarkPosition.START) {
835: seconds = 0;
836: }
837: else if (this.tickMarkPosition == DateTickMarkPosition.MIDDLE) {
838: seconds = 30;
839: }
840: else {
841: seconds = 59;
842: }
843: calendar.clear(Calendar.MILLISECOND);
844: calendar.set(years, months, days, hours, value, seconds);
845: Date d0 = calendar.getTime();
846: if (d0.getTime() >= date.getTime()) {
847: calendar.set(Calendar.MINUTE, value - 1);
848: d0 = calendar.getTime();
849: }
850: return d0;
851:
852: case (DateTickUnit.HOUR) :
853: years = calendar.get(Calendar.YEAR);
854: months = calendar.get(Calendar.MONTH);
855: days = calendar.get(Calendar.DATE);
856: if (this.tickMarkPosition == DateTickMarkPosition.START) {
857: minutes = 0;
858: seconds = 0;
859: }
860: else if (this.tickMarkPosition == DateTickMarkPosition.MIDDLE) {
861: minutes = 30;
862: seconds = 0;
863: }
864: else {
865: minutes = 59;
866: seconds = 59;
867: }
868: calendar.clear(Calendar.MILLISECOND);
869: calendar.set(years, months, days, value, minutes, seconds);
870: Date d1 = calendar.getTime();
871: if (d1.getTime() >= date.getTime()) {
872: calendar.set(Calendar.HOUR_OF_DAY, value - 1);
873: d1 = calendar.getTime();
874: }
875: return d1;
876:
877: case (DateTickUnit.DAY) :
878: years = calendar.get(Calendar.YEAR);
879: months = calendar.get(Calendar.MONTH);
880: if (this.tickMarkPosition == DateTickMarkPosition.START) {
881: hours = 0;
882: minutes = 0;
883: seconds = 0;
884: }
885: else if (this.tickMarkPosition == DateTickMarkPosition.MIDDLE) {
886: hours = 12;
887: minutes = 0;
888: seconds = 0;
889: }
890: else {
891: hours = 23;
892: minutes = 59;
893: seconds = 59;
894: }
895: calendar.clear(Calendar.MILLISECOND);
896: calendar.set(years, months, value, hours, 0, 0);
897:
898:
899: Date d2 = calendar.getTime();
900: if (d2.getTime() >= date.getTime()) {
901: calendar.set(Calendar.DATE, value - 1);
902: d2 = calendar.getTime();
903: }
904: return d2;
905:
906: case (DateTickUnit.MONTH) :
907: years = calendar.get(Calendar.YEAR);
908: calendar.clear(Calendar.MILLISECOND);
909: calendar.set(years, value, 1, 0, 0, 0);
910: Month month = new Month(calendar.getTime());
911: Date standardDate = calculateDateForPosition(
912: month, this.tickMarkPosition);
913: long millis = standardDate.getTime();
914: if (millis > date.getTime()) {
915: month = (Month) month.previous();
916: standardDate = calculateDateForPosition(
917: month, this.tickMarkPosition);
918: }
919: return standardDate;
920:
921: case(DateTickUnit.YEAR) :
922: if (this.tickMarkPosition == DateTickMarkPosition.START) {
923: months = 0;
924: days = 1;
925: }
926: else if (this.tickMarkPosition == DateTickMarkPosition.MIDDLE) {
927: months = 6;
928: days = 1;
929: }
930: else {
931: months = 11;
932: days = 31;
933: }
934: calendar.clear(Calendar.MILLISECOND);
935: calendar.set(value, months, days, 0, 0, 0);
936: Date d3 = calendar.getTime();
937: if (d3.getTime() >= date.getTime()) {
938: calendar.set(Calendar.YEAR, value - 1);
939: d3 = calendar.getTime();
940: }
941: return d3;
942:
943: default: return null;
944:
945: }
946:
947: }
948:
949:
958: private Date calculateDateForPosition(RegularTimePeriod period,
959: DateTickMarkPosition position) {
960:
961: if (position == null) {
962: throw new IllegalArgumentException("Null 'position' argument.");
963: }
964: Date result = null;
965: if (position == DateTickMarkPosition.START) {
966: result = new Date(period.getFirstMillisecond());
967: }
968: else if (position == DateTickMarkPosition.MIDDLE) {
969: result = new Date(period.getMiddleMillisecond());
970: }
971: else if (position == DateTickMarkPosition.END) {
972: result = new Date(period.getLastMillisecond());
973: }
974: return result;
975:
976: }
977:
978:
987: protected Date nextStandardDate(Date date, DateTickUnit unit) {
988: Date previous = previousStandardDate(date, unit);
989: Calendar calendar = Calendar.getInstance(this.timeZone);
990: calendar.setTime(previous);
991: calendar.add(unit.getCalendarField(), unit.getCount());
992: return calendar.getTime();
993: }
994:
995:
1004: public static TickUnitSource createStandardDateTickUnits() {
1005: return createStandardDateTickUnits(TimeZone.getDefault());
1006: }
1007:
1008:
1019: public static TickUnitSource createStandardDateTickUnits(TimeZone zone) {
1020:
1021: if (zone == null) {
1022: throw new IllegalArgumentException("Null 'zone' argument.");
1023: }
1024: TickUnits units = new TickUnits();
1025:
1026:
1027: DateFormat f1 = new SimpleDateFormat("HH:mm:ss.SSS");
1028: DateFormat f2 = new SimpleDateFormat("HH:mm:ss");
1029: DateFormat f3 = new SimpleDateFormat("HH:mm");
1030: DateFormat f4 = new SimpleDateFormat("d-MMM, HH:mm");
1031: DateFormat f5 = new SimpleDateFormat("d-MMM");
1032: DateFormat f6 = new SimpleDateFormat("MMM-yyyy");
1033: DateFormat f7 = new SimpleDateFormat("yyyy");
1034:
1035: f1.setTimeZone(zone);
1036: f2.setTimeZone(zone);
1037: f3.setTimeZone(zone);
1038: f4.setTimeZone(zone);
1039: f5.setTimeZone(zone);
1040: f6.setTimeZone(zone);
1041: f7.setTimeZone(zone);
1042:
1043:
1044: units.add(new DateTickUnit(DateTickUnit.MILLISECOND, 1, f1));
1045: units.add(new DateTickUnit(DateTickUnit.MILLISECOND, 5,
1046: DateTickUnit.MILLISECOND, 1, f1));
1047: units.add(new DateTickUnit(DateTickUnit.MILLISECOND, 10,
1048: DateTickUnit.MILLISECOND, 1, f1));
1049: units.add(new DateTickUnit(DateTickUnit.MILLISECOND, 25,
1050: DateTickUnit.MILLISECOND, 5, f1));
1051: units.add(new DateTickUnit(DateTickUnit.MILLISECOND, 50,
1052: DateTickUnit.MILLISECOND, 10, f1));
1053: units.add(new DateTickUnit(DateTickUnit.MILLISECOND, 100,
1054: DateTickUnit.MILLISECOND, 10, f1));
1055: units.add(new DateTickUnit(DateTickUnit.MILLISECOND, 250,
1056: DateTickUnit.MILLISECOND, 10, f1));
1057: units.add(new DateTickUnit(DateTickUnit.MILLISECOND, 500,
1058: DateTickUnit.MILLISECOND, 50, f1));
1059:
1060:
1061: units.add(new DateTickUnit(DateTickUnit.SECOND, 1,
1062: DateTickUnit.MILLISECOND, 50, f2));
1063: units.add(new DateTickUnit(DateTickUnit.SECOND, 5,
1064: DateTickUnit.SECOND, 1, f2));
1065: units.add(new DateTickUnit(DateTickUnit.SECOND, 10,
1066: DateTickUnit.SECOND, 1, f2));
1067: units.add(new DateTickUnit(DateTickUnit.SECOND, 30,
1068: DateTickUnit.SECOND, 5, f2));
1069:
1070:
1071: units.add(new DateTickUnit(DateTickUnit.MINUTE, 1,
1072: DateTickUnit.SECOND, 5, f3));
1073: units.add(new DateTickUnit(DateTickUnit.MINUTE, 2,
1074: DateTickUnit.SECOND, 10, f3));
1075: units.add(new DateTickUnit(DateTickUnit.MINUTE, 5,
1076: DateTickUnit.MINUTE, 1, f3));
1077: units.add(new DateTickUnit(DateTickUnit.MINUTE, 10,
1078: DateTickUnit.MINUTE, 1, f3));
1079: units.add(new DateTickUnit(DateTickUnit.MINUTE, 15,
1080: DateTickUnit.MINUTE, 5, f3));
1081: units.add(new DateTickUnit(DateTickUnit.MINUTE, 20,
1082: DateTickUnit.MINUTE, 5, f3));
1083: units.add(new DateTickUnit(DateTickUnit.MINUTE, 30,
1084: DateTickUnit.MINUTE, 5, f3));
1085:
1086:
1087: units.add(new DateTickUnit(DateTickUnit.HOUR, 1,
1088: DateTickUnit.MINUTE, 5, f3));
1089: units.add(new DateTickUnit(DateTickUnit.HOUR, 2,
1090: DateTickUnit.MINUTE, 10, f3));
1091: units.add(new DateTickUnit(DateTickUnit.HOUR, 4,
1092: DateTickUnit.MINUTE, 30, f3));
1093: units.add(new DateTickUnit(DateTickUnit.HOUR, 6,
1094: DateTickUnit.HOUR, 1, f3));
1095: units.add(new DateTickUnit(DateTickUnit.HOUR, 12,
1096: DateTickUnit.HOUR, 1, f4));
1097:
1098:
1099: units.add(new DateTickUnit(DateTickUnit.DAY, 1,
1100: DateTickUnit.HOUR, 1, f5));
1101: units.add(new DateTickUnit(DateTickUnit.DAY, 2,
1102: DateTickUnit.HOUR, 1, f5));
1103: units.add(new DateTickUnit(DateTickUnit.DAY, 7,
1104: DateTickUnit.DAY, 1, f5));
1105: units.add(new DateTickUnit(DateTickUnit.DAY, 15,
1106: DateTickUnit.DAY, 1, f5));
1107:
1108:
1109: units.add(new DateTickUnit(DateTickUnit.MONTH, 1,
1110: DateTickUnit.DAY, 1, f6));
1111: units.add(new DateTickUnit(DateTickUnit.MONTH, 2,
1112: DateTickUnit.DAY, 1, f6));
1113: units.add(new DateTickUnit(DateTickUnit.MONTH, 3,
1114: DateTickUnit.MONTH, 1, f6));
1115: units.add(new DateTickUnit(DateTickUnit.MONTH, 4,
1116: DateTickUnit.MONTH, 1, f6));
1117: units.add(new DateTickUnit(DateTickUnit.MONTH, 6,
1118: DateTickUnit.MONTH, 1, f6));
1119:
1120:
1121: units.add(new DateTickUnit(DateTickUnit.YEAR, 1,
1122: DateTickUnit.MONTH, 1, f7));
1123: units.add(new DateTickUnit(DateTickUnit.YEAR, 2,
1124: DateTickUnit.MONTH, 3, f7));
1125: units.add(new DateTickUnit(DateTickUnit.YEAR, 5,
1126: DateTickUnit.YEAR, 1, f7));
1127: units.add(new DateTickUnit(DateTickUnit.YEAR, 10,
1128: DateTickUnit.YEAR, 1, f7));
1129: units.add(new DateTickUnit(DateTickUnit.YEAR, 25,
1130: DateTickUnit.YEAR, 5, f7));
1131: units.add(new DateTickUnit(DateTickUnit.YEAR, 50,
1132: DateTickUnit.YEAR, 10, f7));
1133: units.add(new DateTickUnit(DateTickUnit.YEAR, 100,
1134: DateTickUnit.YEAR, 20, f7));
1135:
1136: return units;
1137:
1138: }
1139:
1140:
1143: protected void autoAdjustRange() {
1144:
1145: Plot plot = getPlot();
1146:
1147: if (plot == null) {
1148: return;
1149: }
1150:
1151: if (plot instanceof ValueAxisPlot) {
1152: ValueAxisPlot vap = (ValueAxisPlot) plot;
1153:
1154: Range r = vap.getDataRange(this);
1155: if (r == null) {
1156: if (this.timeline instanceof SegmentedTimeline) {
1157:
1158: r = new DateRange((
1159: (SegmentedTimeline) this.timeline).getStartTime(),
1160: ((SegmentedTimeline) this.timeline).getStartTime()
1161: + 1);
1162: }
1163: else {
1164: r = new DateRange();
1165: }
1166: }
1167:
1168: long upper = this.timeline.toTimelineValue(
1169: (long) r.getUpperBound());
1170: long lower;
1171: long fixedAutoRange = (long) getFixedAutoRange();
1172: if (fixedAutoRange > 0.0) {
1173: lower = upper - fixedAutoRange;
1174: }
1175: else {
1176: lower = this.timeline.toTimelineValue((long) r.getLowerBound());
1177: double range = upper - lower;
1178: long minRange = (long) getAutoRangeMinimumSize();
1179: if (range < minRange) {
1180: long expand = (long) (minRange - range) / 2;
1181: upper = upper + expand;
1182: lower = lower - expand;
1183: }
1184: upper = upper + (long) (range * getUpperMargin());
1185: lower = lower - (long) (range * getLowerMargin());
1186: }
1187:
1188: upper = this.timeline.toMillisecond(upper);
1189: lower = this.timeline.toMillisecond(lower);
1190: DateRange dr = new DateRange(new Date(lower), new Date(upper));
1191: setRange(dr, false, false);
1192: }
1193:
1194: }
1195:
1196:
1205: protected void selectAutoTickUnit(Graphics2D g2,
1206: Rectangle2D dataArea,
1207: RectangleEdge edge) {
1208:
1209: if (RectangleEdge.isTopOrBottom(edge)) {
1210: selectHorizontalAutoTickUnit(g2, dataArea, edge);
1211: }
1212: else if (RectangleEdge.isLeftOrRight(edge)) {
1213: selectVerticalAutoTickUnit(g2, dataArea, edge);
1214: }
1215:
1216: }
1217:
1218:
1227: protected void selectHorizontalAutoTickUnit(Graphics2D g2,
1228: Rectangle2D dataArea,
1229: RectangleEdge edge) {
1230:
1231: long shift = 0;
1232: if (this.timeline instanceof SegmentedTimeline) {
1233: shift = ((SegmentedTimeline) this.timeline).getStartTime();
1234: }
1235: double zero = valueToJava2D(shift + 0.0, dataArea, edge);
1236: double tickLabelWidth
1237: = estimateMaximumTickLabelWidth(g2, getTickUnit());
1238:
1239:
1240: TickUnitSource tickUnits = getStandardTickUnits();
1241: TickUnit unit1 = tickUnits.getCeilingTickUnit(getTickUnit());
1242: double x1 = valueToJava2D(shift + unit1.getSize(), dataArea, edge);
1243: double unit1Width = Math.abs(x1 - zero);
1244:
1245:
1246: double guess = (tickLabelWidth / unit1Width) * unit1.getSize();
1247: DateTickUnit unit2 = (DateTickUnit) tickUnits.getCeilingTickUnit(guess);
1248: double x2 = valueToJava2D(shift + unit2.getSize(), dataArea, edge);
1249: double unit2Width = Math.abs(x2 - zero);
1250: tickLabelWidth = estimateMaximumTickLabelWidth(g2, unit2);
1251: if (tickLabelWidth > unit2Width) {
1252: unit2 = (DateTickUnit) tickUnits.getLargerTickUnit(unit2);
1253: }
1254: setTickUnit(unit2, false, false);
1255: }
1256:
1257:
1266: protected void selectVerticalAutoTickUnit(Graphics2D g2,
1267: Rectangle2D dataArea,
1268: RectangleEdge edge) {
1269:
1270:
1271: TickUnitSource tickUnits = getStandardTickUnits();
1272: double zero = valueToJava2D(0.0, dataArea, edge);
1273:
1274:
1275: double estimate1 = getRange().getLength() / 10.0;
1276: DateTickUnit candidate1
1277: = (DateTickUnit) tickUnits.getCeilingTickUnit(estimate1);
1278: double labelHeight1 = estimateMaximumTickLabelHeight(g2, candidate1);
1279: double y1 = valueToJava2D(candidate1.getSize(), dataArea, edge);
1280: double candidate1UnitHeight = Math.abs(y1 - zero);
1281:
1282:
1283: double estimate2
1284: = (labelHeight1 / candidate1UnitHeight) * candidate1.getSize();
1285: DateTickUnit candidate2
1286: = (DateTickUnit) tickUnits.getCeilingTickUnit(estimate2);
1287: double labelHeight2 = estimateMaximumTickLabelHeight(g2, candidate2);
1288: double y2 = valueToJava2D(candidate2.getSize(), dataArea, edge);
1289: double unit2Height = Math.abs(y2 - zero);
1290:
1291:
1292: DateTickUnit finalUnit;
1293: if (labelHeight2 < unit2Height) {
1294: finalUnit = candidate2;
1295: }
1296: else {
1297: finalUnit = (DateTickUnit) tickUnits.getLargerTickUnit(candidate2);
1298: }
1299: setTickUnit(finalUnit, false, false);
1300:
1301: }
1302:
1303:
1316: private double estimateMaximumTickLabelWidth(Graphics2D g2,
1317: DateTickUnit unit) {
1318:
1319: RectangleInsets tickLabelInsets = getTickLabelInsets();
1320: double result = tickLabelInsets.getLeft() + tickLabelInsets.getRight();
1321:
1322: Font tickLabelFont = getTickLabelFont();
1323: FontRenderContext frc = g2.getFontRenderContext();
1324: LineMetrics lm = tickLabelFont.getLineMetrics("ABCxyz", frc);
1325: if (isVerticalTickLabels()) {
1326:
1327:
1328: result += lm.getHeight();
1329: }
1330: else {
1331:
1332: DateRange range = (DateRange) getRange();
1333: Date lower = range.getLowerDate();
1334: Date upper = range.getUpperDate();
1335: String lowerStr = null;
1336: String upperStr = null;
1337: DateFormat formatter = getDateFormatOverride();
1338: if (formatter != null) {
1339: lowerStr = formatter.format(lower);
1340: upperStr = formatter.format(upper);
1341: }
1342: else {
1343: lowerStr = unit.dateToString(lower);
1344: upperStr = unit.dateToString(upper);
1345: }
1346: FontMetrics fm = g2.getFontMetrics(tickLabelFont);
1347: double w1 = fm.stringWidth(lowerStr);
1348: double w2 = fm.stringWidth(upperStr);
1349: result += Math.max(w1, w2);
1350: }
1351:
1352: return result;
1353:
1354: }
1355:
1356:
1369: private double estimateMaximumTickLabelHeight(Graphics2D g2,
1370: DateTickUnit unit) {
1371:
1372: RectangleInsets tickLabelInsets = getTickLabelInsets();
1373: double result = tickLabelInsets.getTop() + tickLabelInsets.getBottom();
1374:
1375: Font tickLabelFont = getTickLabelFont();
1376: FontRenderContext frc = g2.getFontRenderContext();
1377: LineMetrics lm = tickLabelFont.getLineMetrics("ABCxyz", frc);
1378: if (!isVerticalTickLabels()) {
1379:
1380:
1381: result += lm.getHeight();
1382: }
1383: else {
1384:
1385: DateRange range = (DateRange) getRange();
1386: Date lower = range.getLowerDate();
1387: Date upper = range.getUpperDate();
1388: String lowerStr = null;
1389: String upperStr = null;
1390: DateFormat formatter = getDateFormatOverride();
1391: if (formatter != null) {
1392: lowerStr = formatter.format(lower);
1393: upperStr = formatter.format(upper);
1394: }
1395: else {
1396: lowerStr = unit.dateToString(lower);
1397: upperStr = unit.dateToString(upper);
1398: }
1399: FontMetrics fm = g2.getFontMetrics(tickLabelFont);
1400: double w1 = fm.stringWidth(lowerStr);
1401: double w2 = fm.stringWidth(upperStr);
1402: result += Math.max(w1, w2);
1403: }
1404:
1405: return result;
1406:
1407: }
1408:
1409:
1420: public List refreshTicks(Graphics2D g2,
1421: AxisState state,
1422: Rectangle2D dataArea,
1423: RectangleEdge edge) {
1424:
1425: List result = null;
1426: if (RectangleEdge.isTopOrBottom(edge)) {
1427: result = refreshTicksHorizontal(g2, dataArea, edge);
1428: }
1429: else if (RectangleEdge.isLeftOrRight(edge)) {
1430: result = refreshTicksVertical(g2, dataArea, edge);
1431: }
1432: return result;
1433:
1434: }
1435:
1436:
1445: protected List refreshTicksHorizontal(Graphics2D g2,
1446: Rectangle2D dataArea,
1447: RectangleEdge edge) {
1448:
1449: List result = new java.util.ArrayList();
1450:
1451: Font tickLabelFont = getTickLabelFont();
1452: g2.setFont(tickLabelFont);
1453:
1454: if (isAutoTickUnitSelection()) {
1455: selectAutoTickUnit(g2, dataArea, edge);
1456: }
1457:
1458: DateTickUnit unit = getTickUnit();
1459: Date tickDate = calculateLowestVisibleTickValue(unit);
1460: Date upperDate = getMaximumDate();
1461:
1462: while (tickDate.before(upperDate)) {
1463:
1464: if (!isHiddenValue(tickDate.getTime())) {
1465:
1466: String tickLabel;
1467: DateFormat formatter = getDateFormatOverride();
1468: if (formatter != null) {
1469: tickLabel = formatter.format(tickDate);
1470: }
1471: else {
1472: tickLabel = this.tickUnit.dateToString(tickDate);
1473: }
1474: TextAnchor anchor = null;
1475: TextAnchor rotationAnchor = null;
1476: double angle = 0.0;
1477: if (isVerticalTickLabels()) {
1478: anchor = TextAnchor.CENTER_RIGHT;
1479: rotationAnchor = TextAnchor.CENTER_RIGHT;
1480: if (edge == RectangleEdge.TOP) {
1481: angle = Math.PI / 2.0;
1482: }
1483: else {
1484: angle = -Math.PI / 2.0;
1485: }
1486: }
1487: else {
1488: if (edge == RectangleEdge.TOP) {
1489: anchor = TextAnchor.BOTTOM_CENTER;
1490: rotationAnchor = TextAnchor.BOTTOM_CENTER;
1491: }
1492: else {
1493: anchor = TextAnchor.TOP_CENTER;
1494: rotationAnchor = TextAnchor.TOP_CENTER;
1495: }
1496: }
1497:
1498: Tick tick = new DateTick(
1499: tickDate, tickLabel, anchor, rotationAnchor, angle
1500: );
1501: result.add(tick);
1502: tickDate = unit.addToDate(tickDate);
1503: }
1504: else {
1505: tickDate = unit.rollDate(tickDate);
1506: continue;
1507: }
1508:
1509:
1510: switch (unit.getUnit()) {
1511:
1512: case (DateTickUnit.MILLISECOND) :
1513: case (DateTickUnit.SECOND) :
1514: case (DateTickUnit.MINUTE) :
1515: case (DateTickUnit.HOUR) :
1516: case (DateTickUnit.DAY) :
1517: break;
1518: case (DateTickUnit.MONTH) :
1519: tickDate = calculateDateForPosition(new Month(tickDate),
1520: this.tickMarkPosition);
1521: break;
1522: case(DateTickUnit.YEAR) :
1523: tickDate = calculateDateForPosition(
1524: new Year(tickDate), this.tickMarkPosition);
1525: break;
1526:
1527: default: break;
1528:
1529: }
1530:
1531: }
1532: return result;
1533:
1534: }
1535:
1536:
1545: protected List refreshTicksVertical(Graphics2D g2,
1546: Rectangle2D dataArea,
1547: RectangleEdge edge) {
1548:
1549: List result = new java.util.ArrayList();
1550:
1551: Font tickLabelFont = getTickLabelFont();
1552: g2.setFont(tickLabelFont);
1553:
1554: if (isAutoTickUnitSelection()) {
1555: selectAutoTickUnit(g2, dataArea, edge);
1556: }
1557: DateTickUnit unit = getTickUnit();
1558: Date tickDate = calculateLowestVisibleTickValue(unit);
1559:
1560: Date upperDate = getMaximumDate();
1561: while (tickDate.before(upperDate)) {
1562:
1563: if (!isHiddenValue(tickDate.getTime())) {
1564:
1565: String tickLabel;
1566: DateFormat formatter = getDateFormatOverride();
1567: if (formatter != null) {
1568: tickLabel = formatter.format(tickDate);
1569: }
1570: else {
1571: tickLabel = this.tickUnit.dateToString(tickDate);
1572: }
1573: TextAnchor anchor = null;
1574: TextAnchor rotationAnchor = null;
1575: double angle = 0.0;
1576: if (isVerticalTickLabels()) {
1577: anchor = TextAnchor.BOTTOM_CENTER;
1578: rotationAnchor = TextAnchor.BOTTOM_CENTER;
1579: if (edge == RectangleEdge.LEFT) {
1580: angle = -Math.PI / 2.0;
1581: }
1582: else {
1583: angle = Math.PI / 2.0;
1584: }
1585: }
1586: else {
1587: if (edge == RectangleEdge.LEFT) {
1588: anchor = TextAnchor.CENTER_RIGHT;
1589: rotationAnchor = TextAnchor.CENTER_RIGHT;
1590: }
1591: else {
1592: anchor = TextAnchor.CENTER_LEFT;
1593: rotationAnchor = TextAnchor.CENTER_LEFT;
1594: }
1595: }
1596:
1597: Tick tick = new DateTick(tickDate, tickLabel, anchor,
1598: rotationAnchor, angle);
1599: result.add(tick);
1600: tickDate = unit.addToDate(tickDate);
1601: }
1602: else {
1603: tickDate = unit.rollDate(tickDate);
1604: }
1605: }
1606: return result;
1607: }
1608:
1609:
1625: public AxisState draw(Graphics2D g2,
1626: double cursor,
1627: Rectangle2D plotArea,
1628: Rectangle2D dataArea,
1629: RectangleEdge edge,
1630: PlotRenderingInfo plotState) {
1631:
1632:
1633: if (!isVisible()) {
1634: AxisState state = new AxisState(cursor);
1635:
1636:
1637: List ticks = refreshTicks(g2, state, dataArea, edge);
1638: state.setTicks(ticks);
1639: return state;
1640: }
1641:
1642:
1643: AxisState state = drawTickMarksAndLabels(g2, cursor, plotArea,
1644: dataArea, edge);
1645:
1646:
1647:
1648: state = drawLabel(getLabel(), g2, plotArea, dataArea, edge, state);
1649:
1650: return state;
1651:
1652: }
1653:
1654:
1660: public void zoomRange(double lowerPercent, double upperPercent) {
1661: double start = this.timeline.toTimelineValue(
1662: (long) getRange().getLowerBound()
1663: );
1664: double length = (this.timeline.toTimelineValue(
1665: (long) getRange().getUpperBound())
1666: - this.timeline.toTimelineValue(
1667: (long) getRange().getLowerBound()));
1668: Range adjusted = null;
1669: if (isInverted()) {
1670: adjusted = new DateRange(this.timeline.toMillisecond((long) (start
1671: + (length * (1 - upperPercent)))),
1672: this.timeline.toMillisecond((long) (start + (length
1673: * (1 - lowerPercent)))));
1674: }
1675: else {
1676: adjusted = new DateRange(this.timeline.toMillisecond(
1677: (long) (start + length * lowerPercent)),
1678: this.timeline.toMillisecond((long) (start + length
1679: * upperPercent)));
1680: }
1681: setRange(adjusted);
1682: }
1683:
1684:
1691: public boolean equals(Object obj) {
1692: if (obj == this) {
1693: return true;
1694: }
1695: if (!(obj instanceof DateAxis)) {
1696: return false;
1697: }
1698: DateAxis that = (DateAxis) obj;
1699: if (!ObjectUtilities.equal(this.tickUnit, that.tickUnit)) {
1700: return false;
1701: }
1702: if (!ObjectUtilities.equal(this.dateFormatOverride,
1703: that.dateFormatOverride)) {
1704: return false;
1705: }
1706: if (!ObjectUtilities.equal(this.tickMarkPosition,
1707: that.tickMarkPosition)) {
1708: return false;
1709: }
1710: if (!ObjectUtilities.equal(this.timeline, that.timeline)) {
1711: return false;
1712: }
1713: if (!super.equals(obj)) {
1714: return false;
1715: }
1716: return true;
1717: }
1718:
1719:
1724: public int hashCode() {
1725: if (getLabel() != null) {
1726: return getLabel().hashCode();
1727: }
1728: else {
1729: return 0;
1730: }
1731: }
1732:
1733:
1741: public Object clone() throws CloneNotSupportedException {
1742:
1743: DateAxis clone = (DateAxis) super.clone();
1744:
1745:
1746: if (this.dateFormatOverride != null) {
1747: clone.dateFormatOverride
1748: = (DateFormat) this.dateFormatOverride.clone();
1749: }
1750:
1751:
1752: return clone;
1753:
1754: }
1755:
1756: }