diff --git a/draftlogs/7735_fix.md b/draftlogs/7735_fix.md new file mode 100644 index 00000000000..aa765e144e1 --- /dev/null +++ b/draftlogs/7735_fix.md @@ -0,0 +1 @@ + - Fix unexpected `ticklabelindex` behavior when minor ticks are not shown. [[#7735](https://github.com/plotly/plotly.js/pull/7735)] diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 38a8a7a8909..98db620e1ba 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -603,7 +603,7 @@ function autoShiftMonthBins(binStart, data, dtick, dataMin, calendar) { // ensure we have minor tick0 and dtick calculated axes.prepMinorTicks = function(mockAx, ax, opts) { - if(!ax.minor.dtick) { + if(!ax.minor?.dtick) { delete mockAx.dtick; var hasMajor = ax.dtick && isNumeric(ax._tmin); var mockMinorRange; @@ -690,7 +690,7 @@ axes.prepMinorTicks = function(mockAx, ax, opts) { // put back the original range, to use to find the full set of minor ticks mockAx.range = ax.range; } - if(ax.minor._tick0Init === undefined) { + if(ax.minor?._tick0Init === undefined) { // ensure identical tick0 mockAx.tick0 = ax.tick0; } @@ -973,21 +973,23 @@ axes.calcTicks = function calcTicks(ax, opts) { var allTicklabelVals = []; var hasMinor = ax.minor && (ax.minor.ticks || ax.minor.showgrid); + // minor ticks should be calculated if they are visible or if ticklabelindex is set because then + // the labels are placed at minor ticks (even if invisible) instead of major ticks. + var calcMinor = hasMinor || ticklabelIndex; // calc major first - for(var major = 1; major >= (hasMinor ? 0 : 1); major--) { + for(var major = 1; major >= (calcMinor ? 0 : 1); major--) { var isMinor = !major; if(major) { ax._dtickInit = ax.dtick; ax._tick0Init = ax.tick0; - } else { + } else if (hasMinor) { ax.minor._dtickInit = ax.minor.dtick; ax.minor._tick0Init = ax.minor.tick0; } - var mockAx = major ? ax : Lib.extendFlat({}, ax, ax.minor); - + var mockAx = major ? ax : Lib.extendFlat({}, ax, hasMinor ? ax.minor : {"minor": {}}); if(isMinor) { axes.prepMinorTicks(mockAx, ax, opts); } else { @@ -1074,10 +1076,11 @@ axes.calcTicks = function calcTicks(ax, opts) { } } - if(major && isPeriod) { - // add one item to label period before tick0 + if((major || ticklabelIndex) && isPeriod) { + // if major: add one item to label period before tick0 + // if minor: add one item for ticklabelindex positioning x = axes.tickIncrement(x, dtick, !axrev, calendar); - majorId--; + if (major) majorId--; } for(; @@ -1126,12 +1129,14 @@ axes.calcTicks = function calcTicks(ax, opts) { } // check if ticklabelIndex makes sense, otherwise ignore it - if(!minorTickVals || minorTickVals.length < 2) { + if(!minorTickVals || minorTickVals.length < 3) { ticklabelIndex = false; } else { - var diff = (minorTickVals[1].value - minorTickVals[0].value) * (isReversed ? -1 : 1); + var diff = (minorTickVals[2].value - minorTickVals[1].value) * (isReversed ? -1 : 1); if(!periodCompatibleWithTickformat(diff, ax.tickformat)) { ticklabelIndex = false; + // remove previously added tick before tick0 for handling ticklabelindex positioning + minorTickVals = minorTickVals.slice(1); } } // Determine for which ticks to draw labels @@ -1298,14 +1303,24 @@ axes.calcTicks = function calcTicks(ax, opts) { } else { lastVisibleHead = ax._prevDateHead; t = setTickLabel(ax, tickVals[i]); - if(tickVals[i].skipLabel || - ticklabelIndex && allTicklabelVals.indexOf(tickVals[i]) === -1) { - hideLabel(t); + if (ticklabelIndex) { + if (allTicklabelVals.indexOf(tickVals[i]) === -1) { + hideLabel(t); + } + } else { + if (tickVals[i].skipLabel) { + hideLabel(t); + } } ticksOut.push(t); } } + + if(isPeriod && ticklabelIndex && minorTicks.length) { + // drop very first minor tick that we added to handle ticklabelindex + minorTicks[0].noTick = true; + } ticksOut = ticksOut.concat(minorTicks); ax._inCalcTicks = false; diff --git a/test/image/baselines/date_axes_period2_ticklabelindex.png b/test/image/baselines/date_axes_period2_ticklabelindex.png index 302f62e1dc0..c4473a55386 100644 Binary files a/test/image/baselines/date_axes_period2_ticklabelindex.png and b/test/image/baselines/date_axes_period2_ticklabelindex.png differ diff --git a/test/image/baselines/date_axes_period_ticklabelindex.png b/test/image/baselines/date_axes_period_ticklabelindex.png index 9bd6b296cc6..49277798a98 100644 Binary files a/test/image/baselines/date_axes_period_ticklabelindex.png and b/test/image/baselines/date_axes_period_ticklabelindex.png differ diff --git a/test/image/baselines/zz-ticklabelindex-hidden-minor-ticks-dtick.png b/test/image/baselines/zz-ticklabelindex-hidden-minor-ticks-dtick.png new file mode 100644 index 00000000000..d5a5bc78c7b Binary files /dev/null and b/test/image/baselines/zz-ticklabelindex-hidden-minor-ticks-dtick.png differ diff --git a/test/image/baselines/zz-ticklabelindex-hidden-minor-ticks-one-label.png b/test/image/baselines/zz-ticklabelindex-hidden-minor-ticks-one-label.png new file mode 100644 index 00000000000..35c50e3af79 Binary files /dev/null and b/test/image/baselines/zz-ticklabelindex-hidden-minor-ticks-one-label.png differ diff --git a/test/image/baselines/zz-ticklabelindex-hidden-minor-ticks-ticklabelstep.png b/test/image/baselines/zz-ticklabelindex-hidden-minor-ticks-ticklabelstep.png new file mode 100644 index 00000000000..0b90011dafc Binary files /dev/null and b/test/image/baselines/zz-ticklabelindex-hidden-minor-ticks-ticklabelstep.png differ diff --git a/test/image/mocks/ticklabelindex-hidden-minor-ticks.json b/test/image/mocks/ticklabelindex-hidden-minor-ticks.json new file mode 100644 index 00000000000..ebd9d1c4537 --- /dev/null +++ b/test/image/mocks/ticklabelindex-hidden-minor-ticks.json @@ -0,0 +1,94 @@ +{ + "data": [ + { + "showlegend": false, + "x": [ + "2023-01-01", + "2024-01-01", + "2025-01-01", + "2026-01-01", + "2027-01-01" + ], + "type": "scatter", + "xaxis": "x", + "yaxis": "y" + }, + { + "showlegend": false, + "x": [ + "2023-01-01", + "2024-01-01", + "2025-01-01", + "2026-01-01" + ], + "type": "scatter", + "xaxis": "x2", + "yaxis": "y2" + }, + { + "showlegend": false, + "x": [ + "2023-01-01", + "2024-01-01", + "2025-01-01", + "2026-01-01", + "2027-01-01" + ], + "type": "scatter", + "xaxis": "x3", + "yaxis": "y3" + } + ], + "layout": { + "width": 700, + "height": 700, + "grid": { + "rows": 3, + "columns": 1, + "pattern": "independent" + }, + "xaxis": { + "ticklen": 20, + "ticklabelindex": -1, + "tickformat": "%Y", + "ticklabelmode": "period", + "dtick": "M24", + "minor": { + "dtick": "M12", + "tick0": "2023-01-01" + }, + "title": { + "text": "Should display 2 ticks and labels 2023 and 2025 to the left of them." + } + }, + "xaxis2": { + "ticklen": 20, + "ticklabelindex": -2, + "tickformat": "%Y", + "ticklabelmode": "period", + "dtick": "M24", + "minor": { + "dtick": "M12", + "tick0": "2023-01-01" + }, + "title": { + "text": "Should display a label at 2024" + } + }, + "xaxis3": { + "ticklen": 20, + "ticklabelindex": -1, + "tickformat": "%Y", + "ticklabelmode": "period", + "ticklabelstep": 2, + "dtick": "M12", + "minor": { + "dtick": "M12", + "tick0": "2023-01-01" + }, + "title": { + "text": "Should display yearly ticks with labels at 2023 and 2025" + } + } + } +}