@@ -487,7 +487,11 @@ axes.expand = function(ax, data, options) {
487
487
488
488
axes . autoBin = function ( data , ax , nbins , is2d ) {
489
489
var datamin = Lib . aggNums ( Math . min , null , data ) ,
490
- datamax = Lib . aggNums ( Math . max , null , data ) ;
490
+ datamax = Lib . aggNums ( Math . max , null , data ) ,
491
+ blankcount = 0 ,
492
+ datacount ,
493
+ i ;
494
+
491
495
if ( ax . type === 'category' ) {
492
496
return {
493
497
start : datamin - 0.5 ,
@@ -548,16 +552,16 @@ axes.autoBin = function(data, ax, nbins, is2d) {
548
552
if ( typeof dummyax . dtick === 'number' ) {
549
553
var edgecount = 0 ,
550
554
midcount = 0 ,
551
- intcount = 0 ,
552
- blankcount = 0 ;
553
- for ( var i = 0 ; i < data . length ; i ++ ) {
555
+ intcount = 0 ;
556
+
557
+ for ( i = 0 ; i < data . length ; i ++ ) {
554
558
if ( data [ i ] % 1 === 0 ) intcount ++ ;
555
559
else if ( ! isNumeric ( data [ i ] ) ) blankcount ++ ;
556
560
557
561
if ( nearEdge ( data [ i ] ) ) edgecount ++ ;
558
562
if ( nearEdge ( data [ i ] + dummyax . dtick / 2 ) ) midcount ++ ;
559
563
}
560
- var datacount = data . length - blankcount ;
564
+ datacount = data . length - blankcount ;
561
565
562
566
if ( intcount === datacount && ax . type !== 'date' ) {
563
567
// all integers: if bin size is <1, it's because
@@ -586,6 +590,12 @@ axes.autoBin = function(data, ax, nbins, is2d) {
586
590
binend = binstart + bincount * dummyax . dtick ;
587
591
}
588
592
else {
593
+ // month ticks - should be the only nonlinear kind we have
594
+ // at this point.
595
+ if ( dummyax . dtick . charAt ( 0 ) === 'M' ) {
596
+ binstart = autoShiftMonthBins ( binstart , data , dummyax . dtick , datamin ) ;
597
+ }
598
+
589
599
// calculate the endpoint for nonlinear ticks - you have to
590
600
// just increment until you're done
591
601
binend = binstart ;
@@ -602,6 +612,79 @@ axes.autoBin = function(data, ax, nbins, is2d) {
602
612
} ;
603
613
604
614
615
+ function autoShiftMonthBins ( binStart , data , dtick , dataMin ) {
616
+ var exactYears = 0 ,
617
+ exactMonths = 0 ,
618
+ exactDays = 0 ,
619
+ blankCount = 0 ,
620
+ dataCount ,
621
+ di ,
622
+ d ,
623
+ year ,
624
+ month ;
625
+
626
+ for ( var i = 0 ; i < data . length ; i ++ ) {
627
+ di = data [ i ] ;
628
+ if ( ! isNumeric ( di ) ) {
629
+ blankCount ++ ;
630
+ continue ;
631
+ }
632
+ d = new Date ( di ) ,
633
+ year = d . getUTCFullYear ( ) ;
634
+ if ( di === Date . UTC ( year , 0 , 1 ) ) {
635
+ exactYears ++ ;
636
+ }
637
+ else {
638
+ month = d . getUTCMonth ( ) ;
639
+ if ( di === Date . UTC ( year , month , 1 ) ) {
640
+ exactMonths ++ ;
641
+ }
642
+ else if ( di === Date . UTC ( year , month , d . getUTCDate ( ) ) ) {
643
+ exactDays ++ ;
644
+ }
645
+ }
646
+ }
647
+
648
+ dataCount = data . length - blankCount ;
649
+
650
+ // include bigger exact dates in the smaller ones
651
+ exactMonths += exactYears ;
652
+ exactDays += exactMonths ;
653
+
654
+ // unmber of data points that needs to be an exact value
655
+ // to shift that increment to (near) the bin center
656
+ var threshold = 0.8 * dataCount ;
657
+
658
+ if ( exactDays > threshold ) {
659
+ var numMonths = Number ( dtick . substr ( 1 ) ) ;
660
+
661
+ if ( ( exactYears > threshold ) && ( numMonths % 12 === 0 ) ) {
662
+ // The exact middle of a non-leap-year is 1.5 days into July
663
+ // so if we start the bins here, all but leap years will
664
+ // get hover-labeled as exact years.
665
+ binStart = axes . tickIncrement ( binStart , 'M6' , 'reverse' ) + ONEDAY * 1.5 ;
666
+ }
667
+ else if ( exactMonths > threshold ) {
668
+ // Months are not as clean, but if we shift half the *longest*
669
+ // month (31/2 days) then 31-day months will get labeled exactly
670
+ // and shorter months will get labeled with the correct month
671
+ // but shifted 12-36 hours into it.
672
+ binStart = axes . tickIncrement ( binStart , 'M1' , 'reverse' ) + ONEDAY * 15.5 ;
673
+ }
674
+ else {
675
+ // Shifting half a day is exact, but since these are month bins it
676
+ // will always give a somewhat odd-looking label, until we do something
677
+ // smarter like showing the bin boundaries (or the bounds of the actual
678
+ // data in each bin)
679
+ binStart -= ONEDAY / 2 ;
680
+ }
681
+ var nextBinStart = axes . tickIncrement ( binStart , dtick ) ;
682
+
683
+ if ( nextBinStart <= dataMin ) return nextBinStart ;
684
+ }
685
+ return binStart ;
686
+ }
687
+
605
688
// ----------------------------------------------------
606
689
// Ticks and grids
607
690
// ----------------------------------------------------
@@ -919,6 +1002,7 @@ function autoTickRound(ax) {
919
1002
// for pure powers of 10
920
1003
// numeric ticks always have constant differences, other datetime ticks
921
1004
// can all be calculated as constant number of milliseconds
1005
+ var THREEDAYS = 3 * ONEDAY ;
922
1006
axes . tickIncrement = function ( x , dtick , axrev ) {
923
1007
var axSign = axrev ? - 1 : 1 ;
924
1008
@@ -930,10 +1014,23 @@ axes.tickIncrement = function(x, dtick, axrev) {
930
1014
931
1015
// Dates: months (or years)
932
1016
if ( tType === 'M' ) {
933
- var y = new Date ( x ) ;
934
- // is this browser consistent? setUTCMonth edits a date but
935
- // returns that date's milliseconds
936
- return y . setUTCMonth ( y . getUTCMonth ( ) + dtSigned ) ;
1017
+ /*
1018
+ * set(UTC)Month does not (and CANNOT) always preserve day, since
1019
+ * months have different lengths. The worst example of this is:
1020
+ * d = new Date(1970,0,31); d.setMonth(1) -> Feb 31 turns into Mar 3
1021
+ *
1022
+ * But we want to be able to iterate over the last day of each month,
1023
+ * regardless of what its number is.
1024
+ * So shift 3 days forward, THEN set the new month, then unshift:
1025
+ * 1/31 -> 2/28 (or 29) -> 3/31 -> 4/30 -> ...
1026
+ *
1027
+ * Note that odd behavior still exists if you start from the 26th-28th:
1028
+ * 1/28 -> 2/28 -> 3/31
1029
+ * but at least you can't shift any dates into the wrong month,
1030
+ * and ticks on these days incrementing by month would be very unusual
1031
+ */
1032
+ var y = new Date ( x + THREEDAYS ) ;
1033
+ return y . setUTCMonth ( y . getUTCMonth ( ) + dtSigned ) - THREEDAYS ;
937
1034
}
938
1035
939
1036
// Log scales: Linear, Digits
0 commit comments