Skip to content

Commit d50334e

Browse files
committed
pie color fix & enhancements
- fix inheritance of explicit colors by later traces - allow inheritance (of explicit colors) by earlier traces too - add `piecolorway` and `extendpiecolors` for more control over pie colors
1 parent e8ae349 commit d50334e

File tree

6 files changed

+141
-53
lines changed

6 files changed

+141
-53
lines changed

src/plots/plots.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2435,8 +2435,6 @@ plots.doCalcdata = function(gd, traces) {
24352435

24362436
// for sharing colors across pies (and for legend)
24372437
fullLayout._piecolormap = {};
2438-
fullLayout._piecolorway = null;
2439-
fullLayout._piedefaultcolorcount = 0;
24402438

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

2506+
var calcInteractionsFuncs = [];
2507+
25082508
function calci(i, isContainer) {
25092509
trace = fullData[i];
25102510
_module = trace._module;
@@ -2530,6 +2530,12 @@ plots.doCalcdata = function(gd, traces) {
25302530

25312531
if(_module && _module.calc) {
25322532
cd = _module.calc(gd, trace);
2533+
2534+
// Some modules need to update traces' calcdata after
2535+
// *all* traces have been through calc - so later traces can
2536+
// impact earlier traces.
2537+
var calcInteractions = _module.calcInteractions;
2538+
if(calcInteractions) Lib.pushUnique(calcInteractionsFuncs, calcInteractions);
25332539
}
25342540
}
25352541

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

2564+
for(i = 0; i < calcInteractionsFuncs.length; i++) calcInteractionsFuncs[i](gd, calcdata);
2565+
25582566
Registry.getComponentMethod('fx', 'calc')(gd);
25592567
};
25602568

src/traces/pie/calc.js

Lines changed: 53 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,20 @@ var tinycolor = require('tinycolor2');
1515
var Color = require('../../components/color');
1616
var helpers = require('./helpers');
1717

18-
module.exports = function calc(gd, trace) {
18+
exports.calc = function calc(gd, trace) {
1919
var vals = trace.values;
2020
var hasVals = isArrayOrTypedArray(vals) && vals.length;
2121
var labels = trace.labels;
2222
var colors = trace.marker.colors || [];
2323
var cd = [];
2424
var fullLayout = gd._fullLayout;
25-
var colorWay = fullLayout.colorway;
2625
var colorMap = fullLayout._piecolormap;
2726
var allThisTraceLabels = {};
2827
var vTotal = 0;
2928
var hiddenLabels = fullLayout.hiddenlabels || [];
3029

3130
var i, v, label, hidden, pt;
3231

33-
if(!fullLayout._piecolorway && colorWay !== Color.defaults) {
34-
fullLayout._piecolorway = generateDefaultColors(colorWay);
35-
}
36-
3732
if(trace.dlabel) {
3833
labels = new Array(vals.length);
3934
for(i = 0; i < vals.length; i++) {
@@ -79,7 +74,7 @@ module.exports = function calc(gd, trace) {
7974
cd.push({
8075
v: v,
8176
label: label,
82-
color: pullColor(colors[i]),
77+
color: pullColor(colors[i], label),
8378
i: i,
8479
pts: [i],
8580
hidden: hidden
@@ -99,29 +94,6 @@ module.exports = function calc(gd, trace) {
9994

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

102-
/**
103-
* now go back and fill in colors we're still missing
104-
* this is done after sorting, so we pick defaults
105-
* in the order slices will be displayed
106-
*/
107-
108-
for(i = 0; i < cd.length; i++) {
109-
pt = cd[i];
110-
if(pt.color === false) {
111-
// have we seen this label and assigned a color to it in a previous trace?
112-
if(colorMap[pt.label]) {
113-
pt.color = colorMap[pt.label];
114-
}
115-
else {
116-
colorMap[pt.label] = pt.color = nextDefaultColor(
117-
fullLayout._piedefaultcolorcount,
118-
fullLayout._piecolorway
119-
);
120-
fullLayout._piedefaultcolorcount++;
121-
}
122-
}
123-
}
124-
12597
// include the sum of all values in the first point
12698
if(cd[0]) cd[0].vTotal = vTotal;
12799

@@ -151,34 +123,65 @@ module.exports = function calc(gd, trace) {
151123
return cd;
152124
};
153125

154-
/**
155-
* pick a default color from the main default set, augmented by
156-
* itself lighter then darker before repeating
126+
/*
127+
* `calc` filled in (and collated) explicit colors.
128+
* Now we need to propagate these explicit colors to other traces,
129+
* and fill in default colors.
130+
* This is done after sorting, so we pick defaults
131+
* in the order slices will be displayed
157132
*/
158-
var pieDefaultColors;
133+
exports.calcInteractions = function(gd, calcdata) {
134+
var fullLayout = gd._fullLayout;
135+
var pieColorWay = fullLayout.piecolorway;
136+
var colorMap = fullLayout._piecolormap;
159137

160-
function nextDefaultColor(index, pieColorWay) {
161-
if(!pieDefaultColors) {
162-
// generate this default set on demand (but then it gets saved in the module)
163-
var mainDefaults = Color.defaults;
164-
pieDefaultColors = generateDefaultColors(mainDefaults);
138+
if(fullLayout.extendpiecolors) {
139+
pieColorWay = generateExtendedColors(pieColorWay);
165140
}
141+
var dfltColorCount = 0;
142+
143+
var i, j, cd, pt;
144+
for(i = 0; i < calcdata.length; i++) {
145+
cd = calcdata[i];
146+
if(cd[0].trace.type !== 'pie') continue;
147+
148+
for(j = 0; j < cd.length; j++) {
149+
pt = cd[j];
150+
if(pt.color === false) {
151+
// have we seen this label and assigned a color to it in a previous trace?
152+
if(colorMap[pt.label]) {
153+
pt.color = colorMap[pt.label];
154+
}
155+
else {
156+
colorMap[pt.label] = pt.color = pieColorWay[dfltColorCount % pieColorWay.length];
157+
dfltColorCount++;
158+
}
159+
}
160+
}
161+
}
162+
};
166163

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

171-
function generateDefaultColors(colorList) {
170+
function generateExtendedColors(colorList) {
172171
var i;
172+
var colorString = JSON.stringify(colorList);
173+
var pieColors = extendedColorWays[colorString];
174+
if(!pieColors) {
175+
pieColors = colorList.slice();
173176

174-
var pieColors = colorList.slice();
175-
176-
for(i = 0; i < colorList.length; i++) {
177-
pieColors.push(tinycolor(colorList[i]).lighten(20).toHexString());
178-
}
177+
for(i = 0; i < colorList.length; i++) {
178+
pieColors.push(tinycolor(colorList[i]).lighten(20).toHexString());
179+
}
179180

180-
for(i = 0; i < colorList.length; i++) {
181-
pieColors.push(tinycolor(colorList[i]).darken(20).toHexString());
181+
for(i = 0; i < colorList.length; i++) {
182+
pieColors.push(tinycolor(colorList[i]).darken(20).toHexString());
183+
}
184+
extendedColorWays[colorString] = pieColors;
182185
}
183186

184187
return pieColors;

src/traces/pie/index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ Pie.attributes = require('./attributes');
1414
Pie.supplyDefaults = require('./defaults');
1515
Pie.supplyLayoutDefaults = require('./layout_defaults');
1616
Pie.layoutAttributes = require('./layout_attributes');
17-
Pie.calc = require('./calc');
17+
18+
var calcModule = require('./calc');
19+
Pie.calc = calcModule.calc;
20+
Pie.calcInteractions = calcModule.calcInteractions;
21+
1822
Pie.plot = require('./plot');
1923
Pie.style = require('./style');
2024
Pie.styleOne = require('./style_one');

src/traces/pie/layout_attributes.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,32 @@ module.exports = {
1717
hiddenlabels: {
1818
valType: 'data_array',
1919
editType: 'calc'
20+
},
21+
piecolorway: {
22+
valType: 'colorlist',
23+
role: 'style',
24+
editType: 'calc',
25+
description: [
26+
'Sets the default pie slice colors. Defaults to the main',
27+
'`colorway` used for trace colors. If you specify a new',
28+
'list here it can still be extended with lighter and darker',
29+
'colors, see `extendpiecolors`.'
30+
].join(' ')
31+
},
32+
extendpiecolors: {
33+
valType: 'boolean',
34+
dflt: true,
35+
role: 'style',
36+
editType: 'calc',
37+
description: [
38+
'If `true`, the pie slice colors (whether given by `piecolorway` or',
39+
'inherited from `colorway`) will be extended to three times its',
40+
'original length by first repeating every color 20% lighter then',
41+
'each color 20% darker. This is intended to reduce the likelihood',
42+
'of reusing the same color when you have many slices, but you can',
43+
'set `false` to disable.',
44+
'Colors provided in the trace, using `marker.colors`, are never',
45+
'extended.'
46+
].join(' ')
2047
}
2148
};

src/traces/pie/layout_defaults.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,6 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut) {
1717
return Lib.coerce(layoutIn, layoutOut, layoutAttributes, attr, dflt);
1818
}
1919
coerce('hiddenlabels');
20+
coerce('piecolorway', layoutOut.colorway);
21+
coerce('extendpiecolors');
2022
};

test/jasmine/tests/pie_test.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,50 @@ describe('Pie traces:', function() {
138138
.catch(failTest)
139139
.then(done);
140140
});
141+
142+
function _checkSliceColors(colors) {
143+
return function() {
144+
d3.select(gd).selectAll('.slice path').each(function(d, i) {
145+
expect(this.style.fill.replace(/(\s|rgb\(|\))/g, '')).toBe(colors[i], i);
146+
});
147+
};
148+
}
149+
150+
it('propagates explicit colors to the same labels in earlier OR later traces', function(done) {
151+
var data1 = [
152+
{type: 'pie', values: [3, 2], marker: {colors: ['red', 'black']}, domain: {x: [0.5, 1]}},
153+
{type: 'pie', values: [2, 5], domain: {x: [0, 0.5]}}
154+
];
155+
var data2 = Lib.extendDeep([], [data1[1], data1[0]]);
156+
157+
Plotly.newPlot(gd, data1)
158+
.then(_checkSliceColors(['255,0,0', '0,0,0', '0,0,0', '255,0,0']))
159+
.then(function() {
160+
return Plotly.newPlot(gd, data2);
161+
})
162+
.then(_checkSliceColors(['0,0,0', '255,0,0', '255,0,0', '0,0,0']))
163+
.catch(failTest)
164+
.then(done);
165+
});
166+
167+
it('can use a separate pie colorway and disable extended colors', function(done) {
168+
Plotly.newPlot(gd, [{type: 'pie', values: [7, 6, 5, 4, 3, 2, 1]}], {colorway: ['#777', '#F00']})
169+
.then(_checkSliceColors(['119,119,119', '255,0,0', '170,170,170', '255,102,102', '68,68,68', '153,0,0', '119,119,119']))
170+
.then(function() {
171+
return Plotly.relayout(gd, {extendpiecolors: false});
172+
})
173+
.then(_checkSliceColors(['119,119,119', '255,0,0', '119,119,119', '255,0,0', '119,119,119', '255,0,0', '119,119,119']))
174+
.then(function() {
175+
return Plotly.relayout(gd, {piecolorway: ['#FF0', '#0F0', '#00F']});
176+
})
177+
.then(_checkSliceColors(['255,255,0', '0,255,0', '0,0,255', '255,255,0', '0,255,0', '0,0,255', '255,255,0']))
178+
.then(function() {
179+
return Plotly.relayout(gd, {extendpiecolors: null});
180+
})
181+
.then(_checkSliceColors(['255,255,0', '0,255,0', '0,0,255', '255,255,102', '102,255,102', '102,102,255', '153,153,0']))
182+
.catch(failTest)
183+
.then(done);
184+
});
141185
});
142186

143187
describe('pie hovering', function() {

0 commit comments

Comments
 (0)