diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 099848120ac..91e0f1c2f64 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -216,7 +216,9 @@ var getDataConversions = axes.getDataConversions = function(gd, trace, target, t // setup the data-to-calc method. if(Array.isArray(d2cTarget)) { ax = { - type: autoType(targetArray), + type: autoType(targetArray, undefined, { + autotypenumbers: gd._fullLayout.autotypenumbers + }), _categories: [] }; axes.setConvert(ax); diff --git a/src/plots/cartesian/axis_autotype.js b/src/plots/cartesian/axis_autotype.js index 2cfd9707013..de9e8045512 100644 --- a/src/plots/cartesian/axis_autotype.js +++ b/src/plots/cartesian/axis_autotype.js @@ -14,23 +14,49 @@ var isNumeric = require('fast-isnumeric'); var Lib = require('../../lib'); var BADNUM = require('../../constants/numerical').BADNUM; +var isArrayOrTypedArray = Lib.isArrayOrTypedArray; +var isDateTime = Lib.isDateTime; +var cleanNumber = Lib.cleanNumber; +var round = Math.round; + module.exports = function autoType(array, calendar, opts) { - opts = opts || {}; + var a = array; + + var noMultiCategory = opts.noMultiCategory; + if(isArrayOrTypedArray(a) && !a.length) return '-'; + if(!noMultiCategory && multiCategory(a)) return 'multicategory'; + if(noMultiCategory && Array.isArray(a[0])) { // no need to flat typed arrays here + var b = []; + for(var i = 0; i < a.length; i++) { + if(isArrayOrTypedArray(a[i])) { + for(var j = 0; j < a[i].length; j++) { + b.push(a[i][j]); + } + } + } + a = b; + } + + if(moreDates(a, calendar)) return 'date'; - if(!opts.noMultiCategory && multiCategory(array)) return 'multicategory'; - if(moreDates(array, calendar)) return 'date'; - if(category(array)) return 'category'; - if(linearOK(array)) return 'linear'; - else return '-'; + var convertNumeric = opts.autotypenumbers !== 'strict'; // compare against strict, just in case autotypenumbers was not provided in opts + if(category(a, convertNumeric)) return 'category'; + if(linearOK(a, convertNumeric)) return 'linear'; + + return '-'; }; +function hasTypeNumber(v, convertNumeric) { + return convertNumeric ? isNumeric(v) : typeof v === 'number'; +} + // is there at least one number in array? If not, we should leave // ax.type empty so it can be autoset later -function linearOK(array) { - if(!array) return false; +function linearOK(a, convertNumeric) { + var len = a.length; - for(var i = 0; i < array.length; i++) { - if(isNumeric(array[i])) return true; + for(var i = 0; i < len; i++) { + if(hasTypeNumber(a[i], convertNumeric)) return true; } return false; @@ -43,51 +69,61 @@ function linearOK(array) { // numbers and a few dates // as with categories, consider DISTINCT values only. function moreDates(a, calendar) { - // test at most 1000 points, evenly spaced - var inc = Math.max(1, (a.length - 1) / 1000); - var dcnt = 0; - var ncnt = 0; + var len = a.length; + + var inc = getIncrement(len); + var dats = 0; + var nums = 0; var seen = {}; - for(var i = 0; i < a.length; i += inc) { - var ai = a[Math.round(i)]; + for(var f = 0; f < len; f += inc) { + var i = round(f); + var ai = a[i]; var stri = String(ai); if(seen[stri]) continue; seen[stri] = 1; - if(Lib.isDateTime(ai, calendar)) dcnt += 1; - if(isNumeric(ai)) ncnt += 1; + if(isDateTime(ai, calendar)) dats++; + if(isNumeric(ai)) nums++; } - return (dcnt > ncnt * 2); + return dats > nums * 2; +} + +// return increment to test at most 1000 points, evenly spaced +function getIncrement(len) { + return Math.max(1, (len - 1) / 1000); } // are the (x,y)-values in gd.data mostly text? // require twice as many DISTINCT categories as distinct numbers -function category(a) { - // test at most 1000 points - var inc = Math.max(1, (a.length - 1) / 1000); - var curvenums = 0; - var curvecats = 0; +function category(a, convertNumeric) { + var len = a.length; + + var inc = getIncrement(len); + var nums = 0; + var cats = 0; var seen = {}; - for(var i = 0; i < a.length; i += inc) { - var ai = a[Math.round(i)]; + for(var f = 0; f < len; f += inc) { + var i = round(f); + var ai = a[i]; var stri = String(ai); if(seen[stri]) continue; seen[stri] = 1; - if(typeof ai === 'boolean') curvecats++; - else if(Lib.cleanNumber(ai) !== BADNUM) curvenums++; - else if(typeof ai === 'string') curvecats++; + var t = typeof ai; + if(t === 'boolean') cats++; + else if(convertNumeric ? cleanNumber(ai) !== BADNUM : t === 'number') nums++; + else if(t === 'string') cats++; } - return curvecats > curvenums * 2; + return cats > nums * 2; } // very-loose requirements for multicategory, // trace modules that should never auto-type to multicategory // should be declared with 'noMultiCategory' function multiCategory(a) { - return Lib.isArrayOrTypedArray(a[0]) && Lib.isArrayOrTypedArray(a[1]); + return isArrayOrTypedArray(a[0]) && isArrayOrTypedArray(a[1]); } diff --git a/src/plots/cartesian/layout_attributes.js b/src/plots/cartesian/layout_attributes.js index d62b424256a..9251e2cd50b 100644 --- a/src/plots/cartesian/layout_attributes.js +++ b/src/plots/cartesian/layout_attributes.js @@ -102,6 +102,19 @@ module.exports = { 'the axis in question.' ].join(' ') }, + autotypenumbers: { + valType: 'enumerated', + values: ['convert types', 'strict'], + dflt: 'convert types', + role: 'info', + editType: 'calc', + description: [ + 'Using *strict* a numeric string in trace data is not converted to a number.', + 'Using *convert types* a numeric string in trace data may be', + 'treated as a number during automatic axis `type` detection.', + 'Defaults to layout.autotypenumbers.' + ].join(' ') + }, autorange: { valType: 'enumerated', values: [true, false, 'reversed'], diff --git a/src/plots/cartesian/layout_defaults.js b/src/plots/cartesian/layout_defaults.js index 3e411ae0574..6ce3c069db1 100644 --- a/src/plots/cartesian/layout_defaults.js +++ b/src/plots/cartesian/layout_defaults.js @@ -38,6 +38,8 @@ function appendList(cont, k, item) { } module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { + var autotypenumbersDflt = layoutOut.autotypenumbers; + var ax2traces = {}; var xaMayHide = {}; var yaMayHide = {}; @@ -246,6 +248,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { automargin: true, visibleDflt: visibleDflt, reverseDflt: reverseDflt, + autotypenumbersDflt: autotypenumbersDflt, splomStash: ((layoutOut._splomAxes || {})[axLetter] || {})[axId] }; @@ -310,6 +313,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { automargin: true, visibleDflt: false, reverseDflt: false, + autotypenumbersDflt: autotypenumbersDflt, splomStash: ((layoutOut._splomAxes || {})[axLetter] || {})[axId] }; diff --git a/src/plots/cartesian/type_defaults.js b/src/plots/cartesian/type_defaults.js index f9d73cd3663..80a6933cee4 100644 --- a/src/plots/cartesian/type_defaults.js +++ b/src/plots/cartesian/type_defaults.js @@ -16,6 +16,7 @@ var autoType = require('./axis_autotype'); * name: axis object name (ie 'xaxis') if one should be stored */ module.exports = function handleTypeDefaults(containerIn, containerOut, coerce, options) { + coerce('autotypenumbers', options.autotypenumbersDflt); var axType = coerce('type', (options.splomStash || {}).type); if(axType === '-') { @@ -68,6 +69,8 @@ function setAutoType(ax, data) { opts.noMultiCategory = true; } + opts.autotypenumbers = ax.autotypenumbers; + // check all boxes on this x axis to see // if they're dates, numbers, or categories if(isBoxWithoutPositionCoords(d0, axLetter)) { diff --git a/src/plots/gl3d/layout/axis_attributes.js b/src/plots/gl3d/layout/axis_attributes.js index 76081cc764f..587deabdf2c 100644 --- a/src/plots/gl3d/layout/axis_attributes.js +++ b/src/plots/gl3d/layout/axis_attributes.js @@ -78,6 +78,7 @@ module.exports = overrideAll({ type: extendFlat({}, axesAttrs.type, { values: ['-', 'linear', 'log', 'date', 'category'] }), + autotypenumbers: axesAttrs.autotypenumbers, autorange: axesAttrs.autorange, rangemode: axesAttrs.rangemode, range: extendFlat({}, axesAttrs.range, { diff --git a/src/plots/gl3d/layout/defaults.js b/src/plots/gl3d/layout/defaults.js index dd4c3eca2ce..be2b7da07e1 100644 --- a/src/plots/gl3d/layout/defaults.js +++ b/src/plots/gl3d/layout/defaults.js @@ -40,6 +40,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { font: layoutOut.font, fullData: fullData, getDfltFromLayout: getDfltFromLayout, + autotypenumbersDflt: layoutOut.autotypenumbers, paper_bgcolor: layoutOut.paper_bgcolor, calendar: layoutOut.calendar }); @@ -109,6 +110,7 @@ function handleGl3dDefaults(sceneLayoutIn, sceneLayoutOut, coerce, opts) { data: fullGl3dData, bgColor: bgColorCombined, calendar: opts.calendar, + autotypenumbersDflt: opts.autotypenumbersDflt, fullLayout: opts.fullLayout }); diff --git a/src/plots/layout_attributes.js b/src/plots/layout_attributes.js index e7b9191182f..da0a15d8d91 100644 --- a/src/plots/layout_attributes.js +++ b/src/plots/layout_attributes.js @@ -292,6 +292,19 @@ module.exports = { 'Sets the background color of the plotting area in-between x and y axes.' ].join(' ') }, + autotypenumbers: { + valType: 'enumerated', + values: ['convert types', 'strict'], + dflt: 'convert types', + role: 'info', + editType: 'calc', + description: [ + 'Using *strict* a numeric string in trace data is not converted to a number.', + 'Using *convert types* a numeric string in trace data may be', + 'treated as a number during automatic axis `type` detection.', + 'This is the default value; however it could be overridden for individual axes.' + ].join(' ') + }, separators: { valType: 'string', role: 'style', diff --git a/src/plots/plots.js b/src/plots/plots.js index 34646cb5668..fe9a2eed65f 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -1476,6 +1476,8 @@ plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut, formatObj) { layoutOut._dataTemplate = template.data; } + coerce('autotypenumbers'); + var globalFont = Lib.coerceFont(coerce, 'font'); coerce('title.text', layoutOut._dfltTitle.plot); diff --git a/src/plots/polar/layout_attributes.js b/src/plots/polar/layout_attributes.js index c9cf34ecf29..e4f7630d725 100644 --- a/src/plots/polar/layout_attributes.js +++ b/src/plots/polar/layout_attributes.js @@ -61,6 +61,7 @@ var radialAxisAttrs = { type: extendFlat({}, axesAttrs.type, { values: ['-', 'linear', 'log', 'date', 'category'] }), + autotypenumbers: axesAttrs.autotypenumbers, autorange: extendFlat({}, axesAttrs.autorange, {editType: 'plot'}), rangemode: { @@ -179,6 +180,7 @@ var angularAxisAttrs = { 'If *category, use `period` to set the number of integer coordinates around polar axis.' ].join(' ') }, + autotypenumbers: axesAttrs.autotypenumbers, categoryorder: axesAttrs.categoryorder, categoryarray: axesAttrs.categoryarray, diff --git a/src/plots/polar/layout_defaults.js b/src/plots/polar/layout_defaults.js index f751ad9cef8..5bdc69c746a 100644 --- a/src/plots/polar/layout_defaults.js +++ b/src/plots/polar/layout_defaults.js @@ -57,7 +57,7 @@ function handleDefaults(contIn, contOut, coerce, opts) { axOut._traceIndices = subplotData.map(function(t) { return t._expandedIndex; }); var dataAttr = constants.axisName2dataArray[axName]; - var axType = handleAxisTypeDefaults(axIn, axOut, coerceAxis, subplotData, dataAttr); + var axType = handleAxisTypeDefaults(axIn, axOut, coerceAxis, subplotData, dataAttr, opts); handleCategoryOrderDefaults(axIn, axOut, coerceAxis, { axData: subplotData, @@ -187,7 +187,8 @@ function handleDefaults(contIn, contOut, coerce, opts) { } } -function handleAxisTypeDefaults(axIn, axOut, coerce, subplotData, dataAttr) { +function handleAxisTypeDefaults(axIn, axOut, coerce, subplotData, dataAttr, options) { + var autotypenumbers = coerce('autotypenumbers', options.autotypenumbersDflt); var axType = coerce('type'); if(axType === '-') { @@ -201,7 +202,10 @@ function handleAxisTypeDefaults(axIn, axOut, coerce, subplotData, dataAttr) { } if(trace && trace[dataAttr]) { - axOut.type = autoType(trace[dataAttr], 'gregorian'); + axOut.type = autoType(trace[dataAttr], 'gregorian', { + noMultiCategory: true, + autotypenumbers: autotypenumbers + }); } if(axOut.type === '-') { @@ -224,6 +228,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) { attributes: layoutAttributes, handleDefaults: handleDefaults, font: layoutOut.font, + autotypenumbersDflt: layoutOut.autotypenumbers, paper_bgcolor: layoutOut.paper_bgcolor, fullData: fullData, layoutOut: layoutOut diff --git a/src/traces/box/defaults.js b/src/traces/box/defaults.js index 849a3d433da..b0e155042b1 100644 --- a/src/traces/box/defaults.js +++ b/src/traces/box/defaults.js @@ -109,6 +109,11 @@ function handleSampleDefaults(traceIn, traceOut, coerce, layout) { var yLen = yDims && Lib.minRowLength(y); var xLen = xDims && Lib.minRowLength(x); + var calendar = layout.calendar; + var opts = { + autotypenumbers: layout.autotypenumbers + }; + var defaultOrientation, len; if(traceOut._hasPreCompStats) { switch(String(xDims) + String(yDims)) { @@ -160,7 +165,7 @@ function handleSampleDefaults(traceIn, traceOut, coerce, layout) { var hasCategories = false; var i; for(i = 0; i < x.length; i++) { - if(autoType(x[i]) === 'category') { + if(autoType(x[i], calendar, opts) === 'category') { hasCategories = true; break; } @@ -171,7 +176,7 @@ function handleSampleDefaults(traceIn, traceOut, coerce, layout) { len = Math.min(sLen, xLen, y.length); } else { for(i = 0; i < y.length; i++) { - if(autoType(y[i]) === 'category') { + if(autoType(y[i], calendar, opts) === 'category') { hasCategories = true; break; } diff --git a/src/traces/carpet/ab_defaults.js b/src/traces/carpet/ab_defaults.js index b0875392f19..76f1c0bde59 100644 --- a/src/traces/carpet/ab_defaults.js +++ b/src/traces/carpet/ab_defaults.js @@ -47,6 +47,7 @@ function mimickAxisDefaults(traceIn, traceOut, fullLayout, dfltColor) { calendar: traceOut.calendar, dfltColor: dfltColor, bgColor: fullLayout.paper_bgcolor, + autotypenumbersDflt: fullLayout.autotypenumbers, fullLayout: fullLayout }; diff --git a/src/traces/carpet/axis_attributes.js b/src/traces/carpet/axis_attributes.js index a6e1ea53cfb..b308bdbfaf8 100644 --- a/src/traces/carpet/axis_attributes.js +++ b/src/traces/carpet/axis_attributes.js @@ -88,6 +88,7 @@ module.exports = { 'the axis in question.' ].join(' ') }, + autotypenumbers: axesAttrs.autotypenumbers, autorange: { valType: 'enumerated', values: [true, false, 'reversed'], diff --git a/src/traces/carpet/axis_defaults.js b/src/traces/carpet/axis_defaults.js index f6ae5e36344..0c28cfdc741 100644 --- a/src/traces/carpet/axis_defaults.js +++ b/src/traces/carpet/axis_defaults.js @@ -51,6 +51,7 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, options) } // now figure out type and do some more initialization + coerce('autotypenumbers', options.autotypenumbersDflt); var axType = coerce('type'); if(axType === '-') { if(options.data) setAutoType(containerOut, options.data); @@ -219,5 +220,7 @@ function setAutoType(ax, data) { var calAttr = axLetter + 'calendar'; var calendar = ax[calAttr]; - ax.type = autoType(data, calendar); + ax.type = autoType(data, calendar, { + autotypenumbers: ax.autotypenumbers + }); } diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js index c6f864dda9b..009bd26e37a 100644 --- a/test/jasmine/tests/axes_test.js +++ b/test/jasmine/tests/axes_test.js @@ -190,6 +190,7 @@ describe('Test axes', function() { beforeEach(function() { layoutOut = { + autotypenumbers: 'convert types', _has: Plots._hasPlotType, _basePlotModules: [], _dfltTitle: {x: 'x', y: 'y'}, @@ -331,7 +332,92 @@ describe('Test axes', function() { ['d', 'e', 'f'] ] }); - checkTypes('linear', 'linear'); + checkTypes('linear', 'category'); + }); + }); + + describe('autotype disable/enable converting numeric strings', function() { + it('should disable converting numeric strings using axis.autotypenumbers', function() { + layoutIn = { + xaxis: {}, + yaxis: { autotypenumbers: 'strict' } + }; + + supplyLayoutDefaults(layoutIn, layoutOut, [{ + type: 'scatter', + xaxis: 'x', + yaxis: 'y', + x: ['0', '1', '1970', '2000'], + y: ['0', '1', '1970', '2000'] + }]); + + expect(layoutOut.xaxis.autotypenumbers).toBe('convert types'); + expect(layoutOut.yaxis.autotypenumbers).toBe('strict'); + expect(layoutOut.xaxis.type).toBe('linear'); + expect(layoutOut.yaxis.type).toBe('category'); + }); + + it('should enable converting numeric strings using axis.autotypenumbers and inherit defaults from layout.autotypenumbers', function() { + layoutOut.autotypenumbers = 'strict'; + + layoutIn = { + xaxis: { autotypenumbers: 'convert types' }, + yaxis: {} + }; + + supplyLayoutDefaults(layoutIn, layoutOut, [{ + type: 'scatter', + xaxis: 'x', + yaxis: 'y', + x: ['0', '1', '1970', '2000'], + y: ['0', '1', '1970', '2000'] + }]); + + expect(layoutOut.xaxis.autotypenumbers).toBe('convert types'); + expect(layoutOut.yaxis.autotypenumbers).toBe('strict'); + expect(layoutOut.xaxis.type).toBe('linear'); + expect(layoutOut.yaxis.type).toBe('category'); + }); + + it('should autotype date having more dates with & without strict autotypenumbers', function() { + layoutIn = { + xaxis: {}, + yaxis: { autotypenumbers: 'strict' } + }; + + var dates = [ + 0, + '0', + '00', + '0000', + '1970', + '2000', + '2001-01', + '2001-02', + '2001-03', + '2001-04', + '2001-05', + '2001-06', + '2001-07', + '2001-08', + '2001-09', + '2001-10', + '2001-11', + '2001-12' + ]; + + supplyLayoutDefaults(layoutIn, layoutOut, [{ + type: 'scatter', + xaxis: 'x', + yaxis: 'y', + x: dates, + y: dates + }]); + + expect(layoutOut.xaxis.autotypenumbers).toBe('convert types'); + expect(layoutOut.yaxis.autotypenumbers).toBe('strict'); + expect(layoutOut.xaxis.type).toBe('date'); + expect(layoutOut.yaxis.type).toBe('date'); }); }); diff --git a/test/jasmine/tests/box_test.js b/test/jasmine/tests/box_test.js index 9ea11d6b406..bf915365c95 100644 --- a/test/jasmine/tests/box_test.js +++ b/test/jasmine/tests/box_test.js @@ -26,7 +26,7 @@ describe('Test boxes supplyDefaults', function() { it('should set visible to false when x and y are empty', function() { traceIn = {}; - supplyDefaults(traceIn, traceOut, defaultColor); + supplyDefaults(traceIn, traceOut, defaultColor, {}); expect(traceOut.visible).toBe(false); traceIn = { @@ -636,6 +636,82 @@ describe('Test boxes supplyDefaults', function() { }); }); +describe('Test box autoType', function() { + it('should disable converting numeric strings using axis.autotypenumbers', function() { + var gd = { + layout: { + xaxis: { autotypenumbers: 'strict' } + }, + data: [{ + type: 'box', + x: ['3', '0', '1', '2'], + + xaxis: 'x', + lowerfence: ['0', '0', '0', '0'], + q1: ['0.5', '1', '1.5', '2'], + median: ['1', '2', '3', '4'], + q3: ['1.5', '3', '4.5', '6'], + upperfence: ['2', '4', '6', '8'], + }] + }; + + supplyAllDefaults(gd); + + expect(gd._fullLayout.xaxis.autotypenumbers).toBe('strict'); + expect(gd._fullLayout.xaxis.type).toBe('category'); + }); + + it('should enable converting numeric strings using axis.autotypenumbers', function() { + var gd = { + layout: { + autotypenumbers: 'strict', + xaxis: { autotypenumbers: 'convert types' } + }, + data: [{ + type: 'box', + x: ['3', '0', '1', '2'], + + xaxis: 'x', + lowerfence: ['0', '0', '0', '0'], + q1: ['0.5', '1', '1.5', '2'], + median: ['1', '2', '3', '4'], + q3: ['1.5', '3', '4.5', '6'], + upperfence: ['2', '4', '6', '8'], + }] + }; + + supplyAllDefaults(gd); + + expect(gd._fullLayout.xaxis.autotypenumbers).toBe('convert types'); + expect(gd._fullLayout.xaxis.type).toBe('linear'); + }); + + it('should enable converting numeric inherit defaults from layout.autotypenumbers', function() { + var gd = { + layout: { + autotypenumbers: 'strict', + xaxis: {} + }, + data: [{ + type: 'box', + x: ['3', '0', '1', '2'], + + xaxis: 'x', + lowerfence: ['0', '0', '0', '0'], + q1: ['0.5', '1', '1.5', '2'], + median: ['1', '2', '3', '4'], + q3: ['1.5', '3', '4.5', '6'], + upperfence: ['2', '4', '6', '8'], + }] + }; + + supplyAllDefaults(gd); + + expect(gd._fullLayout.xaxis.autotypenumbers).toBe('strict'); + expect(gd._fullLayout.xaxis.type).toBe('category'); + }); +}); + describe('Test box hover:', function() { var gd; diff --git a/test/jasmine/tests/carpet_test.js b/test/jasmine/tests/carpet_test.js index 8bf0aab5033..fa821fbdc5c 100644 --- a/test/jasmine/tests/carpet_test.js +++ b/test/jasmine/tests/carpet_test.js @@ -217,6 +217,75 @@ describe('supplyDefaults visibility check', function() { }); }); +describe('Test carpet autoType', function() { + it('should disable converting numeric strings using axis.autotypenumbers', function() { + var gd = { + layout: { + xaxis: { autotypenumbers: 'strict' }, + yaxis: {} + }, + data: [{ + type: 'carpet', + a: ['1', '2', '3'], + b: ['1', '2'], + x: [['1', '2', '3'], ['4', '5', '6']], + y: [['1', '2', '3'], ['4', '5', '6']], + }] + }; + + supplyAllDefaults(gd); + + var xaxis = gd._fullLayout.xaxis; + var yaxis = gd._fullLayout.yaxis; + expect(xaxis.autotypenumbers).toBe('strict'); + expect(yaxis.autotypenumbers).toBe('convert types'); + expect(xaxis.type).toBe('category'); + expect(yaxis.type).toBe('linear'); + + // inherit default from layout.autotypenumbers + var aaxis = gd._fullData[0].aaxis; + var baxis = gd._fullData[0].baxis; + expect(aaxis.autotypenumbers).toBe('convert types'); + expect(baxis.autotypenumbers).toBe('convert types'); + expect(aaxis.type).toBe('linear'); + expect(baxis.type).toBe('linear'); + }); + + it('should enable converting numeric strings using axis.autotypenumbers and inherit defaults from layout.autotypenumbers', function() { + var gd = { + layout: { + autotypenumbers: 'strict', + xaxis: { autotypenumbers: 'convert types' }, + yaxis: {} + }, + data: [{ + type: 'carpet', + a: ['1', '2', '3'], + b: ['1', '2'], + x: [['1', '2', '3'], ['4', '5', '6']], + y: [['1', '2', '3'], ['4', '5', '6']], + }] + }; + + supplyAllDefaults(gd); + + var xaxis = gd._fullLayout.xaxis; + var yaxis = gd._fullLayout.yaxis; + expect(xaxis.autotypenumbers).toBe('convert types'); + expect(yaxis.autotypenumbers).toBe('strict'); + expect(xaxis.type).toBe('linear'); + expect(yaxis.type).toBe('category'); + + // inherit default from layout.autotypenumbers + var aaxis = gd._fullData[0].aaxis; + var baxis = gd._fullData[0].baxis; + expect(aaxis.autotypenumbers).toBe('strict'); + expect(baxis.autotypenumbers).toBe('strict'); + expect(aaxis.type).toBe('category'); + expect(baxis.type).toBe('category'); + }); +}); + describe('carpet smooth_fill_2d_array', function() { var _; diff --git a/test/jasmine/tests/gl3dlayout_test.js b/test/jasmine/tests/gl3dlayout_test.js index 06232dab190..a4a56bd29a0 100644 --- a/test/jasmine/tests/gl3dlayout_test.js +++ b/test/jasmine/tests/gl3dlayout_test.js @@ -26,7 +26,9 @@ describe('Test gl3d axes defaults', function() { }; beforeEach(function() { - layoutOut = {}; + layoutOut = { + autotypenumbers: 'convert types' + }; }); it('should define specific default set with empty initial layout', function() { @@ -128,6 +130,7 @@ describe('Test Gl3d layout defaults', function() { beforeEach(function() { layoutOut = { + autotypenumbers: 'convert types', _basePlotModules: ['gl3d'], _dfltTitle: {x: 'xxx', y: 'yyy', colorbar: 'cbbb'}, _subplots: {gl3d: ['scene']} @@ -380,6 +383,157 @@ describe('Test Gl3d layout defaults', function() { expect(layoutOut.scene.zaxis.gridcolor) .toEqual(tinycolor.mix('#444', bgColor, frac).toRgbString()); }); + + it('should disable converting numeric strings using axis.autotypenumbers', function() { + supplyLayoutDefaults({ + scene: { + xaxis: { autotypenumbers: 'strict' }, + yaxis: {}, + zaxis: { autotypenumbers: 'strict' } + } + }, layoutOut, [{ + type: 'scatter3d', + x: ['1970', '2000', '0', '1'], + y: ['1970', '2000', '0', '1'], + z: ['1970', '2000', '0', '1'], + scene: 'scene' + }]); + + var xaxis = layoutOut.scene.xaxis; + var yaxis = layoutOut.scene.yaxis; + var zaxis = layoutOut.scene.zaxis; + expect(xaxis.autotypenumbers).toBe('strict'); + expect(yaxis.autotypenumbers).toBe('convert types'); + expect(zaxis.autotypenumbers).toBe('strict'); + expect(xaxis.type).toBe('category'); + expect(yaxis.type).toBe('linear'); + expect(zaxis.type).toBe('category'); + }); + + it('should enable converting numeric strings using axis.autotypenumbers and inherit defaults from layout.autotypenumbers', function() { + layoutOut.autotypenumbers = 'strict'; + + supplyLayoutDefaults({ + scene: { + xaxis: { autotypenumbers: 'convert types' }, + yaxis: {}, + zaxis: { autotypenumbers: 'convert types' } + } + }, layoutOut, [{ + type: 'scatter3d', + x: ['1970', '2000', '0', '1'], + y: ['1970', '2000', '0', '1'], + z: ['1970', '2000', '0', '1'], + scene: 'scene' + }]); + + var xaxis = layoutOut.scene.xaxis; + var yaxis = layoutOut.scene.yaxis; + var zaxis = layoutOut.scene.zaxis; + expect(xaxis.autotypenumbers).toBe('convert types'); + expect(yaxis.autotypenumbers).toBe('strict'); + expect(zaxis.autotypenumbers).toBe('convert types'); + expect(xaxis.type).toBe('linear'); + expect(yaxis.type).toBe('category'); + expect(zaxis.type).toBe('linear'); + }); + + ['convert types', 'strict'].forEach(function(autotypenumbers) { + it('with autotypenumbers: *' + autotypenumbers + '* should autotype *linear* case of 2d array', function() { + var typedArray = new Float32Array(2); + typedArray[0] = 0; + typedArray[1] = 1; + + supplyLayoutDefaults({ + scene: { + xaxis: { autotypenumbers: autotypenumbers }, + yaxis: { autotypenumbers: autotypenumbers }, + zaxis: { autotypenumbers: autotypenumbers } + } + }, layoutOut, [{ + scene: 'scene', + type: 'surface', + x: [0, 1], + y: typedArray, + z: [ + typedArray, + [1, 0] + ] + }]); + + var xaxis = layoutOut.scene.xaxis; + var yaxis = layoutOut.scene.yaxis; + var zaxis = layoutOut.scene.zaxis; + expect(xaxis.autotypenumbers).toBe(autotypenumbers); + expect(yaxis.autotypenumbers).toBe(autotypenumbers); + expect(zaxis.autotypenumbers).toBe(autotypenumbers); + expect(xaxis.type).toBe('linear'); + expect(yaxis.type).toBe('linear'); + expect(zaxis.type).toBe('linear'); + }); + }); + + ['convert types', 'strict'].forEach(function(autotypenumbers) { + it('with autotypenumbers: *' + autotypenumbers + '* should autotype *category* case of 2d array', function() { + supplyLayoutDefaults({ + scene: { + xaxis: { autotypenumbers: autotypenumbers }, + yaxis: { autotypenumbers: autotypenumbers }, + zaxis: { autotypenumbers: autotypenumbers } + } + }, layoutOut, [{ + scene: 'scene', + type: 'surface', + x: ['A', 'B'], + y: ['C', 'D'], + z: [ + ['E', 'F'], + ['F', 'E'] + ] + }]); + + var xaxis = layoutOut.scene.xaxis; + var yaxis = layoutOut.scene.yaxis; + var zaxis = layoutOut.scene.zaxis; + expect(xaxis.autotypenumbers).toBe(autotypenumbers); + expect(yaxis.autotypenumbers).toBe(autotypenumbers); + expect(zaxis.autotypenumbers).toBe(autotypenumbers); + expect(xaxis.type).toBe('category'); + expect(yaxis.type).toBe('category'); + expect(zaxis.type).toBe('category'); + }); + }); + + ['convert types', 'strict'].forEach(function(autotypenumbers) { + it('with autotypenumbers: *' + autotypenumbers + '* should autotype *date* case of 2d array', function() { + supplyLayoutDefaults({ + scene: { + xaxis: { autotypenumbers: autotypenumbers }, + yaxis: { autotypenumbers: autotypenumbers }, + zaxis: { autotypenumbers: autotypenumbers } + } + }, layoutOut, [{ + scene: 'scene', + type: 'surface', + x: ['00-01', '00-02'], + y: ['00-01', '00-02'], + z: [ + ['00-01', '00-02'], + ['00-02', '00-01'] + ] + }]); + + var xaxis = layoutOut.scene.xaxis; + var yaxis = layoutOut.scene.yaxis; + var zaxis = layoutOut.scene.zaxis; + expect(xaxis.autotypenumbers).toBe(autotypenumbers); + expect(yaxis.autotypenumbers).toBe(autotypenumbers); + expect(zaxis.autotypenumbers).toBe(autotypenumbers); + expect(xaxis.type).toBe('date'); + expect(yaxis.type).toBe('date'); + expect(zaxis.type).toBe('date'); + }); + }); }); }); diff --git a/test/jasmine/tests/polar_test.js b/test/jasmine/tests/polar_test.js index 4262b87e41d..6c3482420d2 100644 --- a/test/jasmine/tests/polar_test.js +++ b/test/jasmine/tests/polar_test.js @@ -74,6 +74,7 @@ describe('Test polar plots defaults:', function() { }]; layoutOut = { + autotypenumbers: 'convert types', font: {color: 'red'}, _subplots: {polar: ['polar']} }; @@ -209,6 +210,56 @@ describe('Test polar plots defaults:', function() { expect(layoutOut.polar.radialaxis.hoverformat).toBe('g'); expect(layoutOut.polar.angularaxis.hoverformat).toBe('g'); }); + + it('should disable converting numeric strings using axis.autotypenumbers', function() { + _supply({ + polar: { + radialaxis: { + autotypenumbers: 'strict' + }, + angularaxis: { + autotypenumbers: 'strict' + } + } + }, [{ + visible: true, + type: 'scatterpolar', + r: ['0', '1', '1970', '2000'], + theta: ['0', '1', '1970', '2000'], + subplot: 'polar' + }]); + + expect(layoutOut.polar.angularaxis.autotypenumbers).toBe('strict'); + expect(layoutOut.polar.radialaxis.autotypenumbers).toBe('strict'); + expect(layoutOut.polar.radialaxis.type).toBe('category'); + expect(layoutOut.polar.angularaxis.type).toBe('category'); + }); + + it('should enable converting numeric strings using axis.autotypenumbers and inherit defaults from layout.autotypenumbers', function() { + layoutOut.autotypenumbers = 'strict'; + + _supply({ + polar: { + radialaxis: { + autotypenumbers: 'convert types' + }, + angularaxis: { + autotypenumbers: 'convert types' + } + } + }, [{ + visible: true, + type: 'scatterpolar', + r: ['0', '1', '1970', '2000'], + theta: ['0', '1', '1970', '2000'], + subplot: 'polar' + }]); + + expect(layoutOut.polar.angularaxis.autotypenumbers).toBe('convert types'); + expect(layoutOut.polar.radialaxis.autotypenumbers).toBe('convert types'); + expect(layoutOut.polar.radialaxis.type).toBe('linear'); + expect(layoutOut.polar.angularaxis.type).toBe('linear'); + }); }); describe('Test relayout on polar subplots:', function() {