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,

src/plots/polar/layout_defaults.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ function handleDefaults(contIn, contOut, coerce, opts) {
5757
axOut._traceIndices = subplotData.map(function(t) { return t._expandedIndex; });
5858

5959
var dataAttr = constants.axisName2dataArray[axName];
60-
var axType = handleAxisTypeDefaults(axIn, axOut, coerceAxis, subplotData, dataAttr);
60+
var axType = handleAxisTypeDefaults(axIn, axOut, coerceAxis, subplotData, dataAttr, opts);
6161

6262
handleCategoryOrderDefaults(axIn, axOut, coerceAxis, {
6363
axData: subplotData,
@@ -187,7 +187,8 @@ function handleDefaults(contIn, contOut, coerce, opts) {
187187
}
188188
}
189189

190-
function handleAxisTypeDefaults(axIn, axOut, coerce, subplotData, dataAttr) {
190+
function handleAxisTypeDefaults(axIn, axOut, coerce, subplotData, dataAttr, options) {
191+
var autotypenumbers = coerce('autotypenumbers', options.autotypenumbersDflt);
191192
var axType = coerce('type');
192193

193194
if(axType === '-') {
@@ -201,7 +202,10 @@ function handleAxisTypeDefaults(axIn, axOut, coerce, subplotData, dataAttr) {
201202
}
202203

203204
if(trace && trace[dataAttr]) {
204-
axOut.type = autoType(trace[dataAttr], 'gregorian');
205+
axOut.type = autoType(trace[dataAttr], 'gregorian', {
206+
noMultiCategory: true,
207+
autotypenumbers: autotypenumbers
208+
});
205209
}
206210

207211
if(axOut.type === '-') {
@@ -224,6 +228,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
224228
attributes: layoutAttributes,
225229
handleDefaults: handleDefaults,
226230
font: layoutOut.font,
231+
autotypenumbersDflt: layoutOut.autotypenumbers,
227232
paper_bgcolor: layoutOut.paper_bgcolor,
228233
fullData: fullData,
229234
layoutOut: layoutOut

src/traces/box/defaults.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ function handleSampleDefaults(traceIn, traceOut, coerce, layout) {
109109
var yLen = yDims && Lib.minRowLength(y);
110110
var xLen = xDims && Lib.minRowLength(x);
111111

112+
var calendar = layout.calendar;
113+
var opts = {
114+
autotypenumbers: layout.autotypenumbers
115+
};
116+
112117
var defaultOrientation, len;
113118
if(traceOut._hasPreCompStats) {
114119
switch(String(xDims) + String(yDims)) {
@@ -160,7 +165,7 @@ function handleSampleDefaults(traceIn, traceOut, coerce, layout) {
160165
var hasCategories = false;
161166
var i;
162167
for(i = 0; i < x.length; i++) {
163-
if(autoType(x[i]) === 'category') {
168+
if(autoType(x[i], calendar, opts) === 'category') {
164169
hasCategories = true;
165170
break;
166171
}
@@ -171,7 +176,7 @@ function handleSampleDefaults(traceIn, traceOut, coerce, layout) {
171176
len = Math.min(sLen, xLen, y.length);
172177
} else {
173178
for(i = 0; i < y.length; i++) {
174-
if(autoType(y[i]) === 'category') {
179+
if(autoType(y[i], calendar, opts) === 'category') {
175180
hasCategories = true;
176181
break;
177182
}

src/traces/carpet/ab_defaults.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ function mimickAxisDefaults(traceIn, traceOut, fullLayout, dfltColor) {
4747
calendar: traceOut.calendar,
4848
dfltColor: dfltColor,
4949
bgColor: fullLayout.paper_bgcolor,
50+
autotypenumbersDflt: fullLayout.autotypenumbers,
5051
fullLayout: fullLayout
5152
};
5253

src/traces/carpet/axis_attributes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ module.exports = {
8888
'the axis in question.'
8989
].join(' ')
9090
},
91+
autotypenumbers: axesAttrs.autotypenumbers,
9192
autorange: {
9293
valType: 'enumerated',
9394
values: [true, false, 'reversed'],

src/traces/carpet/axis_defaults.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, options)
5151
}
5252

5353
// now figure out type and do some more initialization
54+
coerce('autotypenumbers', options.autotypenumbersDflt);
5455
var axType = coerce('type');
5556
if(axType === '-') {
5657
if(options.data) setAutoType(containerOut, options.data);
@@ -219,5 +220,7 @@ function setAutoType(ax, data) {
219220
var calAttr = axLetter + 'calendar';
220221
var calendar = ax[calAttr];
221222

222-
ax.type = autoType(data, calendar);
223+
ax.type = autoType(data, calendar, {
224+
autotypenumbers: ax.autotypenumbers
225+
});
223226
}

0 commit comments

Comments
 (0)