diff --git a/draftlogs/7959_fix.md b/draftlogs/7959_fix.md new file mode 100644 index 00000000000..83f894cd694 --- /dev/null +++ b/draftlogs/7959_fix.md @@ -0,0 +1 @@ +- Fix numeric color sorting for bundled parallel-categories (parcats) paths [[#7959](https://github.com/plotly/plotly.js/pull/7959)], with thanks to @CAOShurong for the contribution! diff --git a/src/traces/parcats/parcats.js b/src/traces/parcats/parcats.js index 39f75d3b674..77e226b4032 100644 --- a/src/traces/parcats/parcats.js +++ b/src/traces/parcats/parcats.js @@ -370,6 +370,33 @@ function compareRawColor(a, b) { } } +/** + * Compare two sort arrays element by element in ascending order. + * Values that do not order against each other, for example NaN, sort last. + * The shorter array sorts first when one array is a prefix of the other. + * + * @param {Array} a + * @param {Array} b + */ +function compareArrays(a, b) { + for(var i = 0; i < Math.min(a.length, b.length); i++) { + var valA = a[i]; + var valB = b[i]; + + if(valA < valB) return -1; + if(valA > valB) return 1; + // Handle values that do not order against each other (NaN, undefined, etc.) + if(valA !== valB) { + // Sort these after every orderable value. + var badA = isNaN(valA); + var badB = isNaN(valB); + if(badA !== badB) return badA ? 1 : -1; + } + } + + return a.length - b.length; +} + /** * Handle path mouseover * @param {PathViewModel} d @@ -1734,15 +1761,8 @@ function updatePathViewModels(parcatsViewModel) { sortArray2.unshift(v2.rawColor); } - // colors equal, sort by display categories - if(sortArray1 < sortArray2) { - return -1; - } - if(sortArray1 > sortArray2) { - return 1; - } - - return 0; + // Sort by color, then display categories + return compareArrays(sortArray1, sortArray2); }); // Create path models diff --git a/test/jasmine/tests/parcats_test.js b/test/jasmine/tests/parcats_test.js index f6210c4521d..390d2b1d0ca 100644 --- a/test/jasmine/tests/parcats_test.js +++ b/test/jasmine/tests/parcats_test.js @@ -284,6 +284,55 @@ describe('Basic parcats trace', function() { .then(done, done.fail); }); + it('should sort bundled paths by numeric color values', function(done) { + var trace = { + type: 'parcats', + dimensions: [ + {values: ['a', 'a', 'a', 'a']}, + {values: ['b', 'b', 'b', 'b']} + ], + line: {color: [1, 10, 2, 20]}, + bundlecolors: true + }; + + Plotly.newPlot(gd, [trace]) + .then(function() { + var parcatsViewModel = d3Select('g.trace.parcats').datum(); + var pathColors = parcatsViewModel.paths.map(function(path) { + return path.model.rawColor; + }); + + expect(pathColors).toEqual([1, 2, 10, 20]); + }) + .then(done, done.fail); + }); + + it('should sort NaN color values after orderable values', function(done) { + var trace = { + type: 'parcats', + dimensions: [ + {values: ['a', 'a', 'a', 'a']}, + {values: ['b', 'b', 'b', 'b']} + ], + line: {color: [10, NaN, 2, NaN]}, + bundlecolors: true + }; + + Plotly.newPlot(gd, [trace]) + .then(function() { + var parcatsViewModel = d3Select('g.trace.parcats').datum(); + var pathColors = parcatsViewModel.paths.map(function(path) { + return path.model.rawColor; + }); + + // Orderable values sort first, NaN values sort last + expect(pathColors.length).toBe(3) + expect(pathColors.slice(0, 2)).toEqual([2, 10]); + expect(isNaN(pathColors[2])).toBe(true); + }) + .then(done, done.fail); + }); + it('should compute initial model views properly', function(done) { Plotly.newPlot(gd, basicMock) .then(function() {