Skip to content

Multicategory sorting fixes #3362

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Dec 21, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 55 additions & 33 deletions src/plots/cartesian/set_convert.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
18 changes: 11 additions & 7 deletions src/plots/plots.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -2579,7 +2579,7 @@ plots.doCalcdata = function(gd, traces) {
);
}

clearAxesCalc(axList);
setupAxisCategories(axList, fullData);

var hasCalcTransform = false;

Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clearCalc is also used in rangeslider.draw and gl2d/scene2d - do these need setupMultiCategory? Should that just be part of clearCalc for multicategory axes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clearCalc is also used in rangeslider.draw and gl2d/scene2d - do these need setupMultiCategory?

setupMultiCategory only needs to be called once (here in doCalcdata, to fill in ax._muticatList), so rangeslider don't need it, that's why I didn't put the setupMultiCategory logic in clearCalc. As for gl2d/scene2d, gl2d subplots don't support multicategory axes, so I think we're 👌

}
}

Expand Down
Binary file added test/image/baselines/multicategory-sorting.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/multicategory-y.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/image/baselines/multicategory2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
52 changes: 52 additions & 0 deletions test/image/mocks/multicategory-sorting.json
Original file line number Diff line number Diff line change
@@ -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
}
}
10 changes: 6 additions & 4 deletions test/jasmine/tests/axes_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down Expand Up @@ -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});
});
Expand All @@ -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});
});
Expand All @@ -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});
});
Expand Down Expand Up @@ -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});
});
Expand Down