@@ -694,6 +694,11 @@ axes.prepMinorTicks = function(mockAx, ax, opts) {
694694 // ensure identical tick0
695695 mockAx . tick0 = ax . tick0 ;
696696 }
697+ autoTickRound ( mockAx ) ;
698+ if ( ax . ticklabelmode === 'period' ) {
699+ mockAx . _definedDelta = definedDeltaForTickformat ( axes . getTickFormat ( mockAx ) ) ;
700+ }
701+ Lib . extendFlat ( ax . minor , mockAx ) ;
697702} ;
698703
699704function isMultiple ( bigger , smaller ) {
@@ -764,90 +769,96 @@ function nMonths(dtick) {
764769 return + ( dtick . substring ( 1 ) ) ;
765770}
766771
767- function adjustPeriodDelta ( ax ) { // adjusts ax.dtick and sets ax._definedDelta
768- var definedDelta ;
772+ // Maps a tick format string to the period length its labels represent.
773+ // Returns undefined if the format has no (or a sub-second/minute) period unit,
774+ // in which case the period length is derived from the actual tick spacing.
775+ function definedDeltaForTickformat ( tickformat ) {
776+ if (
777+ ! tickformat ||
778+ / % [ f L Q s S M X ] / . test ( tickformat )
779+ // %f: microseconds as a decimal number [000000, 999999]
780+ // %L: milliseconds as a decimal number [000, 999]
781+ // %Q: milliseconds since UNIX epoch
782+ // %s: seconds since UNIX epoch
783+ // %S: second as a decimal number [00,61]
784+ // %M: minute as a decimal number [00,59]
785+ // %X: the locale’s time, such as %-I:%M:%S %p
786+ ) return undefined ;
787+
788+ if (
789+ / % [ H I ] / . test ( tickformat )
790+ // %H: hour (24-hour clock) as a decimal number [00,23]
791+ // %I: hour (12-hour clock) as a decimal number [01,12]
792+ ) return ONEHOUR ;
793+ if (
794+ / % p / . test ( tickformat ) // %p: either AM or PM
795+ ) return HALFDAY ;
796+ if (
797+ / % [ A a d e j u w x ] / . test ( tickformat )
798+ // %A: full weekday name
799+ // %a: abbreviated weekday name
800+ // %d: zero-padded day of the month as a decimal number [01,31]
801+ // %e: space-padded day of the month as a decimal number [ 1,31]
802+ // %j: day of the year as a decimal number [001,366]
803+ // %u: Monday-based (ISO 8601) weekday as a decimal number [1,7]
804+ // %w: Sunday-based weekday as a decimal number [0,6]
805+ // %x: the locale’s date, such as %-m/%-d/%Y
806+ ) return ONEDAY ;
807+ if (
808+ / % [ U V W ] / . test ( tickformat )
809+ // %U: Sunday-based week of the year as a decimal number [00,53]
810+ // %V: ISO 8601 week of the year as a decimal number [01, 53]
811+ // %W: Monday-based week of the year as a decimal number [00,53]
812+ ) return ONEWEEK ;
813+ if (
814+ / % [ B b m ] / . test ( tickformat )
815+ // %B: full month name
816+ // %b: abbreviated month name
817+ // %m: month as a decimal number [01,12]
818+ ) return ONEAVGMONTH ;
819+ if (
820+ / % [ q ] / . test ( tickformat )
821+ // %q: quarter of the year as a decimal number [1,4]
822+ ) return ONEAVGQUARTER ;
823+ if (
824+ / % [ Y y ] / . test ( tickformat )
825+ // %Y: year with century as a decimal number, such as 1999
826+ // %y: year without century as a decimal number [00,99]
827+ ) return ONEAVGYEAR ;
828+ return undefined ;
829+ }
769830
831+ function adjustPeriodDelta ( ax ) { // adjusts ax.dtick and sets ax._definedDelta
770832 function mDate ( ) {
771833 return ! (
772834 isNumeric ( ax . dtick ) ||
773835 ax . dtick . charAt ( 0 ) !== 'M'
774836 ) ;
775837 }
776838 var isMDate = mDate ( ) ;
777- var tickformat = axes . getTickFormat ( ax ) ;
778- if ( tickformat ) {
839+ var definedDelta = definedDeltaForTickformat ( axes . getTickFormat ( ax ) ) ;
840+ if ( definedDelta !== undefined ) {
779841 var noDtick = ax . _dtickInit !== ax . dtick ;
780- if (
781- ! ( / % [ f L Q s S M X ] / . test ( tickformat ) )
782- // %f: microseconds as a decimal number [000000, 999999]
783- // %L: milliseconds as a decimal number [000, 999]
784- // %Q: milliseconds since UNIX epoch
785- // %s: seconds since UNIX epoch
786- // %S: second as a decimal number [00,61]
787- // %M: minute as a decimal number [00,59]
788- // %X: the locale’s time, such as %-I:%M:%S %p
789- ) {
790- if (
791- / % [ H I ] / . test ( tickformat )
792- // %H: hour (24-hour clock) as a decimal number [00,23]
793- // %I: hour (12-hour clock) as a decimal number [01,12]
794- ) {
795- definedDelta = ONEHOUR ;
796- if ( noDtick && ! isMDate && ax . dtick < ONEHOUR ) ax . dtick = ONEHOUR ;
797- } else if (
798- / % p / . test ( tickformat ) // %p: either AM or PM
799- ) {
800- definedDelta = HALFDAY ;
801- if ( noDtick && ! isMDate && ax . dtick < HALFDAY ) ax . dtick = HALFDAY ;
802- } else if (
803- / % [ A a d e j u w x ] / . test ( tickformat )
804- // %A: full weekday name
805- // %a: abbreviated weekday name
806- // %d: zero-padded day of the month as a decimal number [01,31]
807- // %e: space-padded day of the month as a decimal number [ 1,31]
808- // %j: day of the year as a decimal number [001,366]
809- // %u: Monday-based (ISO 8601) weekday as a decimal number [1,7]
810- // %w: Sunday-based weekday as a decimal number [0,6]
811- // %x: the locale’s date, such as %-m/%-d/%Y
812- ) {
813- definedDelta = ONEDAY ;
814- if ( noDtick && ! isMDate && ax . dtick < ONEDAY ) ax . dtick = ONEDAY ;
815- } else if (
816- / % [ U V W ] / . test ( tickformat )
817- // %U: Sunday-based week of the year as a decimal number [00,53]
818- // %V: ISO 8601 week of the year as a decimal number [01, 53]
819- // %W: Monday-based week of the year as a decimal number [00,53]
820- ) {
821- definedDelta = ONEWEEK ;
822- if ( noDtick && ! isMDate && ax . dtick < ONEWEEK ) ax . dtick = ONEWEEK ;
823- } else if (
824- / % [ B b m ] / . test ( tickformat )
825- // %B: full month name
826- // %b: abbreviated month name
827- // %m: month as a decimal number [01,12]
828- ) {
829- definedDelta = ONEAVGMONTH ;
830- if ( noDtick && (
831- isMDate ? nMonths ( ax . dtick ) < 1 : ax . dtick < ONEMINMONTH )
832- ) ax . dtick = 'M1' ;
833- } else if (
834- / % [ q ] / . test ( tickformat )
835- // %q: quarter of the year as a decimal number [1,4]
836- ) {
837- definedDelta = ONEAVGQUARTER ;
838- if ( noDtick && (
839- isMDate ? nMonths ( ax . dtick ) < 3 : ax . dtick < ONEMINQUARTER )
840- ) ax . dtick = 'M3' ;
841- } else if (
842- / % [ Y y ] / . test ( tickformat )
843- // %Y: year with century as a decimal number, such as 1999
844- // %y: year without century as a decimal number [00,99]
845- ) {
846- definedDelta = ONEAVGYEAR ;
847- if ( noDtick && (
848- isMDate ? nMonths ( ax . dtick ) < 12 : ax . dtick < ONEMINYEAR )
849- ) ax . dtick = 'M12' ;
850- }
842+ if ( definedDelta === ONEHOUR ) {
843+ if ( noDtick && ! isMDate && ax . dtick < ONEHOUR ) ax . dtick = ONEHOUR ;
844+ } else if ( definedDelta === HALFDAY ) {
845+ if ( noDtick && ! isMDate && ax . dtick < HALFDAY ) ax . dtick = HALFDAY ;
846+ } else if ( definedDelta === ONEDAY ) {
847+ if ( noDtick && ! isMDate && ax . dtick < ONEDAY ) ax . dtick = ONEDAY ;
848+ } else if ( definedDelta === ONEWEEK ) {
849+ if ( noDtick && ! isMDate && ax . dtick < ONEWEEK ) ax . dtick = ONEWEEK ;
850+ } else if ( definedDelta === ONEAVGMONTH ) {
851+ if ( noDtick && (
852+ isMDate ? nMonths ( ax . dtick ) < 1 : ax . dtick < ONEMINMONTH )
853+ ) ax . dtick = 'M1' ;
854+ } else if ( definedDelta === ONEAVGQUARTER ) {
855+ if ( noDtick && (
856+ isMDate ? nMonths ( ax . dtick ) < 3 : ax . dtick < ONEMINQUARTER )
857+ ) ax . dtick = 'M3' ;
858+ } else if ( definedDelta === ONEAVGYEAR ) {
859+ if ( noDtick && (
860+ isMDate ? nMonths ( ax . dtick ) < 12 : ax . dtick < ONEMINYEAR )
861+ ) ax . dtick = 'M12' ;
851862 }
852863 }
853864
@@ -971,6 +982,7 @@ axes.calcTicks = function calcTicks(ax, opts) {
971982 var isReversed = ax . range [ 0 ] > ax . range [ 1 ] ;
972983 var ticklabelIndex = ( ! ax . ticklabelindex || Lib . isArrayOrTypedArray ( ax . ticklabelindex ) ) ?
973984 ax . ticklabelindex : [ ax . ticklabelindex ] ;
985+ ax . _useTicklabelIndex = ticklabelIndex != null && ticklabelIndex !== 0 ;
974986 var rng = Lib . simpleMap ( ax . range , ax . r2l , undefined , undefined , opts ) ;
975987 var axrev = ( rng [ 1 ] < rng [ 0 ] ) ;
976988 var minRange = Math . min ( rng [ 0 ] , rng [ 1 ] ) ;
@@ -993,7 +1005,7 @@ axes.calcTicks = function calcTicks(ax, opts) {
9931005 var hasMinor = ax . minor && ( ax . minor . ticks || ax . minor . showgrid ) ;
9941006 // minor ticks should be calculated if they are visible or if ticklabelindex is set because then
9951007 // the labels are placed at minor ticks (even if invisible) instead of major ticks.
996- var calcMinor = hasMinor || ( ticklabelIndex != null && ticklabelIndex !== 0 ) ;
1008+ var calcMinor = hasMinor || ax . _useTicklabelIndex ;
9971009
9981010 var majorDtick = ax . dtick ;
9991011 // calc major first
@@ -1098,10 +1110,10 @@ axes.calcTicks = function calcTicks(ax, opts) {
10981110
10991111 if ( isPeriod ) {
11001112 // add an additional major tick and correspondingly many minor ticks
1101- // before the first and after the last major tick
1102- // to be able to label a period before the first and last visible ticks.
1113+ // before the first major tick to be able to label the period before the first visible ticks.
11031114 x = axes . tickIncrement ( x , majorDtick , ! axrev , calendar ) ;
1104- endTick = axes . tickIncrement ( endTick , majorDtick , axrev , calendar ) ;
1115+ // same after the last major tick to be able to label the period after the last visible tick.
1116+ if ( ax . _useTicklabelIndex ) endTick = axes . tickIncrement ( endTick , majorDtick , axrev , calendar ) ;
11051117 if ( major ) majorId -- ;
11061118 }
11071119
@@ -1174,17 +1186,19 @@ axes.calcTicks = function calcTicks(ax, opts) {
11741186 // It makes sense if in addition to the always present dummy, there are at least 2 minor ticks
11751187 // with the required distance to each other.
11761188 const visibleMinorTicks = minorTickVals . filter ( ( minorTick ) => minorTick . noTick !== true ) ;
1177- if ( ! visibleMinorTicks || visibleMinorTicks . length < 2 ) {
1178- ticklabelIndex = false ;
1179- } else {
1180- var diff = ( visibleMinorTicks [ 1 ] . value - visibleMinorTicks [ 0 ] . value ) * ( isReversed ? - 1 : 1 ) ;
1181- if ( ! periodCompatibleWithTickformat ( diff , ax . tickformat ) ) {
1182- ticklabelIndex = false ;
1189+ if ( ax . _useTicklabelIndex ) {
1190+ if ( ! visibleMinorTicks || visibleMinorTicks . length < 2 ) {
1191+ ax . _useTicklabelIndex = false ;
1192+ } else {
1193+ var diff = ( visibleMinorTicks [ 1 ] . value - visibleMinorTicks [ 0 ] . value ) * ( isReversed ? - 1 : 1 ) ;
1194+ if ( ! periodCompatibleWithTickformat ( diff , axes . getTickFormat ( ax . minor ) ) ) {
1195+ ax . _useTicklabelIndex = false ;
1196+ }
11831197 }
11841198 }
11851199
11861200 // Determine for which ticks to draw labels
1187- if ( ! ticklabelIndex ) {
1201+ if ( ! ax . _useTicklabelIndex ) {
11881202 allTicklabelVals = tickVals ;
11891203 } else {
11901204 // For each major tick, find the minor tick `ticklabelIndex` steps away.
@@ -1248,7 +1262,10 @@ axes.calcTicks = function calcTicks(ax, opts) {
12481262 minorTickVals = list ;
12491263 }
12501264 }
1251- if ( isPeriod ) positionPeriodTicks ( allTicklabelVals , ax , ax . _definedDelta , periodEndTicks ) ;
1265+ if ( isPeriod ) {
1266+ var periodDefinedDelta = ax . _useTicklabelIndex ? ax . minor . _definedDelta : ax . _definedDelta ;
1267+ positionPeriodTicks ( allTicklabelVals , ax , periodDefinedDelta , periodEndTicks ) ;
1268+ }
12521269
12531270 var i ;
12541271 if ( ax . rangebreaks ) {
@@ -1332,7 +1349,7 @@ axes.calcTicks = function calcTicks(ax, opts) {
13321349 var _value = tickVals [ i ] . value ;
13331350
13341351 if ( _minor ) {
1335- if ( ticklabelIndex && allTicklabelVals . indexOf ( tickVals [ i ] ) !== - 1 ) {
1352+ if ( ax . _useTicklabelIndex && allTicklabelVals . indexOf ( tickVals [ i ] ) !== - 1 ) {
13361353 t = setTickLabel ( ax , tickVals [ i ] ) ;
13371354 } else {
13381355 t = { x : _value , text : '' } ;
@@ -1890,8 +1907,9 @@ function tickTextObj(ax, x, text) {
18901907}
18911908
18921909function formatDate ( ax , out , hover , extraPrecision ) {
1893- var tr = ax . _tickround ;
1894- var fmt = ( hover && ax . hoverformat ) || axes . getTickFormat ( ax ) ;
1910+ var periodAxis = ax . _useTicklabelIndex ? ax . minor : ax ;
1911+ var tr = periodAxis . _tickround ;
1912+ var fmt = ( hover && ax . hoverformat ) || axes . getTickFormat ( periodAxis ) ;
18951913
18961914 // Only apply extra precision if no explicit format was provided.
18971915 extraPrecision = ! fmt && extraPrecision ;
0 commit comments