Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Property ticklabelindex for custom tick label display #7036

Merged
merged 35 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
e5725c6
Property `drawminorticklabel` for drawing the label for each minor ti…
my-tien Jun 25, 2024
11fd3f1
Ensure correct spacing between ticklabels and ticks when using drawmi…
my-tien Jun 25, 2024
a3e41af
baseline test mock for drawminorticklabel
my-tien Jun 25, 2024
f7d9abf
Draftlog for PR 7036 (drawminorticklabel)
my-tien Jun 25, 2024
1f52f4e
"noDrawminorticklabel" exception for gl3d plots
my-tien Jun 25, 2024
35a7f51
Fix failing source-syntax check: missing newline in mock
my-tien Jun 27, 2024
0a80d24
Restrict drawminorticklabel to period axes
my-tien Jun 27, 2024
1e7ebc3
Revert "Ensure correct spacing between ticklabels and ticks when usin…
my-tien Jun 27, 2024
0671ebb
Update mock after restricting new property to period axes
my-tien Jun 27, 2024
6fcda1a
baseline image for zzz_drawminorticklabel_period_axes.png
my-tien Jun 27, 2024
21a573c
Refactor periodCompatibleWithTickformat to make behavior clearer.
my-tien Jul 5, 2024
e511fc8
Note about possible future extension of the minor tick label customiz…
my-tien Jul 4, 2024
7585121
Honor ticklabelstep also when using drawminorticklabel
my-tien Jul 5, 2024
e2493a6
drawminorticklabel mock for date_axes_period, date_axes_period2, date…
my-tien Jul 5, 2024
00971f1
Mention in description that drawminorticklabel only applies to period…
my-tien Jul 5, 2024
6f5ba12
Missing trailing newlines for mocks
my-tien Jul 5, 2024
cd7e7c1
Baseline images for new drawminorticklabel mocks
my-tien Jul 5, 2024
ee329a3
Merge remote-tracking branch 'origin-plotly/master' into custom_tick_…
my-tien Jul 5, 2024
0019cb0
Rename drawminorticklabel → ticklabelindex
my-tien Jul 8, 2024
e328c9f
Allow `ticklabelindex` also for non-period date axes and linear axes.
my-tien Jul 8, 2024
a580821
Update `ticklabelindex` description after allowing linear and date axes.
my-tien Jul 8, 2024
16d792c
Add constant ONEMILLI
my-tien Jul 8, 2024
82f1fa0
Fix missing EOF newline in zzz_date_axes_ticklabelindex.json
my-tien Jul 8, 2024
908deec
New and updated baseline images for ticklabelindex mocks
my-tien Jul 8, 2024
884d975
Satisfy eslint rules
my-tien Jul 8, 2024
6fd6bd4
Fix ticklabelindex for reversed axes
my-tien Jul 9, 2024
05d456d
Add `arrayOk` for `ticklabelindex`, add test case to mock
my-tien Jul 9, 2024
d351462
Update baseline image for zzz_ticklabelindex after adding more test c…
my-tien Jul 9, 2024
96a26da
replace Array.isArray with Lib.isArrayOrTypedArray
my-tien Jul 9, 2024
64e14d6
Add to ticklabelindex description how it can be useful for period axes
my-tien Jul 10, 2024
50b1e0f
Mention contributor in draftlog
my-tien Jul 10, 2024
86b43e4
refactor calcTicks
my-tien Jul 10, 2024
0515237
refactor calcTicks
my-tien Jul 10, 2024
b6903e4
comment about possible extra values for ticklabelindex
my-tien Jul 10, 2024
f8974e0
Mention contributor in draftlog for PR 7006
my-tien Jul 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion draftlogs/7006_add.md
Original file line number Diff line number Diff line change
@@ -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)]
- 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)]
1 change: 1 addition & 0 deletions draftlogs/7036_add.md
Original file line number Diff line number Diff line change
@@ -0,0 +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)]
3 changes: 2 additions & 1 deletion src/constants/numerical.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ module.exports = {
ONEHOUR: 3600000,
ONEMIN: 60000,
ONESEC: 1000,

ONEMILLI: 1,
ONEMICROSEC: 0.001,
my-tien marked this conversation as resolved.
Show resolved Hide resolved
/*
* For fast conversion btwn world calendars and epoch ms, the Julian Day Number
* of the unix epoch. From calendars.instance().newDate(1970, 1, 1).toJD()
Expand Down
138 changes: 111 additions & 27 deletions src/plots/cartesian/axes.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ var HALFDAY = ONEDAY / 2;
var ONEHOUR = constants.ONEHOUR;
var ONEMIN = constants.ONEMIN;
var ONESEC = constants.ONESEC;
var ONEMILLI = constants.ONEMILLI;
var ONEMICROSEC = constants.ONEMICROSEC;
var MINUS_SIGN = constants.MINUS_SIGN;
var BADNUM = constants.BADNUM;

Expand Down Expand Up @@ -908,7 +910,9 @@ axes.calcTicks = function calcTicks(ax, opts) {
var calendar = ax.calendar;
var ticklabelstep = ax.ticklabelstep;
var isPeriod = ax.ticklabelmode === 'period';

var isReversed = ax.range[0] > ax.range[1];
var ticklabelIndex = (!ax.ticklabelindex || Lib.isArrayOrTypedArray(ax.ticklabelindex)) ?
ax.ticklabelindex : [ax.ticklabelindex];
var rng = Lib.simpleMap(ax.range, ax.r2l, undefined, undefined, opts);
var axrev = (rng[1] < rng[0]);
var minRange = Math.min(rng[0], rng[1]);
Expand All @@ -921,6 +925,9 @@ axes.calcTicks = function calcTicks(ax, opts) {

var tickVals = [];
var minorTickVals = [];
// all ticks for which labels are drawn which is not necessarily the major ticks when
// `ticklabelindex` is set.
var allTicklabelVals = [];

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

Expand Down Expand Up @@ -1075,6 +1082,52 @@ axes.calcTicks = function calcTicks(ax, opts) {
}
}

// check if ticklabelIndex makes sense, otherwise ignore it
if(!minorTickVals || minorTickVals.length < 2) {
ticklabelIndex = false;
} else {
var diff = (minorTickVals[1].value - minorTickVals[0].value) * (isReversed ? -1 : 1);
if(!periodCompatibleWithTickformat(diff, ax.tickformat)) {
ticklabelIndex = false;
}
}
// Determine for which ticks to draw labels
if(!ticklabelIndex) {
allTicklabelVals = tickVals;
} else {
// Collect and sort all major and minor ticks, to find the minor ticks `ticklabelIndex`
// steps away from each major tick. For those minor ticks we want to draw the label.

var allTickVals = tickVals.concat(minorTickVals);
if(isPeriod && tickVals.length) {
// first major tick was just added for period handling
allTickVals = allTickVals.slice(1);
}

allTickVals =
allTickVals
.sort(function(a, b) { return a.value - b.value; })
.filter(function(tick, index, self) {
return index === 0 || tick.value !== self[index - 1].value;
});

var majorTickIndices =
allTickVals
.map(function(item, index) {
return item.minor === undefined && !item.skipLabel ? index : null;
})
.filter(function(index) { return index !== null; });

majorTickIndices.forEach(function(majorIdx) {
ticklabelIndex.map(function(nextLabelIdx) {
var minorIdx = majorIdx + nextLabelIdx;
if(minorIdx >= 0 && minorIdx < allTickVals.length) {
Lib.pushUnique(allTicklabelVals, allTickVals[minorIdx]);
}
});
});
}

if(hasMinor) {
var canOverlap =
(ax.minor.ticks === 'inside' && ax.ticks === 'outside') ||
Expand Down Expand Up @@ -1108,7 +1161,7 @@ axes.calcTicks = function calcTicks(ax, opts) {
}
}

if(isPeriod) positionPeriodTicks(tickVals, ax, ax._definedDelta);
if(isPeriod) positionPeriodTicks(allTicklabelVals, ax, ax._definedDelta);

var i;
if(ax.rangebreaks) {
Expand Down Expand Up @@ -1166,38 +1219,44 @@ axes.calcTicks = function calcTicks(ax, opts) {

tickVals = tickVals.concat(minorTickVals);

var t, p;
function setTickLabel(ax, tickVal) {
var text = axes.tickText(
ax,
tickVal.value,
false, // hover
tickVal.simpleLabel // noSuffixPrefix
);
var p = tickVal.periodX;
if(p !== undefined) {
text.periodX = p;
if(p > maxRange || p < minRange) { // hide label if outside the range
if(p > maxRange) text.periodX = maxRange;
if(p < minRange) text.periodX = minRange;

hideLabel(text);
}
}
return text;
}

var t;
for(i = 0; i < tickVals.length; i++) {
var _minor = tickVals[i].minor;
var _value = tickVals[i].value;

if(_minor) {
minorTicks.push({
x: _value,
minor: true
});
if(ticklabelIndex && allTicklabelVals.indexOf(tickVals[i]) !== -1) {
t = setTickLabel(ax, tickVals[i]);
} else {
t = { x: _value };
}
t.minor = true;
minorTicks.push(t);
} else {
lastVisibleHead = ax._prevDateHead;

t = axes.tickText(
ax,
_value,
false, // hover
tickVals[i].simpleLabel // noSuffixPrefix
);

p = tickVals[i].periodX;
if(p !== undefined) {
t.periodX = p;
if(p > maxRange || p < minRange) { // hide label if outside the range
if(p > maxRange) t.periodX = maxRange;
if(p < minRange) t.periodX = minRange;

hideLabel(t);
}
}

if(tickVals[i].skipLabel) {
t = setTickLabel(ax, tickVals[i]);
if(tickVals[i].skipLabel ||
ticklabelIndex && allTicklabelVals.indexOf(tickVals[i]) === -1) {
hideLabel(t);
}

Expand Down Expand Up @@ -4542,3 +4601,28 @@ function setShiftVal(ax, axShifts) {
axShifts[ax.overlaying][ax.side] :
(ax.shift || 0);
}

/**
* Checks if the given period is at least the period described by the tickformat or larger. If that
* is the case, they are compatible, because then the tickformat can be used to describe the period.
* E.g. it doesn't make sense to put a year label on a period spanning only a month.
* @param {number} period in ms
* @param {string} tickformat
* @returns {boolean}
*/
my-tien marked this conversation as resolved.
Show resolved Hide resolved
function periodCompatibleWithTickformat(period, tickformat) {
return (
/%f/.test(tickformat) ? period >= ONEMICROSEC :
/%L/.test(tickformat) ? period >= ONEMILLI :
/%[SX]/.test(tickformat) ? period >= ONESEC :
/%M/.test(tickformat) ? period >= ONEMIN :
/%[HI]/.test(tickformat) ? period >= ONEHOUR :
/%p/.test(tickformat) ? period >= HALFDAY :
/%[Aadejuwx]/.test(tickformat) ? period >= ONEDAY :
/%[UVW]/.test(tickformat) ? period >= ONEWEEK :
/%[Bbm]/.test(tickformat) ? period >= ONEMINMONTH :
/%[q]/.test(tickformat) ? period >= ONEMINQUARTER :
/%[Yy]/.test(tickformat) ? period >= ONEMINYEAR :
true
);
}
4 changes: 4 additions & 0 deletions src/plots/cartesian/axis_defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce,
}
}

if(!options.noTicklabelindex && (axType === 'date' || axType === 'linear')) {
coerce('ticklabelindex');
}

var ticklabelposition = '';
if(!options.noTicklabelposition || axType === 'multicategory') {
ticklabelposition = Lib.coerce(containerIn, containerOut, {
Expand Down
15 changes: 15 additions & 0 deletions src/plots/cartesian/layout_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,21 @@ module.exports = {
'outside and vice versa.'
].join(' ')
},
ticklabelindex: {
// in the future maybe add `extras: ['all', 'minor']` to allow showing labels for all ticks
// or for all minor ticks.
valType: 'integer',
arrayOk: true,
archmoj marked this conversation as resolved.
Show resolved Hide resolved
editType: 'calc',
description: [
'Only for axes with `type` *date* or *linear*.',
'Instead of drawing the major tick label, draw the label for the minor tick',
'that is n positions away from the major tick. E.g. to always draw the label for the',
'minor tick before each major tick, choose `ticklabelindex` -1. This is useful for date',
'axes with `ticklabelmode` *period* if you want to label the period that ends with each',
'major tick instead of the period that begins there.'
].join(' ')
},
mirror: {
valType: 'enumerated',
values: [true, 'ticks', false, 'all', 'allticks'],
Expand Down
1 change: 1 addition & 0 deletions src/plots/gl3d/layout/axis_defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, options) {
data: options.data,
showGrid: true,
noAutotickangles: true,
noTicklabelindex: true,
noTickson: true,
noTicklabelmode: true,
noTicklabelshift: true,
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/image/baselines/zzz_ticklabelindex.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading