Skip to content

Commit 41d870b

Browse files
authored
Merge pull request #2870 from plotly/pie-colors
Pie colors fix & enhancements
2 parents e8ae349 + 0bf82c8 commit 41d870b

File tree

9 files changed

+151
-78
lines changed

9 files changed

+151
-78
lines changed

src/plot_api/plot_api.js

-6
Original file line numberDiff line numberDiff line change
@@ -292,11 +292,6 @@ exports.plot = function(gd, data, layout, config) {
292292
return;
293293
}
294294

295-
Plots.doCrossTraceCalc(gd);
296-
297-
// calc and autorange for errorbars
298-
Registry.getComponentMethod('errorbars', 'calc')(gd);
299-
300295
// TODO: autosize extra for text markers and images
301296
// see https://github.com/plotly/plotly.js/issues/1111
302297
return Lib.syncOrAsync([
@@ -331,7 +326,6 @@ exports.plot = function(gd, data, layout, config) {
331326
];
332327

333328
if(hasCartesian) seq.push(positionAndAutorange);
334-
else seq.push(Plots.doCrossTraceCalc);
335329

336330
seq.push(subroutines.layoutStyles);
337331
if(hasCartesian) seq.push(drawAxes);

src/plots/plots.js

+18-12
Original file line numberDiff line numberDiff line change
@@ -2236,8 +2236,6 @@ plots.transition = function(gd, data, layout, traces, frameOpts, transitionOpts)
22362236

22372237
plots.supplyDefaults(gd);
22382238
plots.doCalcdata(gd);
2239-
plots.doCrossTraceCalc(gd);
2240-
Registry.getComponentMethod('errorbars', 'calc')(gd);
22412239

22422240
return Promise.resolve();
22432241
}
@@ -2435,8 +2433,6 @@ plots.doCalcdata = function(gd, traces) {
24352433

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

24412437
// If traces were specified and this trace was not included,
24422438
// then transfer it over from the old calcdata:
@@ -2555,7 +2551,10 @@ plots.doCalcdata = function(gd, traces) {
25552551
for(i = 0; i < fullData.length; i++) calci(i, true);
25562552
for(i = 0; i < fullData.length; i++) calci(i, false);
25572553

2554+
doCrossTraceCalc(gd);
2555+
25582556
Registry.getComponentMethod('fx', 'calc')(gd);
2557+
Registry.getComponentMethod('errorbars', 'calc')(gd);
25592558
};
25602559

25612560
function clearAxesCalc(axList) {
@@ -2564,7 +2563,7 @@ function clearAxesCalc(axList) {
25642563
}
25652564
}
25662565

2567-
plots.doCrossTraceCalc = function(gd) {
2566+
function doCrossTraceCalc(gd) {
25682567
var fullLayout = gd._fullLayout;
25692568
var modules = fullLayout._visibleModules;
25702569
var hash = {};
@@ -2591,18 +2590,25 @@ plots.doCrossTraceCalc = function(gd) {
25912590
var methods = hash[k];
25922591
var subplots = fullLayout._subplots[k];
25932592

2594-
for(i = 0; i < subplots.length; i++) {
2595-
var sp = subplots[i];
2596-
var spInfo = k === 'cartesian' ?
2597-
fullLayout._plots[sp] :
2598-
fullLayout[sp];
2593+
if(Array.isArray(subplots)) {
2594+
for(i = 0; i < subplots.length; i++) {
2595+
var sp = subplots[i];
2596+
var spInfo = k === 'cartesian' ?
2597+
fullLayout._plots[sp] :
2598+
fullLayout[sp];
25992599

2600+
for(j = 0; j < methods.length; j++) {
2601+
methods[j](gd, spInfo);
2602+
}
2603+
}
2604+
}
2605+
else {
26002606
for(j = 0; j < methods.length; j++) {
2601-
methods[j](gd, spInfo);
2607+
methods[j](gd);
26022608
}
26032609
}
26042610
}
2605-
};
2611+
}
26062612

26072613
plots.rehover = function(gd) {
26082614
if(gd._fullLayout._rehover) {

src/traces/pie/calc.js

+54-50
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,66 @@ 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.crossTraceCalc = function(gd) {
134+
var fullLayout = gd._fullLayout;
135+
var calcdata = gd.calcdata;
136+
var pieColorWay = fullLayout.piecolorway;
137+
var colorMap = fullLayout._piecolormap;
159138

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);
139+
if(fullLayout.extendpiecolors) {
140+
pieColorWay = generateExtendedColors(pieColorWay);
165141
}
142+
var dfltColorCount = 0;
143+
144+
var i, j, cd, pt;
145+
for(i = 0; i < calcdata.length; i++) {
146+
cd = calcdata[i];
147+
if(cd[0].trace.type !== 'pie') continue;
148+
149+
for(j = 0; j < cd.length; j++) {
150+
pt = cd[j];
151+
if(pt.color === false) {
152+
// have we seen this label and assigned a color to it in a previous trace?
153+
if(colorMap[pt.label]) {
154+
pt.color = colorMap[pt.label];
155+
}
156+
else {
157+
colorMap[pt.label] = pt.color = pieColorWay[dfltColorCount % pieColorWay.length];
158+
dfltColorCount++;
159+
}
160+
}
161+
}
162+
}
163+
};
166164

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

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

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

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

184188
return pieColors;

src/traces/pie/index.js

+5-1
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.crossTraceCalc = calcModule.crossTraceCalc;
21+
1822
Pie.plot = require('./plot');
1923
Pie.style = require('./style');
2024
Pie.styleOne = require('./style_one');

src/traces/pie/layout_attributes.js

+27
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

+2
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/annotations_test.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,7 @@ describe('annotations autorange', function() {
775775
});
776776
})
777777
.then(function() {
778-
_assert('auto rng / big tx', [-0.22, 3.57], [0.84, 3.365]);
778+
_assert('auto rng / big tx', [-0.22, 3.59], [0.84, 3.365]);
779779
return Plotly.relayout(gd, 'annotations[0].text', 'a');
780780
})
781781
.then(function() {

test/jasmine/tests/bar_test.js

-8
Original file line numberDiff line numberDiff line change
@@ -1801,14 +1801,6 @@ function mockBarPlot(dataWithoutTraceType, layout) {
18011801
supplyAllDefaults(gd);
18021802
Plots.doCalcdata(gd);
18031803

1804-
var plotinfo = {
1805-
xaxis: gd._fullLayout.xaxis,
1806-
yaxis: gd._fullLayout.yaxis
1807-
};
1808-
1809-
// call Bar.crossTraceCalc
1810-
Bar.crossTraceCalc(gd, plotinfo);
1811-
18121804
return gd;
18131805
}
18141806

test/jasmine/tests/pie_test.js

+44
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)