@@ -575,6 +575,10 @@ axes.prepTicks = function(ax, opts) {
575575 }
576576 }
577577
578+ if ( ax . ticklabelmode === 'period' ) {
579+ adjustPeriodDelta ( ax ) ;
580+ }
581+
578582 // check for missing tick0
579583 if ( ! ax . tick0 ) {
580584 ax . tick0 = ( ax . type === 'date' ) ? '2000-01-01' : 0 ;
@@ -592,47 +596,18 @@ function nMonths(dtick) {
592596 return + ( dtick . substring ( 1 ) ) ;
593597}
594598
595- // calculate the ticks: text, values, positioning
596- // if ticks are set to automatic, determine the right values (tick0,dtick)
597- // in any case, set tickround to # of digits to round tick labels to,
598- // or codes to this effect for log and date scales
599- axes . calcTicks = function calcTicks ( ax , opts ) {
600- axes . prepTicks ( ax , opts ) ;
601- var rng = Lib . simpleMap ( ax . range , ax . r2l , undefined , undefined , opts ) ;
602-
603- // now that we've figured out the auto values for formatting
604- // in case we're missing some ticktext, we can break out for array ticks
605- if ( ax . tickmode === 'array' ) return arrayTicks ( ax ) ;
606-
607- // add a tiny bit so we get ticks which may have rounded out
608- var exRng = expandRange ( rng ) ;
609- var startTick = exRng [ 0 ] ;
610- var endTick = exRng [ 1 ] ;
611- // check for reversed axis
612- var axrev = ( rng [ 1 ] < rng [ 0 ] ) ;
613- var minRange = Math . min ( rng [ 0 ] , rng [ 1 ] ) ;
614- var maxRange = Math . max ( rng [ 0 ] , rng [ 1 ] ) ;
615-
616- // find the first tick
617- ax . _tmin = axes . tickFirst ( ax , opts ) ;
618-
619- // No visible ticks? Quit.
620- // I've only seen this on category axes with all categories off the edge.
621- if ( ( ax . _tmin < startTick ) !== axrev ) return [ ] ;
599+ function adjustPeriodDelta ( ax ) { // adjusts ax.dtick and sets ax._definedDelta
600+ var definedDelta ;
622601
623- // return the full set of tick vals
624- if ( ax . type === 'category' || ax . type === 'multicategory' ) {
625- endTick = ( axrev ) ? Math . max ( - 0.5 , endTick ) :
626- Math . min ( ax . _categories . length - 0.5 , endTick ) ;
602+ function mDate ( ) {
603+ return ! (
604+ isNumeric ( ax . dtick ) ||
605+ ax . dtick . charAt ( 0 ) !== 'M'
606+ ) ;
627607 }
628-
629- var isDLog = ( ax . type === 'log' ) && ! ( isNumeric ( ax . dtick ) || ax . dtick . charAt ( 0 ) === 'L' ) ;
630- var isMDate = ( ax . type === 'date' ) && ! ( isNumeric ( ax . dtick ) || ax . dtick . charAt ( 0 ) === 'M' ) ;
631-
608+ var isMDate = mDate ( ) ;
632609 var tickformat = axes . getTickFormat ( ax ) ;
633- var isPeriod = ax . ticklabelmode === 'period' ;
634- var definedDelta ;
635- if ( isPeriod && tickformat ) {
610+ if ( tickformat ) {
636611 var noDtick = ax . _dtickInit !== ax . dtick ;
637612 if (
638613 ! ( / % [ f L Q s S M X ] / . test ( tickformat ) )
@@ -708,9 +683,136 @@ axes.calcTicks = function calcTicks(ax, opts) {
708683 }
709684 }
710685
711- var maxTicks = Math . max ( 1000 , ax . _length || 0 ) ;
712- var tickVals = [ ] ;
713- var xPrevious = null ;
686+ isMDate = mDate ( ) ;
687+ if ( isMDate && ax . tick0 === ax . _dowTick0 ) {
688+ // discard Sunday/Monday tweaks
689+ ax . tick0 = ax . _rawTick0 ;
690+ }
691+
692+ ax . _definedDelta = definedDelta ;
693+ }
694+
695+ function positionPeriodTicks ( tickVals , ax , definedDelta ) {
696+ for ( var i = 0 ; i < tickVals . length ; i ++ ) {
697+ var v = tickVals [ i ] . value ;
698+
699+ var a = i ;
700+ var b = i + 1 ;
701+ if ( i < tickVals . length - 1 ) {
702+ a = i ;
703+ b = i + 1 ;
704+ } else if ( i > 0 ) {
705+ a = i - 1 ;
706+ b = i ;
707+ } else {
708+ a = i ;
709+ b = i ;
710+ }
711+
712+ var A = tickVals [ a ] . value ;
713+ var B = tickVals [ b ] . value ;
714+ var actualDelta = Math . abs ( B - A ) ;
715+ var delta = definedDelta || actualDelta ;
716+ var periodLength = 0 ;
717+
718+ if ( delta >= ONEMINYEAR ) {
719+ if ( actualDelta >= ONEMINYEAR && actualDelta <= ONEMAXYEAR ) {
720+ periodLength = actualDelta ;
721+ } else {
722+ periodLength = ONEAVGYEAR ;
723+ }
724+ } else if ( definedDelta === ONEAVGQUARTER && delta >= ONEMINQUARTER ) {
725+ if ( actualDelta >= ONEMINQUARTER && actualDelta <= ONEMAXQUARTER ) {
726+ periodLength = actualDelta ;
727+ } else {
728+ periodLength = ONEAVGQUARTER ;
729+ }
730+ } else if ( delta >= ONEMINMONTH ) {
731+ if ( actualDelta >= ONEMINMONTH && actualDelta <= ONEMAXMONTH ) {
732+ periodLength = actualDelta ;
733+ } else {
734+ periodLength = ONEAVGMONTH ;
735+ }
736+ } else if ( definedDelta === ONEWEEK && delta >= ONEWEEK ) {
737+ periodLength = ONEWEEK ;
738+ } else if ( delta >= ONEDAY ) {
739+ periodLength = ONEDAY ;
740+ } else if ( definedDelta === HALFDAY && delta >= HALFDAY ) {
741+ periodLength = HALFDAY ;
742+ } else if ( definedDelta === ONEHOUR && delta >= ONEHOUR ) {
743+ periodLength = ONEHOUR ;
744+ }
745+
746+ var inBetween ;
747+ if ( periodLength >= actualDelta ) {
748+ // ensure new label positions remain between ticks
749+ periodLength = actualDelta ;
750+ inBetween = true ;
751+ }
752+
753+ var endPeriod = v + periodLength ;
754+ if ( ax . rangebreaks && periodLength > 0 ) {
755+ var nAll = 84 ; // highly divisible 7 * 12
756+ var n = 0 ;
757+ for ( var c = 0 ; c < nAll ; c ++ ) {
758+ var r = ( c + 0.5 ) / nAll ;
759+ if ( ax . maskBreaks ( v * ( 1 - r ) + r * endPeriod ) !== BADNUM ) n ++ ;
760+ }
761+ periodLength *= n / nAll ;
762+
763+ if ( ! periodLength ) {
764+ tickVals [ i ] . drop = true ;
765+ }
766+
767+ if ( inBetween && actualDelta > ONEWEEK ) periodLength = actualDelta ; // center monthly & longer periods
768+ }
769+
770+ if (
771+ periodLength > 0 || // not instant
772+ i === 0 // taking care first tick added
773+ ) {
774+ tickVals [ i ] . periodX = v + periodLength / 2 ;
775+ }
776+ }
777+ }
778+
779+ // calculate the ticks: text, values, positioning
780+ // if ticks are set to automatic, determine the right values (tick0,dtick)
781+ // in any case, set tickround to # of digits to round tick labels to,
782+ // or codes to this effect for log and date scales
783+ axes . calcTicks = function calcTicks ( ax , opts ) {
784+ axes . prepTicks ( ax , opts ) ;
785+ var rng = Lib . simpleMap ( ax . range , ax . r2l , undefined , undefined , opts ) ;
786+
787+ // now that we've figured out the auto values for formatting
788+ // in case we're missing some ticktext, we can break out for array ticks
789+ if ( ax . tickmode === 'array' ) return arrayTicks ( ax ) ;
790+
791+ // add a tiny bit so we get ticks which may have rounded out
792+ var exRng = expandRange ( rng ) ;
793+ var startTick = exRng [ 0 ] ;
794+ var endTick = exRng [ 1 ] ;
795+ // check for reversed axis
796+ var axrev = ( rng [ 1 ] < rng [ 0 ] ) ;
797+ var minRange = Math . min ( rng [ 0 ] , rng [ 1 ] ) ;
798+ var maxRange = Math . max ( rng [ 0 ] , rng [ 1 ] ) ;
799+
800+ var isDLog = ( ax . type === 'log' ) && ! ( isNumeric ( ax . dtick ) || ax . dtick . charAt ( 0 ) === 'L' ) ;
801+ var isPeriod = ax . ticklabelmode === 'period' ;
802+
803+ // find the first tick
804+ ax . _tmin = axes . tickFirst ( ax , opts ) ;
805+
806+ // No visible ticks? Quit.
807+ // I've only seen this on category axes with all categories off the edge.
808+ if ( ( ax . _tmin < startTick ) !== axrev ) return [ ] ;
809+
810+ // return the full set of tick vals
811+ if ( ax . type === 'category' || ax . type === 'multicategory' ) {
812+ endTick = ( axrev ) ? Math . max ( - 0.5 , endTick ) :
813+ Math . min ( ax . _categories . length - 0.5 , endTick ) ;
814+ }
815+
714816 var x = ax . _tmin ;
715817
716818 if ( ax . rangebreaks && ax . _tick0Init !== ax . tick0 ) {
@@ -726,6 +828,9 @@ axes.calcTicks = function calcTicks(ax, opts) {
726828 x = axes . tickIncrement ( x , ax . dtick , ! axrev , ax . calendar ) ;
727829 }
728830
831+ var maxTicks = Math . max ( 1000 , ax . _length || 0 ) ;
832+ var tickVals = [ ] ;
833+ var xPrevious = null ;
729834 for ( ;
730835 ( axrev ) ? ( x >= endTick ) : ( x <= endTick ) ;
731836 x = axes . tickIncrement ( x , ax . dtick , axrev , ax . calendar )
@@ -753,91 +858,9 @@ axes.calcTicks = function calcTicks(ax, opts) {
753858 } ) ;
754859 }
755860
756- var i ;
757- if ( isPeriod ) {
758- for ( i = 0 ; i < tickVals . length ; i ++ ) {
759- var v = tickVals [ i ] . value ;
760-
761- var a = i ;
762- var b = i + 1 ;
763- if ( i < tickVals . length - 1 ) {
764- a = i ;
765- b = i + 1 ;
766- } else if ( i > 0 ) {
767- a = i - 1 ;
768- b = i ;
769- } else {
770- a = i ;
771- b = i ;
772- }
773-
774- var A = tickVals [ a ] . value ;
775- var B = tickVals [ b ] . value ;
776- var actualDelta = Math . abs ( B - A ) ;
777- var delta = definedDelta || actualDelta ;
778- var periodLength = 0 ;
779-
780- if ( delta >= ONEMINYEAR ) {
781- if ( actualDelta >= ONEMINYEAR && actualDelta <= ONEMAXYEAR ) {
782- periodLength = actualDelta ;
783- } else {
784- periodLength = ONEAVGYEAR ;
785- }
786- } else if ( definedDelta === ONEAVGQUARTER && delta >= ONEMINQUARTER ) {
787- if ( actualDelta >= ONEMINQUARTER && actualDelta <= ONEMAXQUARTER ) {
788- periodLength = actualDelta ;
789- } else {
790- periodLength = ONEAVGQUARTER ;
791- }
792- } else if ( delta >= ONEMINMONTH ) {
793- if ( actualDelta >= ONEMINMONTH && actualDelta <= ONEMAXMONTH ) {
794- periodLength = actualDelta ;
795- } else {
796- periodLength = ONEAVGMONTH ;
797- }
798- } else if ( definedDelta === ONEWEEK && delta >= ONEWEEK ) {
799- periodLength = ONEWEEK ;
800- } else if ( delta >= ONEDAY ) {
801- periodLength = ONEDAY ;
802- } else if ( definedDelta === HALFDAY && delta >= HALFDAY ) {
803- periodLength = HALFDAY ;
804- } else if ( definedDelta === ONEHOUR && delta >= ONEHOUR ) {
805- periodLength = ONEHOUR ;
806- }
807-
808- var inBetween ;
809- if ( periodLength >= actualDelta ) {
810- // ensure new label positions remain between ticks
811- periodLength = actualDelta ;
812- inBetween = true ;
813- }
814-
815- var endPeriod = v + periodLength ;
816- if ( ax . rangebreaks && periodLength > 0 ) {
817- var nAll = 84 ; // highly divisible 7 * 12
818- var n = 0 ;
819- for ( var c = 0 ; c < nAll ; c ++ ) {
820- var r = ( c + 0.5 ) / nAll ;
821- if ( ax . maskBreaks ( v * ( 1 - r ) + r * endPeriod ) !== BADNUM ) n ++ ;
822- }
823- periodLength *= n / nAll ;
824-
825- if ( ! periodLength ) {
826- tickVals [ i ] . drop = true ;
827- }
828-
829- if ( inBetween && actualDelta > ONEWEEK ) periodLength = actualDelta ; // center monthly & longer periods
830- }
831-
832- if (
833- periodLength > 0 || // not instant
834- i === 0 // taking care first tick added
835- ) {
836- tickVals [ i ] . periodX = v + periodLength / 2 ;
837- }
838- }
839- }
861+ if ( isPeriod ) positionPeriodTicks ( tickVals , ax , ax . _definedDelta ) ;
840862
863+ var i ;
841864 if ( ax . rangebreaks ) {
842865 var flip = ax . _id . charAt ( 0 ) === 'y' ;
843866
@@ -1022,11 +1045,16 @@ axes.autoTicks = function(ax, roughDTick) {
10221045 // this will also move the base tick off 2000-01-01 if dtick is
10231046 // 2 or 3 days... but that's a weird enough case that we'll ignore it.
10241047 var tickformat = axes . getTickFormat ( ax ) ;
1048+ var isPeriod = ax . ticklabelmode === 'period' ;
1049+ if ( isPeriod ) ax . _rawTick0 = ax . tick0 ;
1050+
10251051 if ( / % [ u V W ] / . test ( tickformat ) ) {
10261052 ax . tick0 = Lib . dateTick0 ( ax . calendar , 2 ) ; // Monday
10271053 } else {
10281054 ax . tick0 = Lib . dateTick0 ( ax . calendar , 1 ) ; // Sunday
10291055 }
1056+
1057+ if ( isPeriod ) ax . _dowTick0 = ax . tick0 ;
10301058 } else if ( roughX2 > ONEHOUR ) {
10311059 ax . dtick = roundDTick ( roughDTick , ONEHOUR , roundBase24 ) ;
10321060 } else if ( roughX2 > ONEMIN ) {
0 commit comments