@@ -545,54 +545,82 @@ function autoShiftMonthBins(binStart, data, dtick, dataMin, calendar) {
545545axes . prepMinorTicks = function ( mockAx , ax , opts ) {
546546 if ( ! ax . minor . dtick ) {
547547 delete mockAx . dtick ;
548- var tick2 = axes . tickIncrement ( ax . _tmin , ax . dtick , true ) ;
549- // mock range a tiny bit smaller than one major tick interval
550- mockAx . range = Lib . simpleMap ( [ ax . _tmin , tick2 * 0.99 + ax . _tmin * 0.01 ] , ax . l2r ) ;
548+ var hasMajor = ax . dtick && isNumeric ( ax . _tmin ) ;
549+ var mockMinorRange ;
550+ if ( hasMajor ) {
551+ var tick2 = axes . tickIncrement ( ax . _tmin , ax . dtick , true ) ;
552+ // mock range a tiny bit smaller than one major tick interval
553+ mockMinorRange = [ ax . _tmin , tick2 * 0.99 + ax . _tmin * 0.01 ] ;
554+ } else {
555+ var rl = Lib . simpleMap ( ax . range , ax . r2l ) ;
556+ // If we don't have a major dtick, the concept of minor ticks is a little
557+ // ambiguous - just take a stab and say minor.nticks should span 1/5 the axis
558+ mockMinorRange = [ rl [ 0 ] , 0.8 * rl [ 0 ] + 0.2 * rl [ 1 ] ] ;
559+ }
560+ mockAx . range = Lib . simpleMap ( mockMinorRange , ax . l2r ) ;
551561 mockAx . _isMinor = true ;
562+
552563 axes . prepTicks ( mockAx , opts ) ;
553- if ( isNumeric ( ax . dtick ) && isNumeric ( mockAx . dtick ) ) {
554- if ( ! isMultiple ( ax . dtick , mockAx . dtick ) ) {
555- // give up on minor ticks, with one exception:
556- // dtick === 2 weeks, minor = 3 days -> set minor 1 week
557- // other than that, this can only happen if minor.nticks is
558- // smaller than two jumps in the auto-tick scale and the first
559- // jump is not an even multiple (5 -> 2 or for dates 3 ->2, 15 -> 10 etc)
560- // or if you provided an explicit dtick, in which case it's fine to
561- // give up, you can provide an explicit minor.dtick.
562- if ( ( ax . dtick === 2 * ONEWEEK ) && ( mockAx . dtick === 3 * ONEDAY ) ) {
564+
565+ if ( hasMajor ) {
566+ var numericMajor = isNumeric ( ax . dtick ) ;
567+ var numericMinor = isNumeric ( mockAx . dtick ) ;
568+ var majorNum = numericMajor ? ax . dtick : + ax . dtick . substring ( 1 ) ;
569+ var minorNum = numericMinor ? mockAx . dtick : + mockAx . dtick . substring ( 1 ) ;
570+ if ( numericMajor && numericMinor ) {
571+ if ( ! isMultiple ( majorNum , minorNum ) ) {
572+ // give up on minor ticks - outside the below exceptions,
573+ // this can only happen if minor.nticks is smaller than two jumps
574+ // in the auto-tick scale and the first jump is not an even multiple
575+ // (5 -> 2 or for dates 3 ->2, 15 -> 10 etc) or if you provided
576+ // an explicit dtick, in which case it's fine to give up,
577+ // you can provide an explicit minor.dtick.
578+ if ( ( majorNum === 2 * ONEWEEK ) && ( minorNum === 3 * ONEDAY ) ) {
579+ mockAx . dtick = ONEWEEK ;
580+ } else if ( majorNum === ONEWEEK && ! ( ax . _input . minor || { } ) . nticks ) {
581+ // minor.nticks defaults to 5, but in this one case we want 7,
582+ // so the minor ticks show on all days of the week
583+ mockAx . dtick = ONEDAY ;
584+ } else if ( isClose ( majorNum / minorNum , 2.5 ) ) {
585+ // 5*10^n -> 2*10^n and you've set nticks < 5
586+ // quarters are pretty common, we don't do this by default as it
587+ // would add an extra digit to display, but minor has no labels
588+ mockAx . dtick = majorNum / 2 ;
589+ } else {
590+ mockAx . dtick = majorNum ;
591+ }
592+ } else if ( majorNum === 2 * ONEWEEK && minorNum === 2 * ONEDAY ) {
593+ // this is a weird one: we don't want to automatically choose
594+ // 2-day minor ticks for 2-week major, even though it IS an even multiple,
595+ // because people would expect to see the weeks clearly
563596 mockAx . dtick = ONEWEEK ;
564- } else {
565- mockAx . dtick = ax . dtick ;
566597 }
567- } else if ( ax . dtick === 2 * ONEWEEK && mockAx . dtick === 2 * ONEDAY ) {
568- // this is a weird one: we don't want to automatically choose
569- // 2-day minor ticks for 2-week major, even though it IS an even multiple,
570- // because people would expect to see the weeks clearly
571- mockAx . dtick = ONEWEEK ;
572- }
573- } else if ( String ( ax . dtick ) . charAt ( 0 ) === 'M' ) {
574- if ( isNumeric ( mockAx . dtick ) ) {
575- mockAx . dtick = 'M1' ;
576- } else {
577- var majorMonths = + ax . dtick . substring ( 1 ) ;
578- var minorMonths = + mockAx . dtick . substring ( 1 ) ;
579- if ( ! isMultiple ( majorMonths , minorMonths ) ) {
580- // unless you provided an explicit ax.dtick (in which case
581- // it's OK for us to give up, you can provide an explicit
582- // minor.dtick too), this can only happen with:
583- // minor.nticks < 3 and dtick === M3, or
584- // minor.nticks < 5 and dtick === 5 * 10^n years
585- // so in all cases we just give up.
586- mockAx . dtick = ax . dtick ;
598+ } else if ( String ( ax . dtick ) . charAt ( 0 ) === 'M' ) {
599+ if ( numericMinor ) {
600+ mockAx . dtick = 'M1' ;
601+ } else {
602+ if ( ! isMultiple ( majorNum , minorNum ) ) {
603+ // unless you provided an explicit ax.dtick (in which case
604+ // it's OK for us to give up, you can provide an explicit
605+ // minor.dtick too), this can only happen with:
606+ // minor.nticks < 3 and dtick === M3, or
607+ // minor.nticks < 5 and dtick === 5 * 10^n years
608+ // so in all cases we just give up.
609+ mockAx . dtick = ax . dtick ;
610+ } else if ( ( majorNum >= 12 ) && ( minorNum === 2 ) ) {
611+ // another special carve-out: for year major ticks, don't show
612+ // 2-month minor ticks, bump to quarters
613+ mockAx . dtick = 'M3' ;
614+ }
587615 }
588- }
589- } else if ( String ( mockAx . dtick ) . charAt ( 0 ) === 'L' ) {
590- if ( String ( ax . dtick ) . charAt ( 0 ) === 'L' ) {
591- if ( ! isMultiple ( + ax . dtick . substring ( 1 ) , + mockAx . dtick . substring ( 1 ) ) ) {
592- mockAx . dtick = ax . dtick ;
616+ } else if ( String ( mockAx . dtick ) . charAt ( 0 ) === 'L' ) {
617+ if ( String ( ax . dtick ) . charAt ( 0 ) === 'L' ) {
618+ if ( ! isMultiple ( majorNum , minorNum ) ) {
619+ mockAx . dtick = isClose ( majorNum / minorNum , 2.5 ) ? ( ax . dtick / 2 ) : ax . dtick ;
620+ }
621+ } else {
622+ mockAx . dtick = 'D1' ;
593623 }
594- } else {
595- mockAx . dtick = 'D1' ;
596624 }
597625 }
598626 // put back the original range, to use to find the full set of minor ticks
@@ -608,6 +636,10 @@ function isMultiple(bigger, smaller) {
608636 return Math . abs ( ( bigger / smaller + 0.5 ) % 1 - 0.5 ) < 0.001 ;
609637}
610638
639+ function isClose ( a , b ) {
640+ return Math . abs ( ( a / b ) - 1 ) < 0.001 ;
641+ }
642+
611643// ensure we have tick0, dtick, and tick rounding calculated
612644axes . prepTicks = function ( ax , opts ) {
613645 var rng = Lib . simpleMap ( ax . range , ax . r2l , undefined , undefined , opts ) ;
0 commit comments