Skip to content

Commit ce8946c

Browse files
committed
first cut at aggregated pies
1 parent 679ed9c commit ce8946c

File tree

9 files changed

+170
-76
lines changed

9 files changed

+170
-76
lines changed

src/traces/pie/attributes.js

+11-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,13 @@ module.exports = {
2424
labels: {
2525
valType: 'data_array',
2626
editType: 'calc',
27-
description: 'Sets the sector labels.'
27+
description: [
28+
'Sets the sector labels.',
29+
'If `labels` entries are duplicated, we sum associated `values`',
30+
'or simply count occurrences if `values` is not provided.',
31+
'For other array attributes (including color) we use the first',
32+
'non-empty entry among all occurrences of the label.'
33+
].join(' ')
2834
},
2935
// equivalent of x0 and dx, if label is missing
3036
label0: {
@@ -50,7 +56,10 @@ module.exports = {
5056
values: {
5157
valType: 'data_array',
5258
editType: 'calc',
53-
description: 'Sets the values of the sectors of this pie chart.'
59+
description: [
60+
'Sets the values of the sectors of this pie chart.',
61+
'If omitted, we count occurrences of each label.'
62+
].join(' ')
5463
},
5564

5665
marker: {

src/traces/pie/calc.js

+61-40
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,18 @@ var helpers = require('./helpers');
1616

1717
module.exports = function calc(gd, trace) {
1818
var vals = trace.values,
19+
hasVals = Array.isArray(vals) && vals.length,
1920
labels = trace.labels,
21+
colors = trace.marker.colors,
2022
cd = [],
2123
fullLayout = gd._fullLayout,
2224
colorMap = fullLayout._piecolormap,
2325
allThisTraceLabels = {},
24-
needDefaults = false,
2526
vTotal = 0,
2627
hiddenLabels = fullLayout.hiddenlabels || [],
2728
i,
2829
v,
2930
label,
30-
color,
3131
hidden,
3232
pt;
3333

@@ -38,46 +38,60 @@ module.exports = function calc(gd, trace) {
3838
}
3939
}
4040

41-
for(i = 0; i < vals.length; i++) {
42-
v = vals[i];
43-
if(!isNumeric(v)) continue;
44-
v = +v;
45-
if(v < 0) continue;
41+
function pullColor(color, label) {
42+
if(!color) return false;
43+
44+
color = tinycolor(color);
45+
if(!color.isValid()) return false;
46+
47+
color = Color.addOpacity(color, color.getAlpha());
48+
if(!colorMap[label]) colorMap[label] = color;
49+
50+
return color;
51+
}
52+
53+
var seriesLen = (hasVals ? vals : labels).length;
54+
55+
for(i = 0; i < seriesLen; i++) {
56+
if(hasVals) {
57+
v = vals[i];
58+
if(!isNumeric(v)) continue;
59+
v = +v;
60+
if(v < 0) continue;
61+
}
62+
else v = 1;
4663

4764
label = labels[i];
4865
if(label === undefined || label === '') label = i;
4966
label = String(label);
50-
// only take the first occurrence of any given label.
51-
// TODO: perhaps (optionally?) sum values for a repeated label?
52-
if(allThisTraceLabels[label] === undefined) allThisTraceLabels[label] = true;
53-
else continue;
54-
55-
color = tinycolor(trace.marker.colors[i]);
56-
if(color.isValid()) {
57-
color = Color.addOpacity(color, color.getAlpha());
58-
if(!colorMap[label]) {
59-
colorMap[label] = color;
60-
}
61-
}
62-
// have we seen this label and assigned a color to it in a previous trace?
63-
else if(colorMap[label]) color = colorMap[label];
64-
// color needs a default - mark it false, come back after sorting
65-
else {
66-
color = false;
67-
needDefaults = true;
68-
}
6967

70-
hidden = hiddenLabels.indexOf(label) !== -1;
68+
var thisLabelIndex = allThisTraceLabels[label];
69+
if(thisLabelIndex === undefined) {
70+
allThisTraceLabels[label] = cd.length;
7171

72-
if(!hidden) vTotal += v;
72+
hidden = hiddenLabels.indexOf(label) !== -1;
7373

74-
cd.push({
75-
v: v,
76-
label: label,
77-
color: color,
78-
i: i,
79-
hidden: hidden
80-
});
74+
if(!hidden) vTotal += v;
75+
76+
cd.push({
77+
v: v,
78+
label: label,
79+
color: pullColor(colors[i]),
80+
i: i,
81+
pts: [i],
82+
hidden: hidden
83+
});
84+
}
85+
else {
86+
pt = cd[thisLabelIndex];
87+
pt.v += v;
88+
pt.pts.push(i);
89+
if(!pt.hidden) vTotal += v;
90+
91+
if(pt.color === false && colors[i]) {
92+
pt.color = pullColor(colors[i], label);
93+
}
94+
}
8195
}
8296

8397
if(trace.sort) cd.sort(function(a, b) { return b.v - a.v; });
@@ -88,10 +102,14 @@ module.exports = function calc(gd, trace) {
88102
* in the order slices will be displayed
89103
*/
90104

91-
if(needDefaults) {
92-
for(i = 0; i < cd.length; i++) {
93-
pt = cd[i];
94-
if(pt.color === false) {
105+
for(i = 0; i < cd.length; i++) {
106+
pt = cd[i];
107+
if(pt.color === false) {
108+
// have we seen this label and assigned a color to it in a previous trace?
109+
if(colorMap[pt.label]) {
110+
pt.color = colorMap[pt.label];
111+
}
112+
else {
95113
colorMap[pt.label] = pt.color = nextDefaultColor(fullLayout._piedefaultcolorcount);
96114
fullLayout._piedefaultcolorcount++;
97115
}
@@ -113,7 +131,10 @@ module.exports = function calc(gd, trace) {
113131
for(i = 0; i < cd.length; i++) {
114132
pt = cd[i];
115133
thisText = hasLabel ? [pt.label] : [];
116-
if(hasText && trace.text[pt.i]) thisText.push(trace.text[pt.i]);
134+
if(hasText) {
135+
var texti = helpers.getFirstFilled(trace.text, pt.pts);
136+
if(texti) thisText.push(texti);
137+
}
117138
if(hasValue) thisText.push(helpers.formatPieValue(pt.v, separators));
118139
if(hasPercent) thisText.push(helpers.formatPiePercent(pt.v / vTotal, separators));
119140
pt.text = thisText.join('<br>');

src/traces/pie/defaults.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
1919
var coerceFont = Lib.coerceFont;
2020

2121
var vals = coerce('values');
22-
if(!Array.isArray(vals) || !vals.length) {
23-
traceOut.visible = false;
24-
return;
25-
}
26-
2722
var labels = coerce('labels');
2823
if(!Array.isArray(labels)) {
24+
if(!Array.isArray(vals) || !vals.length) {
25+
// must have at least one of vals or labels
26+
traceOut.visible = false;
27+
return;
28+
}
29+
2930
coerce('label0');
3031
coerce('dlabel');
3132
}

src/traces/pie/helpers.js

+13
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,16 @@ exports.formatPieValue = function formatPieValue(v, separators) {
2525
}
2626
return Lib.numSeparate(vRounded, separators);
2727
};
28+
29+
exports.getFirstFilled = function getFirstFilled(array, indices) {
30+
if(!Array.isArray(array)) return;
31+
for(var i = 0; i < indices.length; i++) {
32+
var v = array[indices[i]];
33+
if(v || v === 0) return v;
34+
}
35+
};
36+
37+
exports.castOption = function castOption(item, indices) {
38+
if(Array.isArray(item)) return exports.getFirstFilled(item, indices);
39+
else if(item) return item;
40+
};

src/traces/pie/plot.js

+31-19
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,19 @@ module.exports = function plot(gd, cdpie) {
9595
// in case fullLayout or fullData has changed without a replot
9696
var fullLayout2 = gd._fullLayout;
9797
var trace2 = gd._fullData[trace.index];
98-
var hoverinfo = Fx.castHoverinfo(trace2, fullLayout2, pt.i);
98+
99+
var hoverinfo = trace2.hoverinfo;
100+
if(Array.isArray(hoverinfo)) {
101+
// super hacky: we need to pull out the *first* hoverinfo from
102+
// pt.pts, then put it back into an array in a dummy trace
103+
// and call castHoverinfo on that.
104+
// TODO: do we want to have Fx.castHoverinfo somehow handle this?
105+
// it already takes an array for index, for 2D, so this seems tricky.
106+
hoverinfo = Fx.castHoverinfo({
107+
hoverinfo: [helpers.castOption(hoverinfo, pt.pts)],
108+
_module: trace._module
109+
}, fullLayout2, 0);
110+
}
99111

100112
if(hoverinfo === 'all') hoverinfo = 'label+text+value+percent+name';
101113

@@ -115,31 +127,27 @@ module.exports = function plot(gd, cdpie) {
115127

116128
if(hoverinfo.indexOf('label') !== -1) thisText.push(pt.label);
117129
if(hoverinfo.indexOf('text') !== -1) {
118-
if(trace2.hovertext) {
119-
thisText.push(
120-
Array.isArray(trace2.hovertext) ?
121-
trace2.hovertext[pt.i] :
122-
trace2.hovertext
123-
);
124-
} else if(trace2.text && trace2.text[pt.i]) {
125-
thisText.push(trace2.text[pt.i]);
126-
}
130+
var texti = helpers.castOption(trace2.hovertext || trace2.text, pt.pts);
131+
if(texti) thisText.push(texti);
127132
}
128133
if(hoverinfo.indexOf('value') !== -1) thisText.push(helpers.formatPieValue(pt.v, separators));
129134
if(hoverinfo.indexOf('percent') !== -1) thisText.push(helpers.formatPiePercent(pt.v / cd0.vTotal, separators));
130135

136+
var hoverLabel = trace.hoverlabel;
137+
var hoverFont = hoverLabel.font;
138+
131139
Fx.loneHover({
132140
x0: hoverCenterX - rInscribed * cd0.r,
133141
x1: hoverCenterX + rInscribed * cd0.r,
134142
y: hoverCenterY,
135143
text: thisText.join('<br>'),
136144
name: hoverinfo.indexOf('name') !== -1 ? trace2.name : undefined,
137145
idealAlign: pt.pxmid[0] < 0 ? 'left' : 'right',
138-
color: Fx.castHoverOption(trace, pt.i, 'bgcolor') || pt.color,
139-
borderColor: Fx.castHoverOption(trace, pt.i, 'bordercolor'),
140-
fontFamily: Fx.castHoverOption(trace, pt.i, 'font.family'),
141-
fontSize: Fx.castHoverOption(trace, pt.i, 'font.size'),
142-
fontColor: Fx.castHoverOption(trace, pt.i, 'font.color')
146+
color: helpers.castOption(hoverLabel.bgcolor, pt.pts) || pt.color,
147+
borderColor: helpers.castOption(hoverLabel.bordercolor, pt.pts),
148+
fontFamily: helpers.castOption(hoverFont.family, pt.pts),
149+
fontSize: helpers.castOption(hoverFont.size, pt.pts),
150+
fontColor: helpers.castOption(hoverFont.color, pt.pts)
143151
}, {
144152
container: fullLayout2._hoverlayer.node(),
145153
outerContainer: fullLayout2._paper.node(),
@@ -182,7 +190,7 @@ module.exports = function plot(gd, cdpie) {
182190
.on('click', handleClick);
183191

184192
if(trace.pull) {
185-
var pull = +(Array.isArray(trace.pull) ? trace.pull[pt.i] : trace.pull) || 0;
193+
var pull = +helpers.castOption(trace.pull, pt.pts) || 0;
186194
if(pull > 0) {
187195
cx += pull * pt.pxmid[0];
188196
cy += pull * pt.pxmid[1];
@@ -233,8 +241,7 @@ module.exports = function plot(gd, cdpie) {
233241
}
234242

235243
// add text
236-
var textPosition = Array.isArray(trace.textposition) ?
237-
trace.textposition[pt.i] : trace.textposition,
244+
var textPosition = helpers.castOption(trace.textposition, pt.pts),
238245
sliceTextGroup = sliceTop.selectAll('g.slicetext')
239246
.data(pt.text && (textPosition !== 'none') ? [0] : []);
240247

@@ -492,7 +499,12 @@ function scootLabels(quadrants, trace) {
492499
otherPt = wholeSide[i];
493500

494501
// overlap can only happen if the other point is pulled more than this one
495-
if(otherPt === thisPt || ((trace.pull[thisPt.i] || 0) >= trace.pull[otherPt.i] || 0)) continue;
502+
if(otherPt === thisPt || (
503+
(helpers.castOption(trace.pull, thisPt.pts) || 0) >=
504+
(helpers.castOption(trace.pull, otherPt.pts) || 0))
505+
) {
506+
continue;
507+
}
496508

497509
if((thisPt.pxmid[1] - otherPt.pxmid[1]) * yDiffSign > 0) {
498510
// closer to the equator - by construction all of these happen first

src/traces/pie/style_one.js

+6-7
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,14 @@
99
'use strict';
1010

1111
var Color = require('../../components/color');
12+
var castOption = require('./helpers').castOption;
1213

1314
module.exports = function styleOne(s, pt, trace) {
14-
var lineColor = trace.marker.line.color;
15-
if(Array.isArray(lineColor)) lineColor = lineColor[pt.i] || Color.defaultLine;
16-
17-
var lineWidth = trace.marker.line.width || 0;
18-
if(Array.isArray(lineWidth)) lineWidth = lineWidth[pt.i] || 0;
15+
var line = trace.marker.line;
16+
var lineColor = castOption(line.color, pt.pts) || Color.defaultLine;
17+
var lineWidth = castOption(line.width, pt.pts) || 0;
1918

2019
s.style({'stroke-width': lineWidth})
21-
.call(Color.fill, pt.color)
22-
.call(Color.stroke, lineColor);
20+
.call(Color.fill, pt.color)
21+
.call(Color.stroke, lineColor);
2322
};
21.8 KB
Loading

test/image/mocks/pie_aggregated.json

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"data": [
3+
{
4+
"labels": [
5+
"Alice",
6+
"Bob",
7+
"Charlie",
8+
"Charlie",
9+
"Charlie",
10+
"Alice"
11+
],
12+
"type": "pie",
13+
"domain": {"x": [0, 0.4]}
14+
},
15+
{
16+
"labels": [
17+
"Alice",
18+
"Alice",
19+
"Allison",
20+
"Alys",
21+
"Elise",
22+
"Allison",
23+
"Alys",
24+
"Alys"
25+
],
26+
"values": [
27+
10, 20, 30, 40, 50, 60, 70, 80
28+
],
29+
"marker": {"colors": ["", "", "#ccc"]},
30+
"type": "pie",
31+
"domain": {"x": [0.5, 1]},
32+
"textinfo": "label+value+percent"
33+
}
34+
],
35+
"layout": {
36+
"height": 300,
37+
"width": 500
38+
}
39+
}

test/jasmine/tests/pie_test.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ describe('pie hovering', function() {
178178
expect(unhoverData.points.length).toEqual(1);
179179

180180
var fields = [
181-
'v', 'label', 'color', 'i', 'hidden',
181+
'v', 'label', 'color', 'i', 'pts', 'hidden',
182182
'text', 'px1', 'pxmid', 'midangle',
183183
'px0', 'largeArc',
184184
'pointNumber', 'curveNumber',
@@ -405,12 +405,12 @@ describe('Test event property of interactions on a pie plot:', function() {
405405

406406
var pt = futureData.points[0];
407407
expect(Object.keys(pt)).toEqual(jasmine.arrayContaining([
408-
'v', 'label', 'color', 'i', 'hidden', 'vTotal', 'text', 't',
408+
'v', 'label', 'color', 'i', 'pts', 'hidden', 'vTotal', 'text', 't',
409409
'trace', 'r', 'cx', 'cy', 'px1', 'pxmid', 'midangle', 'px0',
410410
'largeArc', 'cxFinal', 'cyFinal',
411411
'pointNumber', 'curveNumber'
412412
]));
413-
expect(Object.keys(pt).length).toBe(21);
413+
expect(Object.keys(pt).length).toBe(22);
414414

415415
expect(typeof pt.color).toEqual(typeof '#1f77b4', 'points[0].color');
416416
expect(pt.cx).toEqual(200, 'points[0].cx');

0 commit comments

Comments
 (0)