Skip to content

Commit 7d02d9c

Browse files
authored
Merge pull request #7036 from my-tien/custom_tick_label_display
Property `ticklabelindex` for custom tick label display
2 parents 8b1f5bd + f8974e0 commit 7d02d9c

19 files changed

+1242
-29
lines changed

draftlogs/7006_add.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
- Add property `ticklabelstandoff` and `ticklabelshift` to cartesian axes to adjust positioning of tick labels [[#7006](https://github.com/plotly/plotly.js/pull/7006)]
1+
- Add property `ticklabelstandoff` and `ticklabelshift` to cartesian axes to adjust positioning of tick labels, with thanks to @my-tien for the contribution! [[#7006](https://github.com/plotly/plotly.js/pull/7006)]

draftlogs/7036_add.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Add axis property `ticklabelindex` for drawing the label for each minor tick n positions away from a major tick, with thanks to @my-tien for the contribution! [[#7036](https://github.com/plotly/plotly.js/pull/7036)]

src/constants/numerical.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ module.exports = {
3737
ONEHOUR: 3600000,
3838
ONEMIN: 60000,
3939
ONESEC: 1000,
40-
40+
ONEMILLI: 1,
41+
ONEMICROSEC: 0.001,
4142
/*
4243
* For fast conversion btwn world calendars and epoch ms, the Julian Day Number
4344
* of the unix epoch. From calendars.instance().newDate(1970, 1, 1).toJD()

src/plots/cartesian/axes.js

+111-27
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ var HALFDAY = ONEDAY / 2;
3131
var ONEHOUR = constants.ONEHOUR;
3232
var ONEMIN = constants.ONEMIN;
3333
var ONESEC = constants.ONESEC;
34+
var ONEMILLI = constants.ONEMILLI;
35+
var ONEMICROSEC = constants.ONEMICROSEC;
3436
var MINUS_SIGN = constants.MINUS_SIGN;
3537
var BADNUM = constants.BADNUM;
3638

@@ -908,7 +910,9 @@ axes.calcTicks = function calcTicks(ax, opts) {
908910
var calendar = ax.calendar;
909911
var ticklabelstep = ax.ticklabelstep;
910912
var isPeriod = ax.ticklabelmode === 'period';
911-
913+
var isReversed = ax.range[0] > ax.range[1];
914+
var ticklabelIndex = (!ax.ticklabelindex || Lib.isArrayOrTypedArray(ax.ticklabelindex)) ?
915+
ax.ticklabelindex : [ax.ticklabelindex];
912916
var rng = Lib.simpleMap(ax.range, ax.r2l, undefined, undefined, opts);
913917
var axrev = (rng[1] < rng[0]);
914918
var minRange = Math.min(rng[0], rng[1]);
@@ -921,6 +925,9 @@ axes.calcTicks = function calcTicks(ax, opts) {
921925

922926
var tickVals = [];
923927
var minorTickVals = [];
928+
// all ticks for which labels are drawn which is not necessarily the major ticks when
929+
// `ticklabelindex` is set.
930+
var allTicklabelVals = [];
924931

925932
var hasMinor = ax.minor && (ax.minor.ticks || ax.minor.showgrid);
926933

@@ -1075,6 +1082,52 @@ axes.calcTicks = function calcTicks(ax, opts) {
10751082
}
10761083
}
10771084

1085+
// check if ticklabelIndex makes sense, otherwise ignore it
1086+
if(!minorTickVals || minorTickVals.length < 2) {
1087+
ticklabelIndex = false;
1088+
} else {
1089+
var diff = (minorTickVals[1].value - minorTickVals[0].value) * (isReversed ? -1 : 1);
1090+
if(!periodCompatibleWithTickformat(diff, ax.tickformat)) {
1091+
ticklabelIndex = false;
1092+
}
1093+
}
1094+
// Determine for which ticks to draw labels
1095+
if(!ticklabelIndex) {
1096+
allTicklabelVals = tickVals;
1097+
} else {
1098+
// Collect and sort all major and minor ticks, to find the minor ticks `ticklabelIndex`
1099+
// steps away from each major tick. For those minor ticks we want to draw the label.
1100+
1101+
var allTickVals = tickVals.concat(minorTickVals);
1102+
if(isPeriod && tickVals.length) {
1103+
// first major tick was just added for period handling
1104+
allTickVals = allTickVals.slice(1);
1105+
}
1106+
1107+
allTickVals =
1108+
allTickVals
1109+
.sort(function(a, b) { return a.value - b.value; })
1110+
.filter(function(tick, index, self) {
1111+
return index === 0 || tick.value !== self[index - 1].value;
1112+
});
1113+
1114+
var majorTickIndices =
1115+
allTickVals
1116+
.map(function(item, index) {
1117+
return item.minor === undefined && !item.skipLabel ? index : null;
1118+
})
1119+
.filter(function(index) { return index !== null; });
1120+
1121+
majorTickIndices.forEach(function(majorIdx) {
1122+
ticklabelIndex.map(function(nextLabelIdx) {
1123+
var minorIdx = majorIdx + nextLabelIdx;
1124+
if(minorIdx >= 0 && minorIdx < allTickVals.length) {
1125+
Lib.pushUnique(allTicklabelVals, allTickVals[minorIdx]);
1126+
}
1127+
});
1128+
});
1129+
}
1130+
10781131
if(hasMinor) {
10791132
var canOverlap =
10801133
(ax.minor.ticks === 'inside' && ax.ticks === 'outside') ||
@@ -1108,7 +1161,7 @@ axes.calcTicks = function calcTicks(ax, opts) {
11081161
}
11091162
}
11101163

1111-
if(isPeriod) positionPeriodTicks(tickVals, ax, ax._definedDelta);
1164+
if(isPeriod) positionPeriodTicks(allTicklabelVals, ax, ax._definedDelta);
11121165

11131166
var i;
11141167
if(ax.rangebreaks) {
@@ -1166,38 +1219,44 @@ axes.calcTicks = function calcTicks(ax, opts) {
11661219

11671220
tickVals = tickVals.concat(minorTickVals);
11681221

1169-
var t, p;
1222+
function setTickLabel(ax, tickVal) {
1223+
var text = axes.tickText(
1224+
ax,
1225+
tickVal.value,
1226+
false, // hover
1227+
tickVal.simpleLabel // noSuffixPrefix
1228+
);
1229+
var p = tickVal.periodX;
1230+
if(p !== undefined) {
1231+
text.periodX = p;
1232+
if(p > maxRange || p < minRange) { // hide label if outside the range
1233+
if(p > maxRange) text.periodX = maxRange;
1234+
if(p < minRange) text.periodX = minRange;
1235+
1236+
hideLabel(text);
1237+
}
1238+
}
1239+
return text;
1240+
}
1241+
1242+
var t;
11701243
for(i = 0; i < tickVals.length; i++) {
11711244
var _minor = tickVals[i].minor;
11721245
var _value = tickVals[i].value;
11731246

11741247
if(_minor) {
1175-
minorTicks.push({
1176-
x: _value,
1177-
minor: true
1178-
});
1248+
if(ticklabelIndex && allTicklabelVals.indexOf(tickVals[i]) !== -1) {
1249+
t = setTickLabel(ax, tickVals[i]);
1250+
} else {
1251+
t = { x: _value };
1252+
}
1253+
t.minor = true;
1254+
minorTicks.push(t);
11791255
} else {
11801256
lastVisibleHead = ax._prevDateHead;
1181-
1182-
t = axes.tickText(
1183-
ax,
1184-
_value,
1185-
false, // hover
1186-
tickVals[i].simpleLabel // noSuffixPrefix
1187-
);
1188-
1189-
p = tickVals[i].periodX;
1190-
if(p !== undefined) {
1191-
t.periodX = p;
1192-
if(p > maxRange || p < minRange) { // hide label if outside the range
1193-
if(p > maxRange) t.periodX = maxRange;
1194-
if(p < minRange) t.periodX = minRange;
1195-
1196-
hideLabel(t);
1197-
}
1198-
}
1199-
1200-
if(tickVals[i].skipLabel) {
1257+
t = setTickLabel(ax, tickVals[i]);
1258+
if(tickVals[i].skipLabel ||
1259+
ticklabelIndex && allTicklabelVals.indexOf(tickVals[i]) === -1) {
12011260
hideLabel(t);
12021261
}
12031262

@@ -4542,3 +4601,28 @@ function setShiftVal(ax, axShifts) {
45424601
axShifts[ax.overlaying][ax.side] :
45434602
(ax.shift || 0);
45444603
}
4604+
4605+
/**
4606+
* Checks if the given period is at least the period described by the tickformat or larger. If that
4607+
* is the case, they are compatible, because then the tickformat can be used to describe the period.
4608+
* E.g. it doesn't make sense to put a year label on a period spanning only a month.
4609+
* @param {number} period in ms
4610+
* @param {string} tickformat
4611+
* @returns {boolean}
4612+
*/
4613+
function periodCompatibleWithTickformat(period, tickformat) {
4614+
return (
4615+
/%f/.test(tickformat) ? period >= ONEMICROSEC :
4616+
/%L/.test(tickformat) ? period >= ONEMILLI :
4617+
/%[SX]/.test(tickformat) ? period >= ONESEC :
4618+
/%M/.test(tickformat) ? period >= ONEMIN :
4619+
/%[HI]/.test(tickformat) ? period >= ONEHOUR :
4620+
/%p/.test(tickformat) ? period >= HALFDAY :
4621+
/%[Aadejuwx]/.test(tickformat) ? period >= ONEDAY :
4622+
/%[UVW]/.test(tickformat) ? period >= ONEWEEK :
4623+
/%[Bbm]/.test(tickformat) ? period >= ONEMINMONTH :
4624+
/%[q]/.test(tickformat) ? period >= ONEMINQUARTER :
4625+
/%[Yy]/.test(tickformat) ? period >= ONEMINYEAR :
4626+
true
4627+
);
4628+
}

src/plots/cartesian/axis_defaults.js

+4
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce,
5959
}
6060
}
6161

62+
if(!options.noTicklabelindex && (axType === 'date' || axType === 'linear')) {
63+
coerce('ticklabelindex');
64+
}
65+
6266
var ticklabelposition = '';
6367
if(!options.noTicklabelposition || axType === 'multicategory') {
6468
ticklabelposition = Lib.coerce(containerIn, containerOut, {

src/plots/cartesian/layout_attributes.js

+15
Original file line numberDiff line numberDiff line change
@@ -721,6 +721,21 @@ module.exports = {
721721
'outside and vice versa.'
722722
].join(' ')
723723
},
724+
ticklabelindex: {
725+
// in the future maybe add `extras: ['all', 'minor']` to allow showing labels for all ticks
726+
// or for all minor ticks.
727+
valType: 'integer',
728+
arrayOk: true,
729+
editType: 'calc',
730+
description: [
731+
'Only for axes with `type` *date* or *linear*.',
732+
'Instead of drawing the major tick label, draw the label for the minor tick',
733+
'that is n positions away from the major tick. E.g. to always draw the label for the',
734+
'minor tick before each major tick, choose `ticklabelindex` -1. This is useful for date',
735+
'axes with `ticklabelmode` *period* if you want to label the period that ends with each',
736+
'major tick instead of the period that begins there.'
737+
].join(' ')
738+
},
724739
mirror: {
725740
valType: 'enumerated',
726741
values: [true, 'ticks', false, 'all', 'allticks'],

src/plots/gl3d/layout/axis_defaults.js

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, options) {
4242
data: options.data,
4343
showGrid: true,
4444
noAutotickangles: true,
45+
noTicklabelindex: true,
4546
noTickson: true,
4647
noTicklabelmode: true,
4748
noTicklabelshift: true,
Loading
Loading
Loading
Loading
36.7 KB
Loading
Loading

0 commit comments

Comments
 (0)