Skip to content

Commit 77d0858

Browse files
authored
Merge pull request #2117 from plotly/pie-agg
Pie aggregation and event cleanup
2 parents b155301 + ee1f8c4 commit 77d0858

File tree

14 files changed

+554
-455
lines changed

14 files changed

+554
-455
lines changed

src/components/fx/helpers.js

+58-11
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ exports.quadrature = function quadrature(dx, dy) {
8989
*
9090
* @param {object} pointData : point data object (gets mutated here)
9191
* @param {object} trace : full trace object
92-
* @param {number} pointNumber : point number
92+
* @param {number|Array(number)} pointNumber : point number. May be a length-2 array
93+
* [row, col] to dig into 2D arrays
9394
*/
9495
exports.appendArrayPointValue = function(pointData, trace, pointNumber) {
9596
var arrayAttrs = trace._arrayAttrs;
@@ -100,22 +101,68 @@ exports.appendArrayPointValue = function(pointData, trace, pointNumber) {
100101

101102
for(var i = 0; i < arrayAttrs.length; i++) {
102103
var astr = arrayAttrs[i];
103-
var key;
104+
var key = getPointKey(astr);
104105

105-
if(astr === 'ids') key = 'id';
106-
else if(astr === 'locations') key = 'location';
107-
else key = astr;
106+
if(pointData[key] === undefined) {
107+
var val = Lib.nestedProperty(trace, astr).get();
108+
var pointVal = getPointData(val, pointNumber);
109+
110+
if(pointVal !== undefined) pointData[key] = pointVal;
111+
}
112+
}
113+
};
114+
115+
/**
116+
* Appends values inside array attributes corresponding to given point number array
117+
* For use when pointData references a plot entity that arose (or potentially arose)
118+
* from multiple points in the input data
119+
*
120+
* @param {object} pointData : point data object (gets mutated here)
121+
* @param {object} trace : full trace object
122+
* @param {Array(number)|Array(Array(number))} pointNumbers : Array of point numbers.
123+
* Each entry in the array may itself be a length-2 array [row, col] to dig into 2D arrays
124+
*/
125+
exports.appendArrayMultiPointValues = function(pointData, trace, pointNumbers) {
126+
var arrayAttrs = trace._arrayAttrs;
127+
128+
if(!arrayAttrs) {
129+
return;
130+
}
131+
132+
for(var i = 0; i < arrayAttrs.length; i++) {
133+
var astr = arrayAttrs[i];
134+
var key = getPointKey(astr);
108135

109136
if(pointData[key] === undefined) {
110137
var val = Lib.nestedProperty(trace, astr).get();
138+
var keyVal = new Array(pointNumbers.length);
111139

112-
if(Array.isArray(pointNumber)) {
113-
if(Array.isArray(val) && Array.isArray(val[pointNumber[0]])) {
114-
pointData[key] = val[pointNumber[0]][pointNumber[1]];
115-
}
116-
} else {
117-
pointData[key] = val[pointNumber];
140+
for(var j = 0; j < pointNumbers.length; j++) {
141+
keyVal[j] = getPointData(val, pointNumbers[j]);
118142
}
143+
pointData[key] = keyVal;
119144
}
120145
}
121146
};
147+
148+
var pointKeyMap = {
149+
ids: 'id',
150+
locations: 'location',
151+
labels: 'label',
152+
values: 'value',
153+
'marker.colors': 'color'
154+
};
155+
156+
function getPointKey(astr) {
157+
return pointKeyMap[astr] || astr;
158+
}
159+
160+
function getPointData(val, pointNumber) {
161+
if(Array.isArray(pointNumber)) {
162+
if(Array.isArray(val) && Array.isArray(val[pointNumber[0]])) {
163+
return val[pointNumber[0]][pointNumber[1]];
164+
}
165+
} else {
166+
return val[pointNumber];
167+
}
168+
}

src/components/fx/hover.js

-8
Original file line numberDiff line numberDiff line change
@@ -155,14 +155,6 @@ exports.loneHover = function loneHover(hoverItem, opts) {
155155

156156
// The actual implementation is here:
157157
function _hover(gd, evt, subplot, noHoverEvent) {
158-
if((subplot === 'pie' || subplot === 'sankey') && !noHoverEvent) {
159-
gd.emit('plotly_hover', {
160-
event: evt.originalEvent,
161-
points: [evt]
162-
});
163-
return;
164-
}
165-
166158
if(!subplot) subplot = 'xy';
167159

168160
// if the user passed in an array of subplots,

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

+78-59
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,18 @@ var Color = require('../../components/color');
1515
var helpers = require('./helpers');
1616

1717
module.exports = function calc(gd, trace) {
18-
var vals = trace.values,
19-
labels = trace.labels,
20-
cd = [],
21-
fullLayout = gd._fullLayout,
22-
colorMap = fullLayout._piecolormap,
23-
allThisTraceLabels = {},
24-
needDefaults = false,
25-
vTotal = 0,
26-
hiddenLabels = fullLayout.hiddenlabels || [],
27-
i,
28-
v,
29-
label,
30-
color,
31-
hidden,
32-
pt;
18+
var vals = trace.values;
19+
var hasVals = Array.isArray(vals) && vals.length;
20+
var labels = trace.labels;
21+
var colors = trace.marker.colors;
22+
var cd = [];
23+
var fullLayout = gd._fullLayout;
24+
var colorMap = fullLayout._piecolormap;
25+
var allThisTraceLabels = {};
26+
var vTotal = 0;
27+
var hiddenLabels = fullLayout.hiddenlabels || [];
28+
29+
var i, v, label, hidden, pt;
3330

3431
if(trace.dlabel) {
3532
labels = new Array(vals.length);
@@ -38,46 +35,60 @@ module.exports = function calc(gd, trace) {
3835
}
3936
}
4037

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

4761
label = labels[i];
4862
if(label === undefined || label === '') label = i;
4963
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-
}
6964

70-
hidden = hiddenLabels.indexOf(label) !== -1;
65+
var thisLabelIndex = allThisTraceLabels[label];
66+
if(thisLabelIndex === undefined) {
67+
allThisTraceLabels[label] = cd.length;
7168

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

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

8394
if(trace.sort) cd.sort(function(a, b) { return b.v - a.v; });
@@ -88,10 +99,14 @@ module.exports = function calc(gd, trace) {
8899
* in the order slices will be displayed
89100
*/
90101

91-
if(needDefaults) {
92-
for(i = 0; i < cd.length; i++) {
93-
pt = cd[i];
94-
if(pt.color === false) {
102+
for(i = 0; i < cd.length; i++) {
103+
pt = cd[i];
104+
if(pt.color === false) {
105+
// have we seen this label and assigned a color to it in a previous trace?
106+
if(colorMap[pt.label]) {
107+
pt.color = colorMap[pt.label];
108+
}
109+
else {
95110
colorMap[pt.label] = pt.color = nextDefaultColor(fullLayout._piedefaultcolorcount);
96111
fullLayout._piedefaultcolorcount++;
97112
}
@@ -103,17 +118,21 @@ module.exports = function calc(gd, trace) {
103118

104119
// now insert text
105120
if(trace.textinfo && trace.textinfo !== 'none') {
106-
var hasLabel = trace.textinfo.indexOf('label') !== -1,
107-
hasText = trace.textinfo.indexOf('text') !== -1,
108-
hasValue = trace.textinfo.indexOf('value') !== -1,
109-
hasPercent = trace.textinfo.indexOf('percent') !== -1,
110-
separators = fullLayout.separators,
111-
thisText;
121+
var hasLabel = trace.textinfo.indexOf('label') !== -1;
122+
var hasText = trace.textinfo.indexOf('text') !== -1;
123+
var hasValue = trace.textinfo.indexOf('value') !== -1;
124+
var hasPercent = trace.textinfo.indexOf('percent') !== -1;
125+
var separators = fullLayout.separators;
126+
127+
var thisText;
112128

113129
for(i = 0; i < cd.length; i++) {
114130
pt = cd[i];
115131
thisText = hasLabel ? [pt.label] : [];
116-
if(hasText && trace.text[pt.i]) thisText.push(trace.text[pt.i]);
132+
if(hasText) {
133+
var texti = helpers.getFirstFilled(trace.text, pt.pts);
134+
if(texti) thisText.push(texti);
135+
}
117136
if(hasValue) thisText.push(helpers.formatPieValue(pt.v, separators));
118137
if(hasPercent) thisText.push(helpers.formatPiePercent(pt.v / vTotal, separators));
119138
pt.text = thisText.join('<br>');

src/traces/pie/defaults.js

+8-19
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
}
@@ -34,14 +35,10 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
3435
if(lineWidth) coerce('marker.line.color');
3536

3637
var colors = coerce('marker.colors');
37-
if(!Array.isArray(colors)) traceOut.marker.colors = []; // later this will get padded with default colors
38+
if(!Array.isArray(colors)) traceOut.marker.colors = [];
3839

3940
coerce('scalegroup');
40-
// TODO: tilt, depth, and hole all need to be coerced to the same values within a scaleegroup
41-
// (ideally actually, depth would get set the same *after* scaling, ie the same absolute depth)
42-
// and if colors aren't specified we should match these up - potentially even if separate pies
43-
// are NOT in the same sharegroup
44-
41+
// TODO: hole needs to be coerced to the same value within a scaleegroup
4542

4643
var textData = coerce('text');
4744
var textInfo = coerce('textinfo', Array.isArray(textData) ? 'text+percent' : 'percent');
@@ -63,14 +60,6 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
6360
coerce('domain.x');
6461
coerce('domain.y');
6562

66-
// 3D attributes commented out until I finish them in a later PR
67-
// var tilt = coerce('tilt');
68-
// if(tilt) {
69-
// coerce('tiltaxis');
70-
// coerce('depth');
71-
// coerce('shading');
72-
// }
73-
7463
coerce('hole');
7564

7665
coerce('sort');

0 commit comments

Comments
 (0)