Skip to content

Commit e5725c6

Browse files
committed
Property drawminorticklabel for drawing the label for each minor tick n positions away from a major tick
- has no effect for category/multicategory/log axes - has no effect if tickformat frequency incompatible with minor tick frequency
1 parent 491ef1c commit e5725c6

File tree

5 files changed

+137
-28
lines changed

5 files changed

+137
-28
lines changed

src/constants/numerical.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ module.exports = {
3737
ONEHOUR: 3600000,
3838
ONEMIN: 60000,
3939
ONESEC: 1000,
40-
40+
ONEMICROSEC: 0.001,
4141
/*
4242
* For fast conversion btwn world calendars and epoch ms, the Julian Day Number
4343
* of the unix epoch. From calendars.instance().newDate(1970, 1, 1).toJD()

src/plots/cartesian/axes.js

Lines changed: 113 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ var HALFDAY = ONEDAY / 2;
3131
var ONEHOUR = constants.ONEHOUR;
3232
var ONEMIN = constants.ONEMIN;
3333
var ONESEC = constants.ONESEC;
34+
var ONEMICROSEC = constants.ONEMICROSEC;
3435
var MINUS_SIGN = constants.MINUS_SIGN;
3536
var BADNUM = constants.BADNUM;
3637

@@ -908,7 +909,7 @@ axes.calcTicks = function calcTicks(ax, opts) {
908909
var calendar = ax.calendar;
909910
var ticklabelstep = ax.ticklabelstep;
910911
var isPeriod = ax.ticklabelmode === 'period';
911-
912+
var drawMinorTickLabel = ax.drawminorticklabel;
912913
var rng = Lib.simpleMap(ax.range, ax.r2l, undefined, undefined, opts);
913914
var axrev = (rng[1] < rng[0]);
914915
var minRange = Math.min(rng[0], rng[1]);
@@ -921,6 +922,9 @@ axes.calcTicks = function calcTicks(ax, opts) {
921922

922923
var tickVals = [];
923924
var minorTickVals = [];
925+
// all ticks for which labels are drawn which is not necessarily the major ticks when
926+
// `drawminorticklabel` is set.
927+
var labelTickVals = [];
924928

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

@@ -1075,6 +1079,47 @@ axes.calcTicks = function calcTicks(ax, opts) {
10751079
}
10761080
}
10771081

1082+
// check if drawMinorTickLabel makes sense, otherwise ignore it
1083+
if(!minorTickVals || minorTickVals.length < 2) {
1084+
drawMinorTickLabel = false;
1085+
} else {
1086+
var diff = minorTickVals[1].value - minorTickVals[0].value;
1087+
if(!periodCompatibleWithTickformat(diff, ax.tickformat)) {
1088+
drawMinorTickLabel = false;
1089+
}
1090+
}
1091+
// Determine for which ticks to draw labels
1092+
if(!drawMinorTickLabel) {
1093+
labelTickVals = tickVals;
1094+
} else {
1095+
// Collect and sort all major and minor ticks, to find the minor ticks `drawMinorTickLabel`
1096+
// steps away from each major tick. For those minor ticks we want to draw the label.
1097+
1098+
var allTickVals = tickVals.concat(minorTickVals);
1099+
if(isPeriod && tickVals.length) {
1100+
// first major tick was just added for period handling
1101+
allTickVals = allTickVals.slice(1);
1102+
}
1103+
allTickVals =
1104+
allTickVals
1105+
.sort(function(a, b) { return a.value - b.value; })
1106+
.filter(function(tick, index, self) {
1107+
return index === 0 || tick.value !== self[index - 1].value;
1108+
});
1109+
1110+
var majorTickIndices =
1111+
allTickVals
1112+
.map(function(item, index) { return item.minor === undefined ? index : null; })
1113+
.filter(function(index) { return index !== null; });
1114+
1115+
majorTickIndices.forEach(function(majorIdx) {
1116+
var minorIdx = majorIdx + drawMinorTickLabel;
1117+
if(minorIdx >= 0 && minorIdx < allTickVals.length) {
1118+
labelTickVals.push(allTickVals[minorIdx]);
1119+
}
1120+
});
1121+
}
1122+
10781123
if(hasMinor) {
10791124
var canOverlap =
10801125
(ax.minor.ticks === 'inside' && ax.ticks === 'outside') ||
@@ -1108,7 +1153,7 @@ axes.calcTicks = function calcTicks(ax, opts) {
11081153
}
11091154
}
11101155

1111-
if(isPeriod) positionPeriodTicks(tickVals, ax, ax._definedDelta);
1156+
if(isPeriod) positionPeriodTicks(labelTickVals, ax, ax._definedDelta);
11121157

11131158
var i;
11141159
if(ax.rangebreaks) {
@@ -1166,38 +1211,44 @@ axes.calcTicks = function calcTicks(ax, opts) {
11661211

11671212
tickVals = tickVals.concat(minorTickVals);
11681213

1169-
var t, p;
1214+
function setTickLabel(ax, tickVal) {
1215+
var t = axes.tickText(
1216+
ax,
1217+
tickVal.value,
1218+
false, // hover
1219+
tickVal.simpleLabel // noSuffixPrefix
1220+
);
1221+
var p = tickVal.periodX;
1222+
if(p !== undefined) {
1223+
t.periodX = p;
1224+
if(p > maxRange || p < minRange) { // hide label if outside the range
1225+
if(p > maxRange) t.periodX = maxRange;
1226+
if(p < minRange) t.periodX = minRange;
1227+
1228+
hideLabel(t);
1229+
}
1230+
}
1231+
return t;
1232+
}
1233+
1234+
var t;
11701235
for(i = 0; i < tickVals.length; i++) {
11711236
var _minor = tickVals[i].minor;
11721237
var _value = tickVals[i].value;
11731238

11741239
if(_minor) {
1175-
minorTicks.push({
1176-
x: _value,
1177-
minor: true
1178-
});
1240+
if(drawMinorTickLabel && labelTickVals.indexOf(tickVals[i]) !== -1) {
1241+
t = setTickLabel(ax, tickVals[i]);
1242+
} else {
1243+
t = { x: _value };
1244+
}
1245+
t.minor = true;
1246+
minorTicks.push(t);
11791247
} else {
11801248
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) {
1249+
t = setTickLabel(ax, tickVals[i]);
1250+
if(tickVals[i].skipLabel ||
1251+
drawMinorTickLabel && labelTickVals.indexOf(tickVals[i]) === -1) {
12011252
hideLabel(t);
12021253
}
12031254

@@ -4523,3 +4574,38 @@ function setShiftVal(ax, axShifts) {
45234574
axShifts[ax.overlaying][ax.side] :
45244575
(ax.shift || 0);
45254576
}
4577+
4578+
/**
4579+
* Checks if the given period is at least the period described by the tickformat or larger. If that
4580+
* is the case, they are compatible, because then the tickformat can be used to describe the period.
4581+
* E.g. it doesn't make sense to put a year label on a period spanning only a month.
4582+
* @param {number} period in ms
4583+
* @param {string} tickformat
4584+
* @returns {boolean}
4585+
*/
4586+
function periodCompatibleWithTickformat(period, tickformat) {
4587+
if(/%f/.test(tickformat)) {
4588+
if(period < ONEMICROSEC) return false;
4589+
} else if(/%L/.test(tickformat)) {
4590+
if(period < 1) return false;
4591+
} else if(/%[SX]/.test(tickformat)) {
4592+
if(period < ONESEC) return false;
4593+
} else if(/%M/.test(tickformat)) {
4594+
if(period < ONEMIN) return false;
4595+
} else if(/%[HI]/.test(tickformat)) {
4596+
if(period < ONEHOUR) return false;
4597+
} else if(/%p/.test(tickformat)) {
4598+
if(period < HALFDAY) return false;
4599+
} else if(/%[Aadejuwx]/.test(tickformat)) {
4600+
if(period < ONEDAY) return false;
4601+
} else if(/%[UVW]/.test(tickformat)) {
4602+
if(period < ONEWEEK) return false;
4603+
} else if(/%[Bbm]/.test(tickformat)) {
4604+
if(period < ONEMINMONTH) return false;
4605+
} else if(/%[q]/.test(tickformat)) {
4606+
if(period < ONEMINQUARTER) return false;
4607+
} else if(/%[Yy]/.test(tickformat)) {
4608+
if(period < ONEMINYEAR) return false;
4609+
}
4610+
return true;
4611+
}

src/plots/cartesian/axis_defaults.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce,
5959
}
6060
}
6161

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

src/plots/cartesian/layout_attributes.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,15 @@ module.exports = {
698698
'In other cases the default is *hide past div*.'
699699
].join(' ')
700700
},
701+
drawminorticklabel: {
702+
valType: 'integer',
703+
editType: 'calc',
704+
description: [
705+
'Instead of drawing the major tick label, draw the label for the minor tick',
706+
'that is n positions away from the major tick. E.g. to always draw the label for the',
707+
'minor tick before each major tick, choose `drawminorticklabel` -1'
708+
].join(' ')
709+
},
701710
mirror: {
702711
valType: 'enumerated',
703712
values: [true, 'ticks', false, 'all', 'allticks'],

test/plot-schema.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14097,6 +14097,11 @@
1409714097
],
1409814098
"valType": "info_array"
1409914099
},
14100+
"drawminorticklabel": {
14101+
"description": "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 `drawminorticklabel` -1",
14102+
"editType": "calc",
14103+
"valType": "integer"
14104+
},
1410014105
"dtick": {
1410114106
"description": "Sets the step in-between ticks on this axis. Use with `tick0`. Must be a positive number, or special strings available to *log* and *date* axes. If the axis `type` is *log*, then ticks are set every 10^(n*dtick) where n is the tick number. For example, to set a tick mark at 1, 10, 100, 1000, ... set dtick to 1. To set tick marks at 1, 100, 10000, ... set dtick to 2. To set tick marks at 1, 5, 25, 125, 625, 3125, ... set dtick to log_10(5), or 0.69897000433. *log* has several special values; *L<f>*, where `f` is a positive number, gives ticks linearly spaced in value (but not position). For example `tick0` = 0.1, `dtick` = *L0.5* will put ticks at 0.1, 0.6, 1.1, 1.6 etc. To show powers of 10 plus small digits between, use *D1* (all digits) or *D2* (only 2 and 5). `tick0` is ignored for *D1* and *D2*. If the axis `type` is *date*, then you must convert the time to milliseconds. For example, to set the interval between ticks to one day, set `dtick` to 86400000.0. *date* also has special values *M<n>* gives ticks spaced by a number of months. `n` must be a positive integer. To set ticks on the 15th of every third month, set `tick0` to *2000-01-15* and `dtick` to *M3*. To set ticks every 4 years, set `dtick` to *M48*",
1410214107
"editType": "ticks",
@@ -15723,6 +15728,11 @@
1572315728
],
1572415729
"valType": "info_array"
1572515730
},
15731+
"drawminorticklabel": {
15732+
"description": "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 `drawminorticklabel` -1",
15733+
"editType": "calc",
15734+
"valType": "integer"
15735+
},
1572615736
"dtick": {
1572715737
"description": "Sets the step in-between ticks on this axis. Use with `tick0`. Must be a positive number, or special strings available to *log* and *date* axes. If the axis `type` is *log*, then ticks are set every 10^(n*dtick) where n is the tick number. For example, to set a tick mark at 1, 10, 100, 1000, ... set dtick to 1. To set tick marks at 1, 100, 10000, ... set dtick to 2. To set tick marks at 1, 5, 25, 125, 625, 3125, ... set dtick to log_10(5), or 0.69897000433. *log* has several special values; *L<f>*, where `f` is a positive number, gives ticks linearly spaced in value (but not position). For example `tick0` = 0.1, `dtick` = *L0.5* will put ticks at 0.1, 0.6, 1.1, 1.6 etc. To show powers of 10 plus small digits between, use *D1* (all digits) or *D2* (only 2 and 5). `tick0` is ignored for *D1* and *D2*. If the axis `type` is *date*, then you must convert the time to milliseconds. For example, to set the interval between ticks to one day, set `dtick` to 86400000.0. *date* also has special values *M<n>* gives ticks spaced by a number of months. `n` must be a positive integer. To set ticks on the 15th of every third month, set `tick0` to *2000-01-15* and `dtick` to *M3*. To set ticks every 4 years, set `dtick` to *M48*",
1572815738
"editType": "ticks",

0 commit comments

Comments
 (0)