Skip to content

Commit 4e09ad3

Browse files
authored
Merge pull request #5240 from plotly/disable-convertnumeric-in-autotype
Implement strict autotypenumbers
2 parents 96d7511 + 6fdf559 commit 4e09ad3

20 files changed

+570
-41
lines changed

src/plots/cartesian/axes.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,9 @@ var getDataConversions = axes.getDataConversions = function(gd, trace, target, t
216216
// setup the data-to-calc method.
217217
if(Array.isArray(d2cTarget)) {
218218
ax = {
219-
type: autoType(targetArray),
219+
type: autoType(targetArray, undefined, {
220+
autotypenumbers: gd._fullLayout.autotypenumbers
221+
}),
220222
_categories: []
221223
};
222224
axes.setConvert(ax);

src/plots/cartesian/axis_autotype.js

Lines changed: 67 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,49 @@ var isNumeric = require('fast-isnumeric');
1414
var Lib = require('../../lib');
1515
var BADNUM = require('../../constants/numerical').BADNUM;
1616

17+
var isArrayOrTypedArray = Lib.isArrayOrTypedArray;
18+
var isDateTime = Lib.isDateTime;
19+
var cleanNumber = Lib.cleanNumber;
20+
var round = Math.round;
21+
1722
module.exports = function autoType(array, calendar, opts) {
18-
opts = opts || {};
23+
var a = array;
24+
25+
var noMultiCategory = opts.noMultiCategory;
26+
if(isArrayOrTypedArray(a) && !a.length) return '-';
27+
if(!noMultiCategory && multiCategory(a)) return 'multicategory';
28+
if(noMultiCategory && Array.isArray(a[0])) { // no need to flat typed arrays here
29+
var b = [];
30+
for(var i = 0; i < a.length; i++) {
31+
if(isArrayOrTypedArray(a[i])) {
32+
for(var j = 0; j < a[i].length; j++) {
33+
b.push(a[i][j]);
34+
}
35+
}
36+
}
37+
a = b;
38+
}
39+
40+
if(moreDates(a, calendar)) return 'date';
1941

20-
if(!opts.noMultiCategory && multiCategory(array)) return 'multicategory';
21-
if(moreDates(array, calendar)) return 'date';
22-
if(category(array)) return 'category';
23-
if(linearOK(array)) return 'linear';
24-
else return '-';
42+
var convertNumeric = opts.autotypenumbers !== 'strict'; // compare against strict, just in case autotypenumbers was not provided in opts
43+
if(category(a, convertNumeric)) return 'category';
44+
if(linearOK(a, convertNumeric)) return 'linear';
45+
46+
return '-';
2547
};
2648

49+
function hasTypeNumber(v, convertNumeric) {
50+
return convertNumeric ? isNumeric(v) : typeof v === 'number';
51+
}
52+
2753
// is there at least one number in array? If not, we should leave
2854
// ax.type empty so it can be autoset later
29-
function linearOK(array) {
30-
if(!array) return false;
55+
function linearOK(a, convertNumeric) {
56+
var len = a.length;
3157

32-
for(var i = 0; i < array.length; i++) {
33-
if(isNumeric(array[i])) return true;
58+
for(var i = 0; i < len; i++) {
59+
if(hasTypeNumber(a[i], convertNumeric)) return true;
3460
}
3561

3662
return false;
@@ -43,51 +69,61 @@ function linearOK(array) {
4369
// numbers and a few dates
4470
// as with categories, consider DISTINCT values only.
4571
function moreDates(a, calendar) {
46-
// test at most 1000 points, evenly spaced
47-
var inc = Math.max(1, (a.length - 1) / 1000);
48-
var dcnt = 0;
49-
var ncnt = 0;
72+
var len = a.length;
73+
74+
var inc = getIncrement(len);
75+
var dats = 0;
76+
var nums = 0;
5077
var seen = {};
5178

52-
for(var i = 0; i < a.length; i += inc) {
53-
var ai = a[Math.round(i)];
79+
for(var f = 0; f < len; f += inc) {
80+
var i = round(f);
81+
var ai = a[i];
5482
var stri = String(ai);
5583
if(seen[stri]) continue;
5684
seen[stri] = 1;
5785

58-
if(Lib.isDateTime(ai, calendar)) dcnt += 1;
59-
if(isNumeric(ai)) ncnt += 1;
86+
if(isDateTime(ai, calendar)) dats++;
87+
if(isNumeric(ai)) nums++;
6088
}
6189

62-
return (dcnt > ncnt * 2);
90+
return dats > nums * 2;
91+
}
92+
93+
// return increment to test at most 1000 points, evenly spaced
94+
function getIncrement(len) {
95+
return Math.max(1, (len - 1) / 1000);
6396
}
6497

6598
// are the (x,y)-values in gd.data mostly text?
6699
// require twice as many DISTINCT categories as distinct numbers
67-
function category(a) {
68-
// test at most 1000 points
69-
var inc = Math.max(1, (a.length - 1) / 1000);
70-
var curvenums = 0;
71-
var curvecats = 0;
100+
function category(a, convertNumeric) {
101+
var len = a.length;
102+
103+
var inc = getIncrement(len);
104+
var nums = 0;
105+
var cats = 0;
72106
var seen = {};
73107

74-
for(var i = 0; i < a.length; i += inc) {
75-
var ai = a[Math.round(i)];
108+
for(var f = 0; f < len; f += inc) {
109+
var i = round(f);
110+
var ai = a[i];
76111
var stri = String(ai);
77112
if(seen[stri]) continue;
78113
seen[stri] = 1;
79114

80-
if(typeof ai === 'boolean') curvecats++;
81-
else if(Lib.cleanNumber(ai) !== BADNUM) curvenums++;
82-
else if(typeof ai === 'string') curvecats++;
115+
var t = typeof ai;
116+
if(t === 'boolean') cats++;
117+
else if(convertNumeric ? cleanNumber(ai) !== BADNUM : t === 'number') nums++;
118+
else if(t === 'string') cats++;
83119
}
84120

85-
return curvecats > curvenums * 2;
121+
return cats > nums * 2;
86122
}
87123

88124
// very-loose requirements for multicategory,
89125
// trace modules that should never auto-type to multicategory
90126
// should be declared with 'noMultiCategory'
91127
function multiCategory(a) {
92-
return Lib.isArrayOrTypedArray(a[0]) && Lib.isArrayOrTypedArray(a[1]);
128+
return isArrayOrTypedArray(a[0]) && isArrayOrTypedArray(a[1]);
93129
}

src/plots/cartesian/layout_attributes.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,19 @@ module.exports = {
102102
'the axis in question.'
103103
].join(' ')
104104
},
105+
autotypenumbers: {
106+
valType: 'enumerated',
107+
values: ['convert types', 'strict'],
108+
dflt: 'convert types',
109+
role: 'info',
110+
editType: 'calc',
111+
description: [
112+
'Using *strict* a numeric string in trace data is not converted to a number.',
113+
'Using *convert types* a numeric string in trace data may be',
114+
'treated as a number during automatic axis `type` detection.',
115+
'Defaults to layout.autotypenumbers.'
116+
].join(' ')
117+
},
105118
autorange: {
106119
valType: 'enumerated',
107120
values: [true, false, 'reversed'],

src/plots/cartesian/layout_defaults.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ function appendList(cont, k, item) {
3838
}
3939

4040
module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
41+
var autotypenumbersDflt = layoutOut.autotypenumbers;
42+
4143
var ax2traces = {};
4244
var xaMayHide = {};
4345
var yaMayHide = {};
@@ -246,6 +248,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
246248
automargin: true,
247249
visibleDflt: visibleDflt,
248250
reverseDflt: reverseDflt,
251+
autotypenumbersDflt: autotypenumbersDflt,
249252
splomStash: ((layoutOut._splomAxes || {})[axLetter] || {})[axId]
250253
};
251254

@@ -310,6 +313,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
310313
automargin: true,
311314
visibleDflt: false,
312315
reverseDflt: false,
316+
autotypenumbersDflt: autotypenumbersDflt,
313317
splomStash: ((layoutOut._splomAxes || {})[axLetter] || {})[axId]
314318
};
315319

src/plots/cartesian/type_defaults.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ var autoType = require('./axis_autotype');
1616
* name: axis object name (ie 'xaxis') if one should be stored
1717
*/
1818
module.exports = function handleTypeDefaults(containerIn, containerOut, coerce, options) {
19+
coerce('autotypenumbers', options.autotypenumbersDflt);
1920
var axType = coerce('type', (options.splomStash || {}).type);
2021

2122
if(axType === '-') {
@@ -68,6 +69,8 @@ function setAutoType(ax, data) {
6869
opts.noMultiCategory = true;
6970
}
7071

72+
opts.autotypenumbers = ax.autotypenumbers;
73+
7174
// check all boxes on this x axis to see
7275
// if they're dates, numbers, or categories
7376
if(isBoxWithoutPositionCoords(d0, axLetter)) {

src/plots/gl3d/layout/axis_attributes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ module.exports = overrideAll({
7878
type: extendFlat({}, axesAttrs.type, {
7979
values: ['-', 'linear', 'log', 'date', 'category']
8080
}),
81+
autotypenumbers: axesAttrs.autotypenumbers,
8182
autorange: axesAttrs.autorange,
8283
rangemode: axesAttrs.rangemode,
8384
range: extendFlat({}, axesAttrs.range, {

src/plots/gl3d/layout/defaults.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
4040
font: layoutOut.font,
4141
fullData: fullData,
4242
getDfltFromLayout: getDfltFromLayout,
43+
autotypenumbersDflt: layoutOut.autotypenumbers,
4344
paper_bgcolor: layoutOut.paper_bgcolor,
4445
calendar: layoutOut.calendar
4546
});
@@ -109,6 +110,7 @@ function handleGl3dDefaults(sceneLayoutIn, sceneLayoutOut, coerce, opts) {
109110
data: fullGl3dData,
110111
bgColor: bgColorCombined,
111112
calendar: opts.calendar,
113+
autotypenumbersDflt: opts.autotypenumbersDflt,
112114
fullLayout: opts.fullLayout
113115
});
114116

src/plots/layout_attributes.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,19 @@ module.exports = {
292292
'Sets the background color of the plotting area in-between x and y axes.'
293293
].join(' ')
294294
},
295+
autotypenumbers: {
296+
valType: 'enumerated',
297+
values: ['convert types', 'strict'],
298+
dflt: 'convert types',
299+
role: 'info',
300+
editType: 'calc',
301+
description: [
302+
'Using *strict* a numeric string in trace data is not converted to a number.',
303+
'Using *convert types* a numeric string in trace data may be',
304+
'treated as a number during automatic axis `type` detection.',
305+
'This is the default value; however it could be overridden for individual axes.'
306+
].join(' ')
307+
},
295308
separators: {
296309
valType: 'string',
297310
role: 'style',

src/plots/plots.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1476,6 +1476,8 @@ plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut, formatObj) {
14761476
layoutOut._dataTemplate = template.data;
14771477
}
14781478

1479+
coerce('autotypenumbers');
1480+
14791481
var globalFont = Lib.coerceFont(coerce, 'font');
14801482

14811483
coerce('title.text', layoutOut._dfltTitle.plot);

src/plots/polar/layout_attributes.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ var radialAxisAttrs = {
6161
type: extendFlat({}, axesAttrs.type, {
6262
values: ['-', 'linear', 'log', 'date', 'category']
6363
}),
64+
autotypenumbers: axesAttrs.autotypenumbers,
6465

6566
autorange: extendFlat({}, axesAttrs.autorange, {editType: 'plot'}),
6667
rangemode: {
@@ -179,6 +180,7 @@ var angularAxisAttrs = {
179180
'If *category, use `period` to set the number of integer coordinates around polar axis.'
180181
].join(' ')
181182
},
183+
autotypenumbers: axesAttrs.autotypenumbers,
182184

183185
categoryorder: axesAttrs.categoryorder,
184186
categoryarray: axesAttrs.categoryarray,

0 commit comments

Comments
 (0)