Skip to content

Commit b81617d

Browse files
committed
minor autotick algo mirrors major based on nticks
1 parent 27e1320 commit b81617d

File tree

2 files changed

+82
-54
lines changed

2 files changed

+82
-54
lines changed

src/plots/cartesian/axes.js

Lines changed: 66 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -542,38 +542,72 @@ function autoShiftMonthBins(binStart, data, dtick, dataMin, calendar) {
542542
// ----------------------------------------------------
543543

544544
// ensure we have minor tick0 and dtick calculated
545-
axes.prepMinorTicks = function(ax) {
546-
var majorDtick = ax._majorDtick;
547-
if(ax.tickmode === 'auto' || !ax.dtick) {
548-
var nt = ax.nticks; // minor.nticks
549-
var dist = majorDtick;
550-
551-
if(ax.type === 'date' && typeof majorDtick === 'string' && majorDtick.charAt(0) === 'M') {
552-
var months = Number(majorDtick.substring(1));
553-
dist = months * ONEAVGMONTH / (nt || 7);
554-
} else if(ax.type === 'log') {
555-
if(!nt) nt = 2;
556-
557-
if(nt > 1) {
558-
if(typeof majorDtick === 'string' && majorDtick.charAt(0) === 'L') {
559-
ax.dtick = 'L' + (majorDtick.substring(1) / nt);
560-
return;
561-
} else if(dist === 'D1') {
562-
dist = nt - 1;
563-
} else if(dist === 'D2') {
564-
dist = nt === 2 ? 'D1' : nt;
545+
axes.prepMinorTicks = function(mockAx, ax, opts) {
546+
if(!ax.minor.dtick) {
547+
delete mockAx.dtick;
548+
var tick2 = axes.tickIncrement(ax._tmin, ax.dtick, true);
549+
// mock range a tiny bit smaller than one major tick interval
550+
mockAx.range = Lib.simpleMap([ax._tmin, tick2 * 0.99 + ax._tmin * 0.01], ax.l2r);
551+
mockAx._isMinor = true;
552+
axes.prepTicks(mockAx, opts);
553+
if(isNumeric(ax.dtick) && isNumeric(mockAx.dtick)) {
554+
if(!isMultiple(ax.dtick, mockAx.dtick)) {
555+
// give up on minor ticks, with one exception:
556+
// dtick === 2 weeks, minor = 3 days -> set minor 1 week
557+
// other than that, this can only happen if minor.nticks is
558+
// smaller than two jumps in the auto-tick scale and the first
559+
// jump is not an even multiple (5 -> 2 or for dates 3 ->2, 15 -> 10 etc)
560+
// or if you provided an explicit dtick, in which case it's fine to
561+
// give up, you can provide an explicit minor.dtick.
562+
if((ax.dtick === 2 * ONEWEEK) && (mockAx.dtick === 3 * ONEDAY)) {
563+
mockAx.dtick = ONEWEEK;
565564
} else {
566-
dist /= nt;
565+
mockAx.dtick = ax.dtick;
567566
}
567+
} else if(ax.dtick === 2 * ONEWEEK && mockAx.dtick === 2 * ONEDAY) {
568+
// this is a weird one: we don't want to automatically choose
569+
// 2-day minor ticks for 2-week major, even though it IS an even multiple,
570+
// because people would expect to see the weeks clearly
571+
mockAx.dtick = ONEWEEK;
572+
}
573+
} else if(String(ax.dtick).charAt(0) === 'M') {
574+
if(isNumeric(mockAx.dtick)) {
575+
mockAx.dtick = 'M1';
576+
} else {
577+
var majorMonths = +ax.dtick.substring(1);
578+
var minorMonths = +mockAx.dtick.substring(1);
579+
if(!isMultiple(majorMonths, minorMonths)) {
580+
// unless you provided an explicit ax.dtick (in which case
581+
// it's OK for us to give up, you can provide an explicit
582+
// minor.dtick too), this can only happen with:
583+
// minor.nticks < 3 and dtick === M3, or
584+
// minor.nticks < 5 and dtick === 5 * 10^n years
585+
// so in all cases we just give up.
586+
mockAx.dtick = ax.dtick;
587+
}
588+
}
589+
} else if(String(mockAx.dtick).charAt(0) === 'L') {
590+
if(String(ax.dtick).charAt(0) === 'L') {
591+
if(!isMultiple(+ax.dtick.substring(1), +mockAx.dtick.substring(1))) {
592+
mockAx.dtick = ax.dtick;
593+
}
594+
} else {
595+
mockAx.dtick = 'D1';
568596
}
569-
} else {
570-
dist /= nt || 7;
571597
}
572-
573-
axes.autoTicks(ax, dist, 'minor');
598+
// put back the original range, to use to find the full set of minor ticks
599+
mockAx.range = ax.range;
600+
}
601+
if(ax.minor._tick0Init === undefined) {
602+
// ensure identical tick0
603+
mockAx.tick0 = ax.tick0;
574604
}
575605
};
576606

607+
function isMultiple(bigger, smaller) {
608+
return Math.abs((bigger / smaller + 0.5) % 1 - 0.5) < 0.001;
609+
}
610+
577611
// ensure we have tick0, dtick, and tick rounding calculated
578612
axes.prepTicks = function(ax, opts) {
579613
var rng = Lib.simpleMap(ax.range, ax.r2l, undefined, undefined, opts);
@@ -855,20 +889,7 @@ axes.calcTicks = function calcTicks(ax, opts) {
855889
var mockAx = major ? ax : Lib.extendFlat({}, ax, ax.minor);
856890

857891
if(isMinor) {
858-
if(!ax.minor.dtick) {
859-
mockAx._majorDtick = ax.dtick;
860-
mockAx.dtick = mockAx._dtickInit;
861-
mockAx.tick0 = mockAx._tick0Init;
862-
mockAx.ntick = mockAx._ntickInit;
863-
}
864-
}
865-
866-
if(isMinor) {
867-
axes.prepMinorTicks(mockAx);
868-
if(mockAx.tick0 !== ax.tick0 && ax.minor._tick0Init === undefined) {
869-
// ensure identical tick0
870-
mockAx.tick0 = ax.tick0;
871-
}
892+
axes.prepMinorTicks(mockAx, ax, opts);
872893
} else {
873894
axes.prepTicks(mockAx, opts);
874895
}
@@ -1336,7 +1357,12 @@ axes.autoTicks = function(ax, roughDTick, isMinor) {
13361357
} else if(ax.type === 'log') {
13371358
ax.tick0 = 0;
13381359
var rng = Lib.simpleMap(ax.range, ax.r2l);
1339-
1360+
if(ax._isMinor) {
1361+
// Log axes by default get MORE than nTicks based on the metrics below
1362+
// But for minor ticks we don't want this increase, we already have
1363+
// the major ticks.
1364+
roughDTick *= 1.5;
1365+
}
13401366
if(roughDTick > 0.7) {
13411367
// only show powers of 10
13421368
ax.dtick = mayCeil(roughDTick);

src/plots/cartesian/layout_attributes.js

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,20 @@ var tickmode = {
2929
].join(' ')
3030
};
3131

32-
var nticks = {
33-
valType: 'integer',
34-
min: 0,
35-
dflt: 0,
36-
editType: 'ticks',
37-
description: [
38-
'Specifies the maximum number of ticks for the particular axis.',
39-
'The actual number of ticks will be chosen automatically to be',
40-
'less than or equal to `nticks`.',
41-
'Has an effect only if `tickmode` is set to *auto*.'
42-
].join(' ')
43-
};
32+
function makeNticks(minor) {
33+
return {
34+
valType: 'integer',
35+
min: 0,
36+
dflt: minor ? 7 : 0,
37+
editType: 'ticks',
38+
description: [
39+
'Specifies the maximum number of ticks for the particular axis.',
40+
'The actual number of ticks will be chosen automatically to be',
41+
'less than or equal to `nticks`.',
42+
'Has an effect only if `tickmode` is set to *auto*.'
43+
].join(' ')
44+
};
45+
}
4446

4547
var tick0 = {
4648
valType: 'any',
@@ -503,7 +505,7 @@ module.exports = {
503505

504506
// ticks
505507
tickmode: tickmode,
506-
nticks: nticks,
508+
nticks: makeNticks(),
507509
tick0: tick0,
508510
dtick: dtick,
509511
ticklabelstep: {
@@ -944,7 +946,7 @@ module.exports = {
944946

945947
minor: {
946948
tickmode: tickmode,
947-
nticks: nticks,
949+
nticks: makeNticks('minor'),
948950
tick0: tick0,
949951
dtick: dtick,
950952
tickvals: tickvals,

0 commit comments

Comments
 (0)