Skip to content

pie color fix & enhancements #2867

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

Closed
wants to merge 1 commit into from
Closed
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
12 changes: 10 additions & 2 deletions src/plots/plots.js
Original file line number Diff line number Diff line change
Expand Up @@ -2435,8 +2435,6 @@ plots.doCalcdata = function(gd, traces) {

// for sharing colors across pies (and for legend)
fullLayout._piecolormap = {};
fullLayout._piecolorway = null;
fullLayout._piedefaultcolorcount = 0;

// If traces were specified and this trace was not included,
// then transfer it over from the old calcdata:
Expand Down Expand Up @@ -2505,6 +2503,8 @@ plots.doCalcdata = function(gd, traces) {
// clear stuff that should recomputed in 'regular' loop
if(hasCalcTransform) clearAxesCalc(axList);

var calcInteractionsFuncs = [];

function calci(i, isContainer) {
trace = fullData[i];
_module = trace._module;
Expand All @@ -2530,6 +2530,12 @@ plots.doCalcdata = function(gd, traces) {

if(_module && _module.calc) {
cd = _module.calc(gd, trace);

// Some modules need to update traces' calcdata after
// *all* traces have been through calc - so later traces can
// impact earlier traces.
var calcInteractions = _module.calcInteractions;
if(calcInteractions) Lib.pushUnique(calcInteractionsFuncs, calcInteractions);
}
}

Expand All @@ -2555,6 +2561,8 @@ plots.doCalcdata = function(gd, traces) {
for(i = 0; i < fullData.length; i++) calci(i, true);
for(i = 0; i < fullData.length; i++) calci(i, false);

for(i = 0; i < calcInteractionsFuncs.length; i++) calcInteractionsFuncs[i](gd, calcdata);

Registry.getComponentMethod('fx', 'calc')(gd);
};

Expand Down
103 changes: 53 additions & 50 deletions src/traces/pie/calc.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,20 @@ var tinycolor = require('tinycolor2');
var Color = require('../../components/color');
var helpers = require('./helpers');

module.exports = function calc(gd, trace) {
exports.calc = function calc(gd, trace) {
var vals = trace.values;
var hasVals = isArrayOrTypedArray(vals) && vals.length;
var labels = trace.labels;
var colors = trace.marker.colors || [];
var cd = [];
var fullLayout = gd._fullLayout;
var colorWay = fullLayout.colorway;
var colorMap = fullLayout._piecolormap;
var allThisTraceLabels = {};
var vTotal = 0;
var hiddenLabels = fullLayout.hiddenlabels || [];

var i, v, label, hidden, pt;

if(!fullLayout._piecolorway && colorWay !== Color.defaults) {
fullLayout._piecolorway = generateDefaultColors(colorWay);
}

if(trace.dlabel) {
labels = new Array(vals.length);
for(i = 0; i < vals.length; i++) {
Expand Down Expand Up @@ -79,7 +74,7 @@ module.exports = function calc(gd, trace) {
cd.push({
v: v,
label: label,
color: pullColor(colors[i]),
color: pullColor(colors[i], label),
i: i,
pts: [i],
hidden: hidden
Expand All @@ -99,29 +94,6 @@ module.exports = function calc(gd, trace) {

if(trace.sort) cd.sort(function(a, b) { return b.v - a.v; });

/**
* now go back and fill in colors we're still missing
* this is done after sorting, so we pick defaults
* in the order slices will be displayed
*/

for(i = 0; i < cd.length; i++) {
pt = cd[i];
if(pt.color === false) {
// have we seen this label and assigned a color to it in a previous trace?
if(colorMap[pt.label]) {
pt.color = colorMap[pt.label];
}
else {
colorMap[pt.label] = pt.color = nextDefaultColor(
fullLayout._piedefaultcolorcount,
fullLayout._piecolorway
);
fullLayout._piedefaultcolorcount++;
}
}
}

// include the sum of all values in the first point
if(cd[0]) cd[0].vTotal = vTotal;

Expand Down Expand Up @@ -151,34 +123,65 @@ module.exports = function calc(gd, trace) {
return cd;
};

/**
* pick a default color from the main default set, augmented by
* itself lighter then darker before repeating
/*
* `calc` filled in (and collated) explicit colors.
* Now we need to propagate these explicit colors to other traces,
* and fill in default colors.
* This is done after sorting, so we pick defaults
* in the order slices will be displayed
*/
var pieDefaultColors;
exports.calcInteractions = function(gd, calcdata) {
var fullLayout = gd._fullLayout;
var pieColorWay = fullLayout.piecolorway;
var colorMap = fullLayout._piecolormap;

function nextDefaultColor(index, pieColorWay) {
if(!pieDefaultColors) {
// generate this default set on demand (but then it gets saved in the module)
var mainDefaults = Color.defaults;
pieDefaultColors = generateDefaultColors(mainDefaults);
if(fullLayout.extendpiecolors) {
pieColorWay = generateExtendedColors(pieColorWay);
}
var dfltColorCount = 0;

var i, j, cd, pt;
for(i = 0; i < calcdata.length; i++) {
cd = calcdata[i];
if(cd[0].trace.type !== 'pie') continue;

for(j = 0; j < cd.length; j++) {
pt = cd[j];
if(pt.color === false) {
// have we seen this label and assigned a color to it in a previous trace?
if(colorMap[pt.label]) {
pt.color = colorMap[pt.label];
}
else {
colorMap[pt.label] = pt.color = pieColorWay[dfltColorCount % pieColorWay.length];
dfltColorCount++;
}
}
}
}
};

var pieColors = pieColorWay || pieDefaultColors;
return pieColors[index % pieColors.length];
}
/**
* pick a default color from the main default set, augmented by
* itself lighter then darker before repeating
*/
var extendedColorWays = {};

function generateDefaultColors(colorList) {
function generateExtendedColors(colorList) {
var i;
var colorString = JSON.stringify(colorList);
var pieColors = extendedColorWays[colorString];
if(!pieColors) {
pieColors = colorList.slice();

var pieColors = colorList.slice();

for(i = 0; i < colorList.length; i++) {
pieColors.push(tinycolor(colorList[i]).lighten(20).toHexString());
}
for(i = 0; i < colorList.length; i++) {
pieColors.push(tinycolor(colorList[i]).lighten(20).toHexString());
}

for(i = 0; i < colorList.length; i++) {
pieColors.push(tinycolor(colorList[i]).darken(20).toHexString());
for(i = 0; i < colorList.length; i++) {
pieColors.push(tinycolor(colorList[i]).darken(20).toHexString());
}
extendedColorWays[colorString] = pieColors;
}

return pieColors;
Expand Down
6 changes: 5 additions & 1 deletion src/traces/pie/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ Pie.attributes = require('./attributes');
Pie.supplyDefaults = require('./defaults');
Pie.supplyLayoutDefaults = require('./layout_defaults');
Pie.layoutAttributes = require('./layout_attributes');
Pie.calc = require('./calc');

var calcModule = require('./calc');
Pie.calc = calcModule.calc;
Pie.calcInteractions = calcModule.calcInteractions;

Pie.plot = require('./plot');
Pie.style = require('./style');
Pie.styleOne = require('./style_one');
Expand Down
27 changes: 27 additions & 0 deletions src/traces/pie/layout_attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,32 @@ module.exports = {
hiddenlabels: {
valType: 'data_array',
editType: 'calc'
},
piecolorway: {
valType: 'colorlist',
role: 'style',
editType: 'calc',
description: [
'Sets the default pie slice colors. Defaults to the main',
'`colorway` used for trace colors. If you specify a new',
'list here it can still be extended with lighter and darker',
'colors, see `extendpiecolors`.'
].join(' ')
},
extendpiecolors: {
valType: 'boolean',
dflt: true,
role: 'style',
editType: 'calc',
description: [
'If `true`, the pie slice colors (whether given by `piecolorway` or',
'inherited from `colorway`) will be extended to three times its',
'original length by first repeating every color 20% lighter then',
'each color 20% darker. This is intended to reduce the likelihood',
'of reusing the same color when you have many slices, but you can',
'set `false` to disable.',
'Colors provided in the trace, using `marker.colors`, are never',
'extended.'
].join(' ')
}
};
2 changes: 2 additions & 0 deletions src/traces/pie/layout_defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) {
return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
}
coerce('hiddenlabels');
coerce('piecolorway', layoutOut.colorway);
coerce('extendpiecolors');
};
44 changes: 44 additions & 0 deletions test/jasmine/tests/pie_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,50 @@ describe('Pie traces:', function() {
.catch(failTest)
.then(done);
});

function _checkSliceColors(colors) {
return function() {
d3.select(gd).selectAll('.slice path').each(function(d, i) {
expect(this.style.fill.replace(/(\s|rgb\(|\))/g, '')).toBe(colors[i], i);
});
};
}

it('propagates explicit colors to the same labels in earlier OR later traces', function(done) {
var data1 = [
{type: 'pie', values: [3, 2], marker: {colors: ['red', 'black']}, domain: {x: [0.5, 1]}},
{type: 'pie', values: [2, 5], domain: {x: [0, 0.5]}}
];
var data2 = Lib.extendDeep([], [data1[1], data1[0]]);

Plotly.newPlot(gd, data1)
.then(_checkSliceColors(['255,0,0', '0,0,0', '0,0,0', '255,0,0']))
.then(function() {
return Plotly.newPlot(gd, data2);
})
.then(_checkSliceColors(['0,0,0', '255,0,0', '255,0,0', '0,0,0']))
.catch(failTest)
.then(done);
});

it('can use a separate pie colorway and disable extended colors', function(done) {
Plotly.newPlot(gd, [{type: 'pie', values: [7, 6, 5, 4, 3, 2, 1]}], {colorway: ['#777', '#F00']})
.then(_checkSliceColors(['119,119,119', '255,0,0', '170,170,170', '255,102,102', '68,68,68', '153,0,0', '119,119,119']))
.then(function() {
return Plotly.relayout(gd, {extendpiecolors: false});
})
.then(_checkSliceColors(['119,119,119', '255,0,0', '119,119,119', '255,0,0', '119,119,119', '255,0,0', '119,119,119']))
.then(function() {
return Plotly.relayout(gd, {piecolorway: ['#FF0', '#0F0', '#00F']});
})
.then(_checkSliceColors(['255,255,0', '0,255,0', '0,0,255', '255,255,0', '0,255,0', '0,0,255', '255,255,0']))
.then(function() {
return Plotly.relayout(gd, {extendpiecolors: null});
})
.then(_checkSliceColors(['255,255,0', '0,255,0', '0,0,255', '255,255,102', '102,255,102', '102,102,255', '153,153,0']))
.catch(failTest)
.then(done);
});
});

describe('pie hovering', function() {
Expand Down