@@ -882,30 +882,48 @@ function adjustPeriodDelta(ax) { // adjusts ax.dtick and sets ax._definedDelta
882882 ax . _definedDelta = definedDelta ;
883883}
884884
885+ /**
886+ * Calculates the period label position for each tick in tickVals.
887+ * @param {array } tickVals: the list of ticks for which to position the period labels
888+ * @param {object } ax: the axis of the ticks
889+ * @param {number } definedDelta: the defined distance between two ticks
890+ */
885891function positionPeriodTicks ( tickVals , ax , definedDelta ) {
886892 for ( var i = 0 ; i < tickVals . length ; i ++ ) {
887893 var v = tickVals [ i ] . value ;
888-
889- var a = i ;
890- var b = i + 1 ;
891- if ( i < tickVals . length - 1 ) {
892- a = i ;
893- b = i + 1 ;
894- } else if ( i > 0 ) {
895- a = i - 1 ;
896- b = i ;
894+ var A , B ;
895+ if ( tickVals [ i ] . periodEndTick != null ) {
896+ // Will be set when ticklabelindex is used because labeled periods can
897+ // have unlabeled periods between each other
898+ A = tickVals [ i ] . value ;
899+ B = tickVals [ i ] . periodEndTick . value ;
897900 } else {
898- a = i ;
899- b = i ;
901+ // Use the next tick in tickVals as the period end tick
902+ var a = i ;
903+ var b = i + 1 ;
904+ if ( i < tickVals . length - 1 ) {
905+ a = i ;
906+ b = i + 1 ;
907+ } else if ( i > 0 ) {
908+ a = i - 1 ;
909+ b = i ;
910+ } else {
911+ a = i ;
912+ b = i ;
913+ }
914+
915+ A = tickVals [ a ] . value ;
916+ B = tickVals [ b ] . value ;
900917 }
901918
902- var A = tickVals [ a ] . value ;
903- var B = tickVals [ b ] . value ;
904919 var actualDelta = Math . abs ( B - A ) ;
905920 var delta = definedDelta || actualDelta ;
906921 var periodLength = 0 ;
907922
908- if ( delta >= ONEMINYEAR ) {
923+ if ( tickVals [ i ] . periodEndTick != null ) {
924+ // the period end is known exactly, so there is nothing to infer
925+ periodLength = actualDelta ;
926+ } else if ( delta >= ONEMINYEAR ) {
909927 if ( actualDelta >= ONEMINYEAR && actualDelta <= ONEMAXYEAR ) {
910928 periodLength = actualDelta ;
911929 } else {
@@ -978,6 +996,7 @@ axes.calcTicks = function calcTicks(ax, opts) {
978996 var isReversed = ax . range [ 0 ] > ax . range [ 1 ] ;
979997 var ticklabelIndex = ( ! ax . ticklabelindex || Lib . isArrayOrTypedArray ( ax . ticklabelindex ) ) ?
980998 ax . ticklabelindex : [ ax . ticklabelindex ] ;
999+ ax . _useTicklabelIndex = ticklabelIndex != null && ticklabelIndex !== 0 ;
9811000 var rng = Lib . simpleMap ( ax . range , ax . r2l , undefined , undefined , opts ) ;
9821001 var axrev = ( rng [ 1 ] < rng [ 0 ] ) ;
9831002 var minRange = Math . min ( rng [ 0 ] , rng [ 1 ] ) ;
@@ -997,7 +1016,7 @@ axes.calcTicks = function calcTicks(ax, opts) {
9971016 var hasMinor = ax . minor && ( ax . minor . ticks || ax . minor . showgrid ) ;
9981017 // minor ticks should be calculated if they are visible or if ticklabelindex is set because then
9991018 // the labels are placed at minor ticks (even if invisible) instead of major ticks.
1000- var calcMinor = hasMinor || ticklabelIndex ;
1019+ var calcMinor = hasMinor || ax . _useTicklabelIndex ;
10011020
10021021 // calc major first
10031022 for ( var major = 1 ; major >= ( calcMinor ? 0 : 1 ) ; major -- ) {
@@ -1099,7 +1118,7 @@ axes.calcTicks = function calcTicks(ax, opts) {
10991118 }
11001119 }
11011120
1102- if ( ( major || ticklabelIndex ) && isPeriod ) {
1121+ if ( ( major || ax . _useTicklabelIndex ) && isPeriod ) {
11031122 // if major: add one item to label period before tick0
11041123 // if minor: add one item for ticklabelindex positioning. positionPeriodTicks requires
11051124 // at least 2 ticks to calculate the period length, so we add a dummy tick, ensuring
@@ -1156,93 +1175,104 @@ axes.calcTicks = function calcTicks(ax, opts) {
11561175 }
11571176 }
11581177
1178+ function findOverlappingTick ( minorTick , majorTicks ) {
1179+ const majorTick = majorTicks . find ( ( majorTick ) => majorTick . value === minorTick . value ) ;
1180+ if ( majorTick != null ) {
1181+ return majorTick ;
1182+ }
1183+ // add 10e6 to eliminate problematic digits
1184+ const epsilon = 10e6 ;
1185+ for ( let i = 0 ; i < majorTicks . length ; i ++ ) {
1186+ if ( epsilon + majorTicks [ i ] . value === epsilon + minorTick . value ) {
1187+ return majorTicks [ i ] ;
1188+ }
1189+ }
1190+ return null ;
1191+ } ;
1192+
11591193 // check if ticklabelIndex makes sense, otherwise ignore it.
11601194 // It makes sense if in addition to the always present dummy, there are at least 2 minor ticks
11611195 // with the required distance to each other.
11621196 if ( ! minorTickVals || minorTickVals . length < 3 ) {
1163- ticklabelIndex = false ;
1197+ ax . _useTicklabelIndex = false ;
11641198 } else {
1165- var diff = ( minorTickVals [ 2 ] . value - minorTickVals [ 1 ] . value ) * ( isReversed ? - 1 : 1 ) ;
1199+ const diff = ( minorTickVals [ 2 ] . value - minorTickVals [ 1 ] . value ) * ( isReversed ? - 1 : 1 ) ;
11661200 if ( ! periodCompatibleWithTickformat ( diff , ax . tickformat ) ) {
1167- ticklabelIndex = false ;
1201+ ax . _useTicklabelIndex = false ;
11681202 // remove previously added tick before tick0 for handling ticklabelindex positioning
11691203 minorTickVals = minorTickVals . slice ( 1 ) ;
11701204 }
11711205 }
1206+
11721207 // Determine for which ticks to draw labels
1173- if ( ! ticklabelIndex ) {
1208+ if ( ! ax . _useTicklabelIndex ) {
11741209 allTicklabelVals = tickVals ;
11751210 } else {
1176- // Collect and sort all major and minor ticks, to find the minor ticks `ticklabelIndex`
1177- // steps away from each major tick. For those minor ticks we want to draw the label .
1211+ // For each major tick, find the minor tick `ticklabelIndex` steps away.
1212+ // This minor tick will be labeled instead of the major tick .
11781213
1179- var allTickVals = tickVals . concat ( minorTickVals ) ;
1180- if ( isPeriod && tickVals . length ) {
1214+ // if a minor tick coincides with a major tick, drop the minor tick and keep the major one
1215+ // because overlapping minor ticks are stripped away if both minor and major ticks are drawn on the same side.
1216+ const filteredMinors = minorTickVals . filter ( ( minor ) => findOverlappingTick ( minor , tickVals ) == null ) ;
1217+ const labelTickValsAscending = tickVals . concat ( filteredMinors )
1218+ . sort ( ( a , b ) => a . value - b . value ) ;
1219+
1220+ if ( isPeriod && tickVals . length ) {
11811221 // first major tick was just added for period handling
1182- allTickVals = allTickVals . slice ( 1 ) ;
1222+ tickVals [ 0 ] . skipLabel = true ;
11831223 }
1184-
1185- allTickVals =
1186- allTickVals
1187- . sort ( function ( a , b ) { return a . value - b . value ; } )
1188- . filter ( function ( tick , index , self ) {
1189- return index === 0 || tick . value !== self [ index - 1 ] . value ;
1190- } ) ;
1191-
1192- var majorTickIndices =
1193- allTickVals
1194- . map ( function ( item , index ) {
1195- return item . minor === undefined && ! item . skipLabel ? index : null ;
1196- } )
1197- . filter ( function ( index ) { return index !== null ; } ) ;
1198-
1199- majorTickIndices . forEach ( function ( majorIdx ) {
1200- ticklabelIndex . map ( function ( nextLabelIdx ) {
1201- var minorIdx = majorIdx + nextLabelIdx ;
1202- if ( minorIdx >= 0 && minorIdx < allTickVals . length ) {
1203- Lib . pushUnique ( allTicklabelVals , allTickVals [ minorIdx ] ) ;
1204- }
1205- } ) ;
1206- } ) ;
1207- tickVals . forEach ( function ( tick ) {
1208- tick . skipLabel = allTicklabelVals . indexOf ( tick ) === - 1 ;
1224+ tickVals . forEach ( function ( majorTick ) {
1225+ if ( ! majorTick . skipLabel ) {
1226+ ticklabelIndex . forEach ( ( labelIndex ) => {
1227+ if ( labelIndex < 0 ) {
1228+ const smallerTicks = labelTickValsAscending . filter ( ( minorTick ) => minorTick . value <= majorTick . value ) ;
1229+ const absLabelIndex = Math . abs ( labelIndex ) ;
1230+ const labeledTickIndex = smallerTicks . length - absLabelIndex - 1 ;
1231+ if ( absLabelIndex < smallerTicks . length ) {
1232+ const tickToLabel = smallerTicks [ labeledTickIndex ] ;
1233+ allTicklabelVals . push ( tickToLabel ) ;
1234+ if ( isPeriod ) {
1235+ tickToLabel . periodEndTick = smallerTicks [ labeledTickIndex + 1 ] ;
1236+ }
1237+ }
1238+ } else { // labelIndex >= 0
1239+ const largerTicks = labelTickValsAscending . filter ( ( minorTick ) => minorTick . value >= majorTick . value ) ;
1240+ if ( labelIndex < largerTicks . length ) {
1241+ const tickToLabel = largerTicks [ labelIndex ] ;
1242+ allTicklabelVals . push ( tickToLabel ) ;
1243+ if ( isPeriod && largerTicks [ labelIndex + 1 ] ) {
1244+ tickToLabel . periodEndTick = largerTicks [ labelIndex + 1 ] ;
1245+ }
1246+ }
1247+ }
1248+ } ) ;
1249+ // Skip the major tick label since the label moved to a minor tick
1250+ majorTick . skipLabel = true ;
1251+ }
12091252 } ) ;
1253+ allTicklabelVals . forEach ( ( t ) => t . skipLabel = false ) ;
12101254 }
12111255
12121256 if ( hasMinor ) {
1213- var canOverlap =
1257+ var allowedToOverlap =
12141258 ( ax . minor . ticks === 'inside' && ax . ticks === 'outside' ) ||
12151259 ( ax . minor . ticks === 'outside' && ax . ticks === 'inside' ) ;
12161260
1217- if ( ! canOverlap ) {
1261+ if ( ! allowedToOverlap ) {
12181262 // remove duplicate minors
1219-
1220- var majorValues = tickVals . map ( function ( d ) { return d . value ; } ) ;
1221-
12221263 var list = [ ] ;
12231264 for ( var k = 0 ; k < minorTickVals . length ; k ++ ) {
1224- var T = minorTickVals [ k ] ;
1225- var v = T . value ;
1226- if ( majorValues . indexOf ( v ) !== - 1 ) {
1227- continue ;
1228- }
1229- var found = false ;
1230- for ( var q = 0 ; ! found && ( q < tickVals . length ) ; q ++ ) {
1231- if (
1232- // add 10e6 to eliminate problematic digits
1233- 10e6 + tickVals [ q ] . value ===
1234- 10e6 + v
1235- ) {
1236- found = true ;
1237- }
1265+ if ( findOverlappingTick ( minorTickVals [ k ] , tickVals ) == null ) {
1266+ list . push ( minorTickVals [ k ] ) ;
12381267 }
1239- if ( ! found ) list . push ( T ) ;
12401268 }
12411269 minorTickVals = list ;
12421270 }
12431271 }
12441272
1245- if ( isPeriod ) positionPeriodTicks ( allTicklabelVals , ax , ax . _definedDelta ) ;
1273+ if ( isPeriod ) {
1274+ positionPeriodTicks ( allTicklabelVals , ax , ax . _definedDelta ) ;
1275+ }
12461276
12471277 var i ;
12481278 if ( ax . rangebreaks ) {
@@ -1328,7 +1358,7 @@ axes.calcTicks = function calcTicks(ax, opts) {
13281358 var _value = tickVals [ i ] . value ;
13291359
13301360 if ( _minor ) {
1331- if ( ticklabelIndex && allTicklabelVals . indexOf ( tickVals [ i ] ) !== - 1 ) {
1361+ if ( ax . _useTicklabelIndex && allTicklabelVals . indexOf ( tickVals [ i ] ) !== - 1 ) {
13321362 t = setTickLabel ( ax , tickVals [ i ] ) ;
13331363 } else {
13341364 t = { x : _value } ;
@@ -1346,7 +1376,7 @@ axes.calcTicks = function calcTicks(ax, opts) {
13461376 }
13471377 }
13481378
1349- if ( isPeriod && ticklabelIndex && minorTicks . length ) {
1379+ if ( isPeriod && ax . _useTicklabelIndex && minorTicks . length ) {
13501380 // drop very first minor tick that we added to handle ticklabelindex
13511381 minorTicks [ 0 ] . noTick = true ;
13521382 }
0 commit comments