Skip to content
Draft
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
1 change: 1 addition & 0 deletions draftlogs/7931_fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Fix `multicategory` axes ordering second-level categories by a single global order instead of the data order within each first-level category [[#7931](https://github.com/plotly/plotly.js/pull/7931)]
43 changes: 22 additions & 21 deletions src/plots/cartesian/set_convert.js
Original file line number Diff line number Diff line change
Expand Up @@ -371,10 +371,15 @@ module.exports = function setConvert(ax, fullLayout) {
}
}

// [ [cnt, {$cat: index}], for 1,2 ]
var seen = [[0, {}], [0, {}]];
// [ [arrayIn[0][i], arrayIn[1][i]], for i .. N ]
var list = [];
// first-level categories in first-appearance order
var parents = [];
// {$parentCat: {seen: {$childCat: 1}, children: [$childCat, ..]}}
// second-level categories are tracked *per parent*, so that each
// parent keeps the child order found in its own data rather than
// sharing one global order across all parents.
// prototype-less objects so that e.g. a category named 'toString'
// does not resolve through Object.prototype
var childrenOf = Object.create(null);

for(i = 0; i < traceIndices.length; i++) {
var trace = fullData[traceIndices[i]];
Expand All @@ -389,31 +394,27 @@ module.exports = function setConvert(ax, fullLayout) {
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(!(v0 in childrenOf)) {
childrenOf[v0] = {seen: Object.create(null), children: []};
parents.push(v0);
}
if(!(v1 in seen[1][1])) {
seen[1][1][v1] = seen[1][0]++;

var kids = childrenOf[v0];
if(!(v1 in kids.seen)) {
kids.seen[v1] = 1;
kids.children.push(v1);
}
}
}
}
}
}

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]);
for(i = 0; i < parents.length; i++) {
var children = childrenOf[parents[i]].children;
for(j = 0; j < children.length; j++) {
setCategoryIndex([parents[i], children[j]]);
}
}
};
}
Expand Down
21 changes: 21 additions & 0 deletions test/jasmine/tests/axes_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4354,6 +4354,27 @@ describe('Test axes', function() {
expect(ax._categoriesMap).toEqual({'1,a': 0, '1,b': 1, '2,a': 2, '2,b': 3});
});

it('should order second-level categories per parent, not globally', function() {
var out = _makeCalcdata({
x: [['1', '1', '2', '2'], ['b', 'a', 'a', 'b']]
}, 'x', 'multicategory');

// '2' keeps its own 'a' then 'b' order, even though 'b' is
// the first second-level category seen overall (under '1')
expect(out).toEqual([0, 1, 2, 3]);
expect(ax._categories).toEqual([['1', 'b'], ['1', 'a'], ['2', 'a'], ['2', 'b']]);
expect(ax._categoriesMap).toEqual({'1,b': 0, '1,a': 1, '2,a': 2, '2,b': 3});
});

it('should not let second-level categories inherit the prototype chain', function() {
var out = _makeCalcdata({
x: [['1', '1'], ['toString', 'a']]
}, 'x', 'multicategory');

expect(out).toEqual([0, 1]);
expect(ax._categories).toEqual([['1', 'toString'], ['1', 'a']]);
});

it('case invalid in x[0]', function() {
var out = _makeCalcdata({
x: [['1', '2', null, '2'], ['a', 'a', 'b', 'b']]
Expand Down
Loading