Skip to content
1 change: 1 addition & 0 deletions draftlogs/7937_fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Fix missing period label in last period when using a negative ticklabelindex [[#7937](https://github.com/plotly/plotly.js/pull/7937)]
82 changes: 43 additions & 39 deletions src/plots/cartesian/axes.js
Original file line number Diff line number Diff line change
Expand Up @@ -1024,6 +1024,7 @@ axes.calcTicks = function calcTicks(ax, opts) {
// the labels are placed at minor ticks (even if invisible) instead of major ticks.
var calcMinor = hasMinor || ax._useTicklabelIndex;

var majorDtick = ax.dtick;
// calc major first
for(var major = 1; major >= (calcMinor ? 0 : 1); major--) {
var isMinor = !major;
Expand All @@ -1041,6 +1042,7 @@ axes.calcTicks = function calcTicks(ax, opts) {
axes.prepMinorTicks(mockAx, ax, opts);
} else {
axes.prepTicks(mockAx, opts);
majorDtick = mockAx.dtick;
}

// now that we've figured out the auto values for formatting
Expand All @@ -1067,9 +1069,11 @@ axes.calcTicks = function calcTicks(ax, opts) {
var exRng = expandRange(rng);
var startTick = exRng[0];
var endTick = exRng[1];
var visibleEndTick = endTick; // for period axis, there will be an additional major tick after that
var dtick = mockAx.dtick;

var numDtick = isNumeric(mockAx.dtick);
var isDLog = (type === 'log') && !(numDtick || mockAx.dtick.charAt(0) === 'L');
var numDtick = isNumeric(dtick);
var isDLog = (type === 'log') && !(numDtick || dtick.charAt(0) === 'L');

// find the first tick
var x0 = axes.tickFirst(mockAx, opts);
Expand Down Expand Up @@ -1113,7 +1117,6 @@ axes.calcTicks = function calcTicks(ax, opts) {
) / _dTick) - 1;
}

var dtick = mockAx.dtick;
var tick0l = (type === 'linear') ? mockAx.r2l(mockAx.tick0) : undefined;

if(mockAx.rangebreaks && mockAx._tick0Init !== mockAx.tick0) {
Expand All @@ -1125,11 +1128,11 @@ axes.calcTicks = function calcTicks(ax, opts) {
}

if((major || ax._useTicklabelIndex) && isPeriod) {
// if major: add one item to label period before tick0
// if minor: add one item for ticklabelindex positioning. positionPeriodTicks requires
// at least 2 ticks to calculate the period length, so we add a dummy tick, ensuring
// that if a tick is labeled, there are always at least 2 ticks.
x = axes.tickIncrement(x, dtick, !axrev, calendar);
// add an additional major tick and correspondingly many minor ticks
// before the first major tick to be able to label the period before the first visible ticks.
x = axes.tickIncrement(x, majorDtick, !axrev, calendar);
// same after the last major tick to be able to label the period after the last visible tick.
if (ax._useTicklabelIndex) endTick = axes.tickIncrement(endTick, majorDtick, axrev, calendar);
if (major) majorId--;
}

Expand Down Expand Up @@ -1160,6 +1163,11 @@ axes.calcTicks = function calcTicks(ax, opts) {

var obj = { value: snapToGrid(x, tick0l, dtick) };

// mark ticks that were only added for period label positioning as "noTick" so they aren't drawn.
if (axrev ? (x > startTick || x < visibleEndTick) : (x < startTick || x > visibleEndTick)) {
obj.noTick = true;
}

if(major) {
// mark first major tick, for showexponent/showtickprefix/showticksuffix 'first'
if(x === x0) obj.first = true;
Expand Down Expand Up @@ -1199,14 +1207,15 @@ axes.calcTicks = function calcTicks(ax, opts) {
// check if ticklabelIndex makes sense, otherwise ignore it.
// It makes sense if in addition to the always present dummy, there are at least 2 minor ticks
// with the required distance to each other.
if(!minorTickVals || minorTickVals.length < 3) {
ax._useTicklabelIndex = false;
} else {
const diff = (minorTickVals[2].value - minorTickVals[1].value) * (isReversed ? -1 : 1);
if(!periodCompatibleWithTickformat(diff, ax.tickformat)) {
if (ax._useTicklabelIndex) {
const visibleMinorTicks = minorTickVals.filter((minorTick) => !minorTick.noTick);
if (visibleMinorTicks.length < 2) {
ax._useTicklabelIndex = false;
// remove previously added tick before tick0 for handling ticklabelindex positioning
minorTickVals = minorTickVals.slice(1);
} else {
const diff = (visibleMinorTicks[1].value - visibleMinorTicks[0].value) * (isReversed ? -1 : 1);
if(!periodCompatibleWithTickformat(diff, ax.tickformat)) {
ax._useTicklabelIndex = false;
}
}
}

Expand All @@ -1223,10 +1232,6 @@ axes.calcTicks = function calcTicks(ax, opts) {
const labelTickValsAscending = tickVals.concat(filteredMinors)
.sort((a, b) => a.value - b.value);

if (isPeriod && tickVals.length) {
// first major tick was just added for period handling
tickVals[0].skipLabel = true;
}
tickVals.forEach(function(majorTick) {
if (!majorTick.skipLabel) {
ticklabelIndex.forEach((labelIndex) => {
Expand All @@ -1236,18 +1241,26 @@ axes.calcTicks = function calcTicks(ax, opts) {
const labeledTickIndex = smallerTicks.length - absLabelIndex - 1;
if (absLabelIndex < smallerTicks.length) {
const tickToLabel = smallerTicks[labeledTickIndex];
allTicklabelVals.push(tickToLabel);
if (isPeriod) {
tickToLabel.periodEndTick = smallerTicks[labeledTickIndex + 1];
const periodEndTick = isPeriod ? smallerTicks[labeledTickIndex + 1] : undefined;
const labelVisible = !tickToLabel.noTick || (periodEndTick && !periodEndTick.noTick);
if (labelVisible) {
allTicklabelVals.push(tickToLabel);
if (periodEndTick && !periodEndTick.noTick) {
tickToLabel.periodEndTick = periodEndTick;
}
}
}
} else { // labelIndex >= 0
const largerTicks = labelTickValsAscending.filter((minorTick) => minorTick.value >= majorTick.value);
if (labelIndex < largerTicks.length) {
if (labelIndex < largerTicks.length && !largerTicks[labelIndex].noTick) {
const tickToLabel = largerTicks[labelIndex];
allTicklabelVals.push(tickToLabel);
if (isPeriod && largerTicks[labelIndex + 1]) {
tickToLabel.periodEndTick = largerTicks[labelIndex + 1];
const periodEndTick = isPeriod ? largerTicks[labelIndex + 1] : undefined;
const labelVisible = !tickToLabel.noTick || (periodEndTick && !periodEndTick.noTick);
if (labelVisible) {
allTicklabelVals.push(tickToLabel);
if (periodEndTick && !periodEndTick.noTick) {
tickToLabel.periodEndTick = periodEndTick;
}
}
}
}
Expand Down Expand Up @@ -1367,8 +1380,9 @@ axes.calcTicks = function calcTicks(ax, opts) {
if(ax._useTicklabelIndex && allTicklabelVals.indexOf(tickVals[i]) !== -1) {
t = setTickLabel(ax, tickVals[i]);
} else {
t = { x: _value };
t = { x: _value, text: ''};
}
t.noTick = tickVals[i].noTick;
t.minor = true;
minorTicks.push(t);
} else {
Expand All @@ -1377,24 +1391,14 @@ axes.calcTicks = function calcTicks(ax, opts) {
if (tickVals[i].skipLabel) {
hideLabel(t);
}

t.noTick = tickVals[i].noTick;
ticksOut.push(t);
}
}

if(isPeriod && ax._useTicklabelIndex && minorTicks.length) {
// drop very first minor tick that we added to handle ticklabelindex
minorTicks[0].noTick = true;
}
ticksOut = ticksOut.concat(minorTicks);

ax._inCalcTicks = false;

if(isPeriod && ticksOut.length) {
// drop very first tick that we added to handle period
ticksOut[0].noTick = true;
}

ticksOut = ticksOut.concat(minorTicks);
return ticksOut;
};

Expand Down
Binary file modified test/image/baselines/date_axes_period2_ticklabelindex.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/date_axes_period_ticklabelindex.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/ticklabelindex-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/ticklabelindex.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 35 additions & 4 deletions test/image/mocks/ticklabelindex-2.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
{
"data": [
{
"x": [
"2020", "2021", "2022"
],
"y": [
3, 2, 1
]
},
{
"mode": "markers+text",
"texttemplate": "%{x|Q%q %y}",
Expand All @@ -9,13 +17,36 @@
],
"y": [
2, 2, 2, 2, 2
]
],
"yaxis": "y2",
"xaxis": "x2"
}
],
"layout": {
"width": 600,
"height": 400,
"height": 800,
"grid": {
"rows": 2,
"columns": 1,
"pattern": "independent"
},
"xaxis": {
"dtick": "M12",
"insiderange": [
"2020-03-27",
"2022-07-21"
],
"tickformat": "%Y",
"ticklabelindex": -1,
"ticks": "outside",
"ticklen": 20,
"ticklabelmode": "period",
"type": "date",
"title": {
"text": "Should display 2 major ticks and labels 2020, 2021, 2022"
}
},
"xaxis2": {
"dtick": "M24",
"insiderange": [
"2019-03-01",
Expand All @@ -28,9 +59,9 @@
"ticklen": 20,
"type": "date",
"ticks": "outside",
"minor": { "dtick": "M12" },
"minor": { "dtick": "M12", "ticks": "outside", "ticklen": 5 },
"title": {
"text": "Should display 1 major tick and a label 2020."
"text": "Should display 1 major tick, 1 minor tick and a label 2020 in between."
}
}
}
Expand Down
Loading