Skip to content

Commit d6bd654

Browse files
authored
Merge pull request #7735 from my-tien/calculate-hidden-minor-ticks
Calculate hidden minor ticks if ticklabelindex is set.
2 parents 4ce088e + 918dedc commit d6bd654

7 files changed

Lines changed: 127 additions & 15 deletions

File tree

draftlogs/7735_fix.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Fix unexpected `ticklabelindex` behavior when minor ticks are not shown. [[#7735](https://github.com/plotly/plotly.js/pull/7735)], with thanks to @my-tien for the contribution!

src/plots/cartesian/axes.js

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,7 @@ function autoShiftMonthBins(binStart, data, dtick, dataMin, calendar) {
603603

604604
// ensure we have minor tick0 and dtick calculated
605605
axes.prepMinorTicks = function(mockAx, ax, opts) {
606-
if(!ax.minor.dtick) {
606+
if(!ax.minor?.dtick) {
607607
delete mockAx.dtick;
608608
var hasMajor = ax.dtick && isNumeric(ax._tmin);
609609
var mockMinorRange;
@@ -690,7 +690,7 @@ axes.prepMinorTicks = function(mockAx, ax, opts) {
690690
// put back the original range, to use to find the full set of minor ticks
691691
mockAx.range = ax.range;
692692
}
693-
if(ax.minor._tick0Init === undefined) {
693+
if(ax.minor?._tick0Init === undefined) {
694694
// ensure identical tick0
695695
mockAx.tick0 = ax.tick0;
696696
}
@@ -973,21 +973,23 @@ axes.calcTicks = function calcTicks(ax, opts) {
973973
var allTicklabelVals = [];
974974

975975
var hasMinor = ax.minor && (ax.minor.ticks || ax.minor.showgrid);
976+
// minor ticks should be calculated if they are visible or if ticklabelindex is set because then
977+
// the labels are placed at minor ticks (even if invisible) instead of major ticks.
978+
var calcMinor = hasMinor || ticklabelIndex;
976979

977980
// calc major first
978-
for(var major = 1; major >= (hasMinor ? 0 : 1); major--) {
981+
for(var major = 1; major >= (calcMinor ? 0 : 1); major--) {
979982
var isMinor = !major;
980983

981984
if(major) {
982985
ax._dtickInit = ax.dtick;
983986
ax._tick0Init = ax.tick0;
984-
} else {
987+
} else if (calcMinor) {
985988
ax.minor._dtickInit = ax.minor.dtick;
986989
ax.minor._tick0Init = ax.minor.tick0;
987990
}
988991

989-
var mockAx = major ? ax : Lib.extendFlat({}, ax, ax.minor);
990-
992+
var mockAx = major ? ax : Lib.extendFlat({}, ax, calcMinor ? ax.minor : {"minor": {}});
991993
if(isMinor) {
992994
axes.prepMinorTicks(mockAx, ax, opts);
993995
} else {
@@ -1074,10 +1076,13 @@ axes.calcTicks = function calcTicks(ax, opts) {
10741076
}
10751077
}
10761078

1077-
if(major && isPeriod) {
1078-
// add one item to label period before tick0
1079+
if((major || ticklabelIndex) && isPeriod) {
1080+
// if major: add one item to label period before tick0
1081+
// if minor: add one item for ticklabelindex positioning. positionPeriodTicks requires
1082+
// at least 2 ticks to calculate the period length, so we add a dummy tick, ensuring
1083+
// that if a tick is labeled, there are always at least 2 ticks.
10791084
x = axes.tickIncrement(x, dtick, !axrev, calendar);
1080-
majorId--;
1085+
if (major) majorId--;
10811086
}
10821087

10831088
for(;
@@ -1125,13 +1130,17 @@ axes.calcTicks = function calcTicks(ax, opts) {
11251130
}
11261131
}
11271132

1128-
// check if ticklabelIndex makes sense, otherwise ignore it
1129-
if(!minorTickVals || minorTickVals.length < 2) {
1133+
// check if ticklabelIndex makes sense, otherwise ignore it.
1134+
// It makes sense if in addition to the always present dummy, there are at least 2 minor ticks
1135+
// with the required distance to each other.
1136+
if(!minorTickVals || minorTickVals.length < 3) {
11301137
ticklabelIndex = false;
11311138
} else {
1132-
var diff = (minorTickVals[1].value - minorTickVals[0].value) * (isReversed ? -1 : 1);
1139+
var diff = (minorTickVals[2].value - minorTickVals[1].value) * (isReversed ? -1 : 1);
11331140
if(!periodCompatibleWithTickformat(diff, ax.tickformat)) {
11341141
ticklabelIndex = false;
1142+
// remove previously added tick before tick0 for handling ticklabelindex positioning
1143+
minorTickVals = minorTickVals.slice(1);
11351144
}
11361145
}
11371146
// Determine for which ticks to draw labels
@@ -1169,6 +1178,9 @@ axes.calcTicks = function calcTicks(ax, opts) {
11691178
}
11701179
});
11711180
});
1181+
tickVals.forEach(function(tick) {
1182+
tick.skipLabel = allTicklabelVals.indexOf(tick) === -1;
1183+
});
11721184
}
11731185

11741186
if(hasMinor) {
@@ -1298,14 +1310,18 @@ axes.calcTicks = function calcTicks(ax, opts) {
12981310
} else {
12991311
lastVisibleHead = ax._prevDateHead;
13001312
t = setTickLabel(ax, tickVals[i]);
1301-
if(tickVals[i].skipLabel ||
1302-
ticklabelIndex && allTicklabelVals.indexOf(tickVals[i]) === -1) {
1313+
if (tickVals[i].skipLabel) {
13031314
hideLabel(t);
13041315
}
13051316

13061317
ticksOut.push(t);
13071318
}
13081319
}
1320+
1321+
if(isPeriod && ticklabelIndex && minorTicks.length) {
1322+
// drop very first minor tick that we added to handle ticklabelindex
1323+
minorTicks[0].noTick = true;
1324+
}
13091325
ticksOut = ticksOut.concat(minorTicks);
13101326

13111327
ax._inCalcTicks = false;

src/plots/cartesian/axis_defaults.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,9 +154,10 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce,
154154
attributes: layoutAttributes
155155
});
156156

157-
// delete minor when no minor ticks or gridlines
157+
// delete minor when no minor ticks or gridlines and no hidden minor ticks are needed
158158
if(
159159
hasMinor &&
160+
containerOut.ticklabelindex == null &&
160161
!containerOut.minor.ticks &&
161162
!containerOut.minor.showgrid
162163
) {
550 Bytes
Loading
584 Bytes
Loading
49.4 KB
Loading
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
{
2+
"data": [
3+
{
4+
"showlegend": false,
5+
"x": [
6+
"2023-01-01",
7+
"2024-01-01",
8+
"2025-01-01",
9+
"2026-01-01",
10+
"2027-01-01"
11+
],
12+
"type": "scatter",
13+
"xaxis": "x",
14+
"yaxis": "y"
15+
},
16+
{
17+
"showlegend": false,
18+
"x": [
19+
"2023-01-01",
20+
"2024-01-01",
21+
"2025-01-01",
22+
"2026-01-01"
23+
],
24+
"type": "scatter",
25+
"xaxis": "x2",
26+
"yaxis": "y2"
27+
},
28+
{
29+
"showlegend": false,
30+
"x": [
31+
"2023-01-01",
32+
"2024-01-01",
33+
"2025-01-01",
34+
"2026-01-01",
35+
"2027-01-01"
36+
],
37+
"type": "scatter",
38+
"xaxis": "x3",
39+
"yaxis": "y3"
40+
}
41+
],
42+
"layout": {
43+
"width": 700,
44+
"height": 700,
45+
"grid": {
46+
"rows": 3,
47+
"columns": 1,
48+
"pattern": "independent"
49+
},
50+
"xaxis": {
51+
"ticklen": 20,
52+
"ticklabelindex": -1,
53+
"tickformat": "%Y",
54+
"ticklabelmode": "period",
55+
"dtick": "M24",
56+
"minor": {
57+
"dtick": "M12",
58+
"tick0": "2023-01-01"
59+
},
60+
"title": {
61+
"text": "Should display 2 ticks and labels 2023 and 2025 to the left of them."
62+
}
63+
},
64+
"xaxis2": {
65+
"ticklen": 20,
66+
"ticklabelindex": -2,
67+
"tickformat": "%Y",
68+
"ticklabelmode": "period",
69+
"dtick": "M24",
70+
"minor": {
71+
"dtick": "M12",
72+
"tick0": "2023-01-01"
73+
},
74+
"title": {
75+
"text": "Should display a label at 2024"
76+
}
77+
},
78+
"xaxis3": {
79+
"ticklen": 20,
80+
"ticklabelindex": -1,
81+
"tickformat": "%Y",
82+
"ticklabelmode": "period",
83+
"ticklabelstep": 2,
84+
"dtick": "M12",
85+
"minor": {
86+
"dtick": "M12",
87+
"tick0": "2023-01-01"
88+
},
89+
"title": {
90+
"text": "Should display yearly ticks with labels at 2023 and 2025"
91+
}
92+
}
93+
}
94+
}

0 commit comments

Comments
 (0)