Skip to content

Unique colors for expanded traces #1830

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 8 commits into from
Jul 10, 2017
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
49 changes: 47 additions & 2 deletions src/plots/plots.js
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,51 @@ plots.linkSubplots = function(newFullData, newFullLayout, oldFullData, oldFullLa
}
};

// This function clears any trace attributes with valType: color and
// no set dflt filed in the plot schema. This is needed because groupby (which
// is the only transform for which this currently applies) supplies parent
// trace defaults, then expanded trace defaults. The result is that `null`
// colors are default-supplied and inherited as a color instead of a null.
// The result is that expanded trace default colors have no effect, with
// the final result that groups are indistinguishable. This function clears
// those colors so that individual groupby groups get unique colors.
plots.clearExpandedTraceDefaultColors = function(trace) {
var colorAttrs, path, i;

// This uses weird closure state in order to satisfy the linter rule
// that we can't create functions in a loop.
function locateColorAttrs(attr, attrName, attrs, level) {
path[level] = attrName;
path.length = level + 1;
if(attr.valType === 'color' && attr.dflt === undefined) {
colorAttrs.push(path.join('.'));
}
}

path = [];

// Get the cached colorAttrs:
colorAttrs = trace._module._colorAttrs;

// Or else compute and cache the colorAttrs on the module:
if(!colorAttrs) {
trace._module._colorAttrs = colorAttrs = [];
PlotSchema.crawl(
trace._module.attributes,
locateColorAttrs
);
}

for(i = 0; i < colorAttrs.length; i++) {
var origprop = Lib.nestedProperty(trace, '_input.' + colorAttrs[i]);

if(!origprop.get()) {
Lib.nestedProperty(trace, colorAttrs[i]).set(null);
}
}
};


plots.supplyDataDefaults = function(dataIn, dataOut, layout, fullLayout) {
var i, fullTrace, trace;
var modules = fullLayout._modules = [],
Expand Down Expand Up @@ -694,8 +739,8 @@ plots.supplyDataDefaults = function(dataIn, dataOut, layout, fullLayout) {
var expandedTraces = applyTransforms(fullTrace, dataOut, layout, fullLayout);

for(var j = 0; j < expandedTraces.length; j++) {
var expandedTrace = expandedTraces[j],
fullExpandedTrace = plots.supplyTraceDefaults(expandedTrace, cnt, fullLayout, i);
var expandedTrace = expandedTraces[j];
var fullExpandedTrace = plots.supplyTraceDefaults(expandedTrace, cnt, fullLayout, i);

// mutate uid here using parent uid and expanded index
// to promote consistency between update calls
Expand Down
3 changes: 3 additions & 0 deletions src/transforms/groupby.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

var Lib = require('../lib');
var PlotSchema = require('../plot_api/plot_schema');
var Plots = require('../plots/plots');

exports.moduleType = 'transform';

Expand Down Expand Up @@ -172,6 +173,8 @@ function transformOne(trace, state) {

newTrace.name = groupName;

Plots.clearExpandedTraceDefaultColors(newTrace);

// there's no need to coerce styleLookup[groupName] here
// as another round of supplyDefaults is done on the transformed traces
newTrace = Lib.extendDeepNoArrays(newTrace, styleLookup[groupName] || {});
Expand Down
63 changes: 63 additions & 0 deletions test/jasmine/tests/transform_groupby_test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
var Plotly = require('@lib/index');
var Plots = require('@src/plots/plots');
var Lib = require('@src/lib');

var createGraphDiv = require('../assets/create_graph_div');
Expand Down Expand Up @@ -236,9 +237,38 @@ describe('groupby', function() {
done();
});
});
});

describe('many-to-many transforms', function() {
it('varies the color for each expanded trace', function() {
var uniqueColors = {};
var dataOut = [];
var dataIn = [{
y: [1, 2, 3],
transforms: [
{type: 'filter', operation: '<', value: 4},
{type: 'groupby', groups: ['a', 'b', 'c']}
]
}, {
y: [4, 5, 6],
transforms: [
{type: 'filter', operation: '<', value: 4},
{type: 'groupby', groups: ['a', 'b', 'b']}
]
}];

Plots.supplyDataDefaults(dataIn, dataOut, {}, {});

for(var i = 0; i < dataOut.length; i++) {
uniqueColors[dataOut[i].marker.color] = true;
}

// Confirm that five total colors exist:
expect(Object.keys(uniqueColors).length).toEqual(5);
});
});


// these tests can be shortened, once the meaning of edge cases gets clarified
describe('symmetry/degeneracy testing of one-to-many transforms on arbitrary arrays where there is no grouping (implicit 1):', function() {
'use strict';
Expand Down Expand Up @@ -662,6 +692,39 @@ describe('groupby', function() {
it('passes with no groups', test(mockData0));
it('passes with empty groups', test(mockData1));
it('passes with falsey groups', test(mockData2));
});

describe('expanded trace coloring', function() {
it('assigns unique colors to each group', function() {
var colors = [];
var dataOut = [];
var dataIn = [{
y: [1, 2, 3],
transforms: [
{type: 'filter', operation: '<', value: 4},
{type: 'groupby', groups: ['a', 'b', 'c']}
]
}, {
y: [4, 5, 6],
transforms: [
{type: 'filter', operation: '<', value: 4},
{type: 'groupby', groups: ['a', 'b', 'b']}
]
}];

Plots.supplyDataDefaults(dataIn, dataOut, {}, {});

for(var i = 0; i < dataOut.length; i++) {
colors.push(dataOut[i].marker.color);
}

expect(colors).toEqual([
'#1f77b4',
'#ff7f0e',
'#2ca02c',
'#d62728',
'#9467bd'
]);
});
});
});