diff --git a/src/plots/cartesian/set_convert.js b/src/plots/cartesian/set_convert.js index 2f5c25cb04f..8129d535188 100644 --- a/src/plots/cartesian/set_convert.js +++ b/src/plots/cartesian/set_convert.js @@ -17,6 +17,7 @@ var cleanNumber = Lib.cleanNumber; var ms2DateTime = Lib.ms2DateTime; var dateTime2ms = Lib.dateTime2ms; var ensureNumber = Lib.ensureNumber; +var isArrayOrTypedArray = Lib.isArrayOrTypedArray; var numConstants = require('../../constants/numerical'); var FP_SAFE = numConstants.FP_SAFE; @@ -148,40 +149,11 @@ module.exports = function setConvert(ax, fullLayout) { function setMultiCategoryIndex(arrayIn, len) { var arrayOut = new Array(len); - var i; - // [ [arrayIn[0][i], arrayIn[1][i]], for i .. len ] - var tmp = new Array(len); - // [ [cnt, {$cat: index}], for j .. arrayIn.length ] - var seen = [[0, {}], [0, {}]]; - - if(Lib.isArrayOrTypedArray(arrayIn[0]) && Lib.isArrayOrTypedArray(arrayIn[1])) { - for(i = 0; i < len; i++) { - var v0 = arrayIn[0][i]; - var v1 = arrayIn[1][i]; - if(isValidCategory(v0) && isValidCategory(v1)) { - tmp[i] = [v0, v1]; - if(!(v0 in seen[0][1])) { - seen[0][1][v0] = seen[0][0]++; - } - if(!(v1 in seen[1][1])) { - seen[1][1][v1] = seen[1][0]++; - } - } - } - - tmp.sort(function(a, b) { - var ind0 = seen[0][1]; - var d = ind0[a[0]] - ind0[b[0]]; - if(d) return d; - - var ind1 = seen[1][1]; - return ind1[a[1]] - ind1[b[1]]; - }); - } - - for(i = 0; i < len; i++) { - arrayOut[i] = setCategoryIndex(tmp[i]); + for(var i = 0; i < len; i++) { + var v0 = (arrayIn[0] || [])[i]; + var v1 = (arrayIn[1] || [])[i]; + arrayOut[i] = getCategoryIndex([v0, v1]); } return arrayOut; @@ -330,6 +302,56 @@ module.exports = function setConvert(ax, fullLayout) { if(Array.isArray(v) || (typeof v === 'string' && v !== '')) return v; return ensureNumber(v); }; + + ax.setupMultiCategory = function(fullData) { + var traceIndices = ax._traceIndices; + var i, j; + + // [ [cnt, {$cat: index}], for 1,2 ] + var seen = ax._multicatSeen = [[0, {}], [0, {}]]; + // [ [arrayIn[0][i], arrayIn[1][i]], for i .. N ] + var list = ax._multicatList = []; + + for(i = 0; i < traceIndices.length; i++) { + var trace = fullData[traceIndices[i]]; + + if(axLetter in trace) { + var arrayIn = trace[axLetter]; + var len = trace._length || Lib.minRowLength(arrayIn); + + if(isArrayOrTypedArray(arrayIn[0]) && isArrayOrTypedArray(arrayIn[1])) { + for(j = 0; j < len; j++) { + var v0 = arrayIn[0][j]; + var v1 = arrayIn[1][j]; + + if(isValidCategory(v0) && isValidCategory(v1)) { + list.push([v0, v1]); + + if(!(v0 in seen[0][1])) { + seen[0][1][v0] = seen[0][0]++; + } + if(!(v1 in seen[1][1])) { + seen[1][1][v1] = seen[1][0]++; + } + } + } + } + } + } + + list.sort(function(a, b) { + var ind0 = seen[0][1]; + var d = ind0[a[0]] - ind0[b[0]]; + if(d) return d; + + var ind1 = seen[1][1]; + return ind1[a[1]] - ind1[b[1]]; + }); + + for(i = 0; i < list.length; i++) { + setCategoryIndex(list[i]); + } + }; } // find the range value at the specified (linear) fraction of the axis diff --git a/src/plots/plots.js b/src/plots/plots.js index 987ec881f1e..ebf4d96628e 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -2524,9 +2524,9 @@ plots.transition = function(gd, data, layout, traces, frameOpts, transitionOpts) }; plots.doCalcdata = function(gd, traces) { - var axList = axisIDs.list(gd), - fullData = gd._fullData, - fullLayout = gd._fullLayout; + var axList = axisIDs.list(gd); + var fullData = gd._fullData; + var fullLayout = gd._fullLayout; var trace, _module, i, j; @@ -2579,7 +2579,7 @@ plots.doCalcdata = function(gd, traces) { ); } - clearAxesCalc(axList); + setupAxisCategories(axList, fullData); var hasCalcTransform = false; @@ -2617,7 +2617,7 @@ plots.doCalcdata = function(gd, traces) { } // clear stuff that should recomputed in 'regular' loop - if(hasCalcTransform) clearAxesCalc(axList); + if(hasCalcTransform) setupAxisCategories(axList, fullData); function calci(i, isContainer) { trace = fullData[i]; @@ -2675,9 +2675,13 @@ plots.doCalcdata = function(gd, traces) { Registry.getComponentMethod('errorbars', 'calc')(gd); }; -function clearAxesCalc(axList) { +function setupAxisCategories(axList, fullData) { for(var i = 0; i < axList.length; i++) { - axList[i].clearCalc(); + var ax = axList[i]; + ax.clearCalc(); + if(ax.type === 'multicategory') { + ax.setupMultiCategory(fullData); + } } } diff --git a/test/image/baselines/multicategory-sorting.png b/test/image/baselines/multicategory-sorting.png new file mode 100644 index 00000000000..421440aab83 Binary files /dev/null and b/test/image/baselines/multicategory-sorting.png differ diff --git a/test/image/baselines/multicategory-y.png b/test/image/baselines/multicategory-y.png index f8986f8a8e6..20e55ffa95b 100644 Binary files a/test/image/baselines/multicategory-y.png and b/test/image/baselines/multicategory-y.png differ diff --git a/test/image/baselines/multicategory2.png b/test/image/baselines/multicategory2.png index ccabc1796dc..3c5942b1272 100644 Binary files a/test/image/baselines/multicategory2.png and b/test/image/baselines/multicategory2.png differ diff --git a/test/image/mocks/multicategory-sorting.json b/test/image/mocks/multicategory-sorting.json new file mode 100644 index 00000000000..634d8ba8a9a --- /dev/null +++ b/test/image/mocks/multicategory-sorting.json @@ -0,0 +1,52 @@ +{ + "data": [{ + "x": [[1, 2, 3, 4, 5 ,6], + [1, 2, 1, 2, 1 ,2]], + "y": [1, 2, 3, 4, 5 ,6] + }, + + { + "x": [[1, 2, 3, 4, 5, 6], + [1, 2, 1, 2, 1, 2]], + "y": [1, 2, 3, 4, 5, 6], + "xaxis": "x2", + "yaxis": "y2" + }, + { + "x": [[4, 5, 6, 7, 8, 9], + [1, 2, 1, 2, 1, 2]], + "y": [1, 2, 3, 4, 5, 6], + "xaxis": "x2", + "yaxis": "y2" + }, + + { + "x": [[1, 2, 3, 4, 5, 6, 4, 5, 6, 7, 8, 9], + [1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2]], + "y": [1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6], + "xaxis": "x3", + "yaxis": "y3" + }, + + { + "x": [[1, 2, 3, 4, 5, 6], + [1, 2, 1, 2, null, 2]], + "y": [1, 2, 3, 4, 5, 6], + "xaxis": "x4", + "yaxis": "y4" + }, + { + "x": [[4, 5, 6, 7, 8, 9], + [1, null, 1, 2, 1, 2]], + "y": [1, 2, 3, 4, 5, 6], + "xaxis": "x4", + "yaxis": "y4" + }], + "layout": { + "grid": {"rows": 2, "columns": 2, "pattern": "independent", "xgap": 0.1, "ygap": 0.15}, + "width": 700, + "height": 400, + "margin": {"l": 20, "b": 40, "t": 20, "r": 20}, + "showlegend": false + } +} diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js index a4f31b78d11..81c5c16a42d 100644 --- a/test/jasmine/tests/axes_test.js +++ b/test/jasmine/tests/axes_test.js @@ -2723,6 +2723,8 @@ describe('Test axes', function() { ax = {type: axType}; Axes.setConvert(ax); ax._categories = []; + ax._traceIndices = [0]; + if(axType === 'multicategory') ax.setupMultiCategory([trace]); return ax.makeCalcdata(trace, axLetter); } @@ -2856,7 +2858,7 @@ describe('Test axes', function() { x: [['1', '2', '1', '2'], ['a', 'a', 'b', 'b']] }, 'x', 'multicategory'); - expect(out).toEqual([0, 1, 2, 3]); + expect(out).toEqual([0, 2, 1, 3]); expect(ax._categories).toEqual([['1', 'a'], ['1', 'b'], ['2', 'a'], ['2', 'b']]); expect(ax._categoriesMap).toEqual({'1,a': 0, '1,b': 1, '2,a': 2, '2,b': 3}); }); @@ -2866,7 +2868,7 @@ describe('Test axes', function() { x: [['1', '2', null, '2'], ['a', 'a', 'b', 'b']] }, 'x', 'multicategory'); - expect(out).toEqual([0, 1, 2, BADNUM]); + expect(out).toEqual([0, 1, BADNUM, 2]); expect(ax._categories).toEqual([['1', 'a'], ['2', 'a'], ['2', 'b']]); expect(ax._categoriesMap).toEqual({'1,a': 0, '2,a': 1, '2,b': 2}); }); @@ -2876,7 +2878,7 @@ describe('Test axes', function() { x: [['1', '2', '1', '2'], ['a', 'a', null, 'b']] }, 'x', 'multicategory'); - expect(out).toEqual([0, 1, 2, BADNUM]); + expect(out).toEqual([0, 1, BADNUM, 2]); expect(ax._categories).toEqual([['1', 'a'], ['2', 'a'], ['2', 'b']]); expect(ax._categoriesMap).toEqual({'1,a': 0, '2,a': 1, '2,b': 2}); }); @@ -2919,7 +2921,7 @@ describe('Test axes', function() { ] }, 'x', 'multicategory'); - expect(out).toEqual([0, 1, 2, 3]); + expect(out).toEqual([0, 2, 1, 3]); expect(ax._categories).toEqual([[1, 10], [1, 20], [2, 10], [2, 20]]); expect(ax._categoriesMap).toEqual({'1,10': 0, '1,20': 1, '2,10': 2, '2,20': 3}); });