Skip to content

Commit cad8e06

Browse files
authored
Merge pull request #1965 from apalchys/tickformatstops
add 'tickformatstops'
2 parents 9af3036 + 0e6747f commit cad8e06

File tree

11 files changed

+483
-6
lines changed

11 files changed

+483
-6
lines changed

src/components/colorbar/attributes.js

+1
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ module.exports = overrideAll({
166166
}),
167167
tickangle: axesAttrs.tickangle,
168168
tickformat: axesAttrs.tickformat,
169+
tickformatstops: axesAttrs.tickformatstops,
169170
tickprefix: axesAttrs.tickprefix,
170171
showtickprefix: axesAttrs.showtickprefix,
171172
ticksuffix: axesAttrs.ticksuffix,

src/plots/cartesian/axes.js

+75-4
Original file line numberDiff line numberDiff line change
@@ -1232,7 +1232,7 @@ function tickTextObj(ax, x, text) {
12321232

12331233
function formatDate(ax, out, hover, extraPrecision) {
12341234
var tr = ax._tickround,
1235-
fmt = (hover && ax.hoverformat) || ax.tickformat;
1235+
fmt = (hover && ax.hoverformat) || axes.getTickFormat(ax);
12361236

12371237
if(extraPrecision) {
12381238
// second or sub-second precision: extra always shows max digits.
@@ -1288,7 +1288,8 @@ function formatDate(ax, out, hover, extraPrecision) {
12881288

12891289
function formatLog(ax, out, hover, extraPrecision, hideexp) {
12901290
var dtick = ax.dtick,
1291-
x = out.x;
1291+
x = out.x,
1292+
tickformat = ax.tickformat;
12921293

12931294
if(hideexp === 'never') {
12941295
// If this is a hover label, then we must *never* hide the exponent
@@ -1302,7 +1303,7 @@ function formatLog(ax, out, hover, extraPrecision, hideexp) {
13021303

13031304
if(extraPrecision && ((typeof dtick !== 'string') || dtick.charAt(0) !== 'L')) dtick = 'L3';
13041305

1305-
if(ax.tickformat || (typeof dtick === 'string' && dtick.charAt(0) === 'L')) {
1306+
if(tickformat || (typeof dtick === 'string' && dtick.charAt(0) === 'L')) {
13061307
out.text = numFormat(Math.pow(10, x), ax, hideexp, extraPrecision);
13071308
}
13081309
else if(isNumeric(dtick) || ((dtick.charAt(0) === 'D') && (Lib.mod(x + 0.01, 1) < 0.1))) {
@@ -1397,7 +1398,7 @@ function numFormat(v, ax, fmtoverride, hover) {
13971398
tickRound = ax._tickround,
13981399
exponentFormat = fmtoverride || ax.exponentformat || 'B',
13991400
exponent = ax._tickexponent,
1400-
tickformat = ax.tickformat,
1401+
tickformat = axes.getTickFormat(ax),
14011402
separatethousands = ax.separatethousands;
14021403

14031404
// special case for hover: set exponent just for this value, and
@@ -1498,6 +1499,76 @@ function numFormat(v, ax, fmtoverride, hover) {
14981499
return v;
14991500
}
15001501

1502+
axes.getTickFormat = function(ax) {
1503+
var i;
1504+
1505+
function convertToMs(dtick) {
1506+
return typeof dtick !== 'string' ? dtick : Number(dtick.replace('M', '')) * ONEAVGMONTH;
1507+
}
1508+
1509+
function compareLogTicks(left, right) {
1510+
var priority = ['L', 'D'];
1511+
if(typeof left === typeof right) {
1512+
if(typeof left === 'number') {
1513+
return left - right;
1514+
} else {
1515+
var leftPriority = priority.indexOf(left.charAt(0));
1516+
var rightPriority = priority.indexOf(right.charAt(0));
1517+
if(leftPriority === rightPriority) {
1518+
return Number(left.replace(/(L|D)/g, '')) - Number(right.replace(/(L|D)/g, ''));
1519+
} else {
1520+
return leftPriority - rightPriority;
1521+
}
1522+
}
1523+
} else {
1524+
return typeof left === 'number' ? 1 : -1;
1525+
}
1526+
}
1527+
1528+
function isProperStop(dtick, range, convert) {
1529+
var convertFn = convert || function(x) { return x;};
1530+
var leftDtick = range[0];
1531+
var rightDtick = range[1];
1532+
return ((!leftDtick && typeof leftDtick !== 'number') || convertFn(leftDtick) <= convertFn(dtick)) &&
1533+
((!rightDtick && typeof rightDtick !== 'number') || convertFn(rightDtick) >= convertFn(dtick));
1534+
}
1535+
1536+
function isProperLogStop(dtick, range) {
1537+
var isLeftDtickNull = range[0] === null;
1538+
var isRightDtickNull = range[1] === null;
1539+
var isDtickInRangeLeft = compareLogTicks(dtick, range[0]) >= 0;
1540+
var isDtickInRangeRight = compareLogTicks(dtick, range[1]) <= 0;
1541+
return (isLeftDtickNull || isDtickInRangeLeft) && (isRightDtickNull || isDtickInRangeRight);
1542+
}
1543+
1544+
var tickstop;
1545+
if(ax.tickformatstops && ax.tickformatstops.length > 0) {
1546+
switch(ax.type) {
1547+
case 'date':
1548+
case 'linear': {
1549+
for(i = 0; i < ax.tickformatstops.length; i++) {
1550+
if(isProperStop(ax.dtick, ax.tickformatstops[i].dtickrange, convertToMs)) {
1551+
tickstop = ax.tickformatstops[i];
1552+
break;
1553+
}
1554+
}
1555+
break;
1556+
}
1557+
case 'log': {
1558+
for(i = 0; i < ax.tickformatstops.length; i++) {
1559+
if(isProperLogStop(ax.dtick, ax.tickformatstops[i].dtickrange)) {
1560+
tickstop = ax.tickformatstops[i];
1561+
break;
1562+
}
1563+
}
1564+
break;
1565+
}
1566+
default:
1567+
}
1568+
}
1569+
return tickstop ? tickstop.value : ax.tickformat;
1570+
};
1571+
15011572
axes.subplotMatch = /^x([0-9]*)y([0-9]*)$/;
15021573

15031574
// getSubplots - extract all combinations of axes we need to make plots for

src/plots/cartesian/layout_attributes.js

+28
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,34 @@ module.exports = {
492492
'*%H~%M~%S.%2f* would display *09~15~23.46*'
493493
].join(' ')
494494
},
495+
tickformatstops: {
496+
_isLinkedToArray: 'tickformatstop',
497+
498+
dtickrange: {
499+
valType: 'info_array',
500+
role: 'info',
501+
items: [
502+
{valType: 'any', editType: 'ticks'},
503+
{valType: 'any', editType: 'ticks'}
504+
],
505+
editType: 'ticks',
506+
description: [
507+
'range [*min*, *max*], where *min*, *max* - dtick values',
508+
'which describe some zoom level, it is possible to omit *min*',
509+
'or *max* value by passing *null*'
510+
].join(' ')
511+
},
512+
value: {
513+
valType: 'string',
514+
dflt: '',
515+
role: 'style',
516+
editType: 'ticks',
517+
description: [
518+
'string - dtickformat for described zoom level, the same as *tickformat*'
519+
].join(' ')
520+
},
521+
editType: 'ticks'
522+
},
495523
hoverformat: {
496524
valType: 'string',
497525
dflt: '',

src/plots/cartesian/tick_label_defaults.js

+25-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
'use strict';
1111

1212
var Lib = require('../../lib');
13-
13+
var layoutAttributes = require('./layout_attributes');
1414

1515
/**
1616
* options: inherits font, outerTicks, noHover from axes.handleAxisDefaults
@@ -40,6 +40,7 @@ module.exports = function handleTickLabelDefaults(containerIn, containerOut, coe
4040

4141
if(axType !== 'category') {
4242
var tickFormat = coerce('tickformat');
43+
tickformatstopsDefaults(containerIn, containerOut);
4344
if(!tickFormat && axType !== 'date') {
4445
coerce('showexponent', showAttrDflt);
4546
coerce('exponentformat');
@@ -80,3 +81,26 @@ function getShowAttrDflt(containerIn) {
8081
return containerIn[showAttrs[0]];
8182
}
8283
}
84+
85+
function tickformatstopsDefaults(tickformatIn, tickformatOut) {
86+
var valuesIn = tickformatIn.tickformatstops;
87+
var valuesOut = tickformatOut.tickformatstops = [];
88+
89+
if(!Array.isArray(valuesIn)) return;
90+
91+
var valueIn, valueOut;
92+
93+
function coerce(attr, dflt) {
94+
return Lib.coerce(valueIn, valueOut, layoutAttributes.tickformatstops, attr, dflt);
95+
}
96+
97+
for(var i = 0; i < valuesIn.length; i++) {
98+
valueIn = valuesIn[i];
99+
valueOut = {};
100+
101+
coerce('dtickrange');
102+
coerce('value');
103+
104+
valuesOut.push(valueOut);
105+
}
106+
}

src/plots/gl3d/layout/axis_attributes.js

+1
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ module.exports = overrideAll({
101101
exponentformat: axesAttrs.exponentformat,
102102
separatethousands: axesAttrs.separatethousands,
103103
tickformat: axesAttrs.tickformat,
104+
tickformatstops: axesAttrs.tickformatstops,
104105
hoverformat: axesAttrs.hoverformat,
105106
// lines and grids
106107
showline: axesAttrs.showline,

src/plots/ternary/layout/axis_attributes.js

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ module.exports = {
3939
tickfont: axesAttrs.tickfont,
4040
tickangle: axesAttrs.tickangle,
4141
tickformat: axesAttrs.tickformat,
42+
tickformatstops: axesAttrs.tickformatstops,
4243
hoverformat: axesAttrs.hoverformat,
4344
// lines and grids
4445
showline: extendFlat({}, axesAttrs.showline, {dflt: true}),

src/traces/carpet/axis_attributes.js

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
var fontAttrs = require('../../plots/font_attributes');
1212
var colorAttrs = require('../../components/color/attributes');
13+
var axesAttrs = require('../../plots/cartesian/layout_attributes');
14+
var overrideAll = require('../../plot_api/edit_types').overrideAll;
1315

1416
module.exports = {
1517
color: {
@@ -290,6 +292,7 @@ module.exports = {
290292
'*%H~%M~%S.%2f* would display *09~15~23.46*'
291293
].join(' ')
292294
},
295+
tickformatstops: overrideAll(axesAttrs.tickformatstops, 'calc', 'from-root'),
293296
categoryorder: {
294297
valType: 'enumerated',
295298
values: [

tasks/util/strict_d3.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ var pathToStrictD3Module = path.join(
66
constants.pathToImageTest,
77
'strict-d3.js'
88
);
9-
109
/**
1110
* Transform `require('d3')` expressions to `require(/path/to/strict-d3.js)`
1211
*/
@@ -18,6 +17,8 @@ module.exports = transformTools.makeRequireTransform('requireTransform',
1817
var pathOut;
1918

2019
if(pathIn === 'd3' && opts.file !== pathToStrictD3Module) {
20+
// JSON.stringify: fix npm-scripts for windows users, for whom
21+
// path has \ in it, without stringify that turns into control chars.
2122
pathOut = 'require(' + JSON.stringify(pathToStrictD3Module) + ')';
2223
}
2324

28.3 KB
Loading

test/image/mocks/tickformatstops.json

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"data": [
3+
{
4+
"x": ["2005-01","2005-02","2005-03","2005-04","2005-05","2005-06","2005-07"],
5+
"y": [-20,10,-5,0,5,-10,20]
6+
}
7+
],
8+
"layout": {
9+
"xaxis": {
10+
"tickformatstops": [
11+
{
12+
"dtickrange": [null, 1000],
13+
"value": "%H:%M:%S.%L ms"
14+
},
15+
{
16+
"dtickrange": [1000, 60000],
17+
"value": "%H:%M:%S s"
18+
},
19+
{
20+
"dtickrange": [60000, 3600000],
21+
"value": "%H:%M m"
22+
},
23+
{
24+
"dtickrange": [3600000, 86400000],
25+
"value": "%H:%M h"
26+
},
27+
{
28+
"dtickrange": [86400000, 604800000],
29+
"value": "%e. %b d"
30+
},
31+
{
32+
"dtickrange": [604800000, "M1"],
33+
"value": "%e. %b w"
34+
},
35+
{
36+
"dtickrange": ["M1", "M12"],
37+
"value": "%b '%y M"
38+
},
39+
{
40+
"dtickrange": ["M12", null],
41+
"value": "%Y Y"
42+
}
43+
]
44+
}
45+
}
46+
}

0 commit comments

Comments
 (0)