Skip to content

Commit 0dea0e9

Browse files
authored
Merge pull request #3222 from plotly/joyplots-etpinard
Joyplots etpinard
2 parents fe3d7ed + c70f7d5 commit 0dea0e9

11 files changed

+114
-79
lines changed

src/traces/box/attributes.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -179,9 +179,9 @@ module.exports = {
179179
dflt: 0,
180180
editType: 'calc',
181181
description: [
182-
'Sets the width of the box.',
182+
'Sets the width of the box in data coordinate',
183183
'If *0* (default value) the width is automatically selected based on the positions',
184-
'of other box traces in the same subplot.',
184+
'of other box traces in the same subplot.'
185185
].join(' ')
186186
},
187187

src/traces/box/layout_attributes.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ module.exports = {
2222
'If *group*, the boxes are plotted next to one another',
2323
'centered around the shared location.',
2424
'If *overlay*, the boxes are plotted over one another,',
25-
'you might need to set *opacity* to see them multiple boxes.'
25+
'you might need to set *opacity* to see them multiple boxes.',
26+
'Has no effect on traces that have *width* set.'
2627
].join(' ')
2728
},
2829
boxgap: {
@@ -34,7 +35,8 @@ module.exports = {
3435
editType: 'calc',
3536
description: [
3637
'Sets the gap (in plot fraction) between boxes of',
37-
'adjacent location coordinates.'
38+
'adjacent location coordinates.',
39+
'Has no effect on traces that have *width* set.'
3840
].join(' ')
3941
},
4042
boxgroupgap: {
@@ -46,7 +48,8 @@ module.exports = {
4648
editType: 'calc',
4749
description: [
4850
'Sets the gap (in plot fraction) between boxes of',
49-
'the same location coordinate.'
51+
'the same location coordinate.',
52+
'Has no effect on traces that have *width* set.'
5053
].join(' ')
5154
}
5255
};

src/traces/box/plot.js

+17-4
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,32 @@ function plot(gd, plotinfo, cdbox, boxLayer) {
2222
var xa = plotinfo.xaxis;
2323
var ya = plotinfo.yaxis;
2424
var numBoxes = fullLayout._numBoxes;
25-
var groupFraction = (1 - fullLayout.boxgap);
2625
var group = (fullLayout.boxmode === 'group' && numBoxes > 1);
26+
var groupFraction = (1 - fullLayout.boxgap);
27+
var groupGapFraction = 1 - fullLayout.boxgroupgap;
2728

2829
Lib.makeTraceGroups(boxLayer, cdbox, 'trace boxes').each(function(cd) {
2930
var plotGroup = d3.select(this);
3031
var cd0 = cd[0];
3132
var t = cd0.t;
3233
var trace = cd0.trace;
3334
if(!plotinfo.isRangePlot) cd0.node3 = plotGroup;
34-
// box half width
35-
var bdPos = t.dPos * groupFraction * (1 - fullLayout.boxgroupgap) / (group ? numBoxes : 1);
35+
36+
// position coordinate delta
37+
var dPos = t.dPos;
38+
// box half width;
39+
var bdPos;
3640
// box center offset
37-
var bPos = group ? 2 * t.dPos * (-0.5 + (t.num + 0.5) / numBoxes) * groupFraction : 0;
41+
var bPos;
42+
43+
if(trace.width) {
44+
bdPos = dPos;
45+
bPos = 0;
46+
} else {
47+
bdPos = dPos * groupFraction * groupGapFraction / (group ? numBoxes : 1);
48+
bPos = group ? 2 * dPos * (-0.5 + (t.num + 0.5) / numBoxes) * groupFraction : 0;
49+
}
50+
3851
// whisker width
3952
var wdPos = bdPos * trace.whiskerwidth;
4053

src/traces/violin/attributes.js

+3-8
Original file line numberDiff line numberDiff line change
@@ -136,18 +136,13 @@ module.exports = {
136136
].join(' ')
137137
}),
138138

139-
width: {
140-
valType: 'number',
141-
min: 0,
142-
role: 'info',
143-
dflt: 0,
144-
editType: 'calc',
139+
width: extendFlat({}, boxAttrs.width, {
145140
description: [
146-
'Sets the width of the violin.',
141+
'Sets the width of the violin in data coordinates.',
147142
'If *0* (default value) the width is automatically selected based on the positions',
148143
'of other violin traces in the same subplot.',
149144
].join(' ')
150-
},
145+
}),
151146

152147
marker: boxAttrs.marker,
153148
text: boxAttrs.text,

src/traces/violin/calc.js

+22-13
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,10 @@ module.exports = function calc(gd, trace) {
2525
trace[trace.orientation === 'h' ? 'xaxis' : 'yaxis']
2626
);
2727

28-
var violinScaleGroupStats = fullLayout._violinScaleGroupStats;
29-
var scaleGroup = trace.scalegroup;
30-
var groupStats = violinScaleGroupStats[scaleGroup];
31-
if(!groupStats) {
32-
groupStats = violinScaleGroupStats[scaleGroup] = {
33-
maxWidth: 0,
34-
maxCount: 0
35-
};
36-
}
37-
3828
var spanMin = Infinity;
3929
var spanMax = -Infinity;
30+
var maxKDE = 0;
31+
var maxCount = 0;
4032

4133
for(var i = 0; i < cd.length; i++) {
4234
var cdi = cd[i];
@@ -61,19 +53,36 @@ module.exports = function calc(gd, trace) {
6153

6254
for(var k = 0, t = span[0]; t < (span[1] + step / 2); k++, t += step) {
6355
var v = kde(t);
64-
groupStats.maxWidth = Math.max(groupStats.maxWidth, v);
6556
cdi.density[k] = {v: v, t: t};
57+
maxKDE = Math.max(maxKDE, v);
6658
}
6759

68-
groupStats.maxCount = Math.max(groupStats.maxCount, vals.length);
69-
60+
maxCount = Math.max(maxCount, vals.length);
7061
spanMin = Math.min(spanMin, span[0]);
7162
spanMax = Math.max(spanMax, span[1]);
7263
}
7364

7465
var extremes = Axes.findExtremes(valAxis, [spanMin, spanMax], {padded: true});
7566
trace._extremes[valAxis._id] = extremes;
7667

68+
if(trace.width) {
69+
cd[0].t.maxKDE = maxKDE;
70+
} else {
71+
var violinScaleGroupStats = fullLayout._violinScaleGroupStats;
72+
var scaleGroup = trace.scalegroup;
73+
var groupStats = violinScaleGroupStats[scaleGroup];
74+
75+
if(groupStats) {
76+
groupStats.maxKDE = Math.max(groupStats.maxKDE, maxKDE);
77+
groupStats.maxCount = Math.max(groupStats.maxCount, maxCount);
78+
} else {
79+
violinScaleGroupStats[scaleGroup] = {
80+
maxKDE: maxKDE,
81+
maxCount: maxCount
82+
};
83+
}
84+
}
85+
7786
cd[0].t.labels.kde = Lib._(gd, 'kde:');
7887

7988
return cd;

src/traces/violin/defaults.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,11 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
2727

2828
coerce('bandwidth');
2929
coerce('side');
30+
3031
var width = coerce('width');
3132
if(!width) {
3233
coerce('scalegroup', traceOut.name);
3334
coerce('scalemode');
34-
} else {
35-
traceOut.scalegroup = '';
36-
traceOut.scalemode = 'width';
3735
}
3836

3937
var span = coerce('span');

src/traces/violin/layout_attributes.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,22 @@ module.exports = {
1919
'If *group*, the violins are plotted next to one another',
2020
'centered around the shared location.',
2121
'If *overlay*, the violins are plotted over one another,',
22-
'you might need to set *opacity* to see them multiple violins.'
22+
'you might need to set *opacity* to see them multiple violins.',
23+
'Has no effect on traces that have *width* set.'
2324
].join(' ')
2425
}),
2526
violingap: extendFlat({}, boxLayoutAttrs.boxgap, {
2627
description: [
2728
'Sets the gap (in plot fraction) between violins of',
28-
'adjacent location coordinates.'
29+
'adjacent location coordinates.',
30+
'Has no effect on traces that have *width* set.'
2931
].join(' ')
3032
}),
3133
violingroupgap: extendFlat({}, boxLayoutAttrs.boxgroupgap, {
3234
description: [
3335
'Sets the gap (in plot fraction) between violins of',
34-
'the same location coordinate.'
36+
'the same location coordinate.',
37+
'Has no effect on traces that have *width* set.'
3538
].join(' ')
3639
})
3740
};

src/traces/violin/plot.js

+32-15
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ module.exports = function plot(gd, plotinfo, cdViolins, violinLayer) {
2020
var fullLayout = gd._fullLayout;
2121
var xa = plotinfo.xaxis;
2222
var ya = plotinfo.yaxis;
23+
var numViolins = fullLayout._numViolins;
24+
var group = (fullLayout.violinmode === 'group' && numViolins > 1);
25+
var groupFraction = 1 - fullLayout.violingap;
26+
var groupGapFraction = 1 - fullLayout.violingroupgap;
2327

2428
function makePath(pts) {
2529
var segments = linePoints(pts, {
@@ -39,16 +43,30 @@ module.exports = function plot(gd, plotinfo, cdViolins, violinLayer) {
3943
var t = cd0.t;
4044
var trace = cd0.trace;
4145
if(!plotinfo.isRangePlot) cd0.node3 = plotGroup;
42-
var numViolins = fullLayout._numViolins;
43-
var group = (fullLayout.violinmode === 'group' && numViolins > 1);
44-
var groupFraction = 1 - fullLayout.violingap;
46+
47+
// position coordinate delta
48+
var dPos = t.dPos;
4549
// violin max half width
46-
var bdPos = t.bdPos = t.dPos * groupFraction * (1 - fullLayout.violingroupgap) / (group ? numViolins : 1);
50+
var bdPos;
4751
// violin center offset
48-
var bPos = t.bPos = group ? 2 * t.dPos * (-0.5 + (t.num + 0.5) / numViolins) * groupFraction : 0;
52+
var bPos;
4953
// half-width within which to accept hover for this violin
5054
// always split the distance to the closest violin
51-
t.wHover = t.dPos * (group ? groupFraction / numViolins : 1);
55+
var wHover;
56+
57+
if(trace.width) {
58+
bdPos = dPos;
59+
bPos = 0;
60+
wHover = dPos;
61+
} else {
62+
bdPos = dPos * groupFraction * groupGapFraction / (group ? numViolins : 1);
63+
bPos = group ? 2 * dPos * (-0.5 + (t.num + 0.5) / numViolins) * groupFraction : 0;
64+
wHover = dPos * (group ? groupFraction / numViolins : 1);
65+
}
66+
67+
t.bdPos = bdPos;
68+
t.bPos = bPos;
69+
t.wHover = wHover;
5270

5371
if(trace.visible !== true || t.empty) {
5472
plotGroup.remove();
@@ -60,7 +78,6 @@ module.exports = function plot(gd, plotinfo, cdViolins, violinLayer) {
6078
var hasBothSides = trace.side === 'both';
6179
var hasPositiveSide = hasBothSides || trace.side === 'positive';
6280
var hasNegativeSide = hasBothSides || trace.side === 'negative';
63-
var groupStats = fullLayout._violinScaleGroupStats[trace.scalegroup];
6481

6582
var violins = plotGroup.selectAll('path.violin').data(Lib.identity);
6683

@@ -76,15 +93,15 @@ module.exports = function plot(gd, plotinfo, cdViolins, violinLayer) {
7693
var len = density.length;
7794
var posCenter = d.pos + bPos;
7895
var posCenterPx = posAxis.c2p(posCenter);
79-
var scale;
8096

81-
switch(trace.scalemode) {
82-
case 'width':
83-
scale = groupStats.maxWidth / bdPos;
84-
break;
85-
case 'count':
86-
scale = (groupStats.maxWidth / bdPos) * (groupStats.maxCount / d.pts.length);
87-
break;
97+
var scale;
98+
if(trace.width) {
99+
scale = t.maxKDE / bdPos;
100+
} else {
101+
var groupStats = fullLayout._violinScaleGroupStats[trace.scalegroup];
102+
scale = trace.scalemode === 'count' ?
103+
(groupStats.maxKDE / bdPos) * (groupStats.maxCount / d.pts.length) :
104+
groupStats.maxKDE / bdPos;
88105
}
89106

90107
var pathPos, pathNeg, path;
Loading
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,44 @@
11
{
22
"data": [{
33
"type": "violin",
4-
"width": 0.315,
4+
"width": 0.4,
5+
"name": "width: 0.4",
56
"x": [0, 5, 7, 8],
6-
"points": "none",
77
"side": "positive",
8-
"box": {
9-
"visible": false
10-
},
11-
"boxpoints": false,
128
"line": {
139
"color": "black"
1410
},
1511
"fillcolor": "#8dd3c7",
1612
"opacity": 0.6,
17-
"meanline": {
18-
"visible": false
19-
},
2013
"y0": 0.0
2114
}, {
2215
"type": "violin",
16+
"name": "auto",
2317
"x": [0, 5, 7, 8],
24-
"points": "none",
2518
"side": "positive",
26-
"box": {
27-
"visible": false
28-
},
29-
"boxpoints": false,
3019
"line": {
3120
"color": "black"
3221
},
3322
"fillcolor": "#d3c78d",
3423
"opacity": 0.6,
35-
"meanline": {
36-
"visible": false
37-
},
3824
"y0": 0.1
3925
}, {
4026
"type": "box",
41-
"width": 0.5421,
27+
"width": 0.6,
28+
"name": "width: 0.6",
4229
"x": [0, 5, 7, 8],
43-
"points": "none",
4430
"side": "positive",
45-
"box": {
46-
"visible": false
47-
},
48-
"boxpoints": false,
4931
"line": {
5032
"color": "black"
5133
},
5234
"fillcolor": "#c78dd3",
5335
"opacity": 0.6,
54-
"meanline": {
55-
"visible": false
56-
},
5736
"y0": 0.2
5837
}],
5938
"layout": {
6039
"title": "Joyplot - Violin with multiple widths",
40+
"legend": {"x": 0},
6141
"xaxis": {"zeroline": false},
62-
"violingap": 0
42+
"yaxis": {"dtick": 0.1, "gridcolor": "black"}
6343
}
6444
}

test/jasmine/tests/violin_test.js

+18-1
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,23 @@ describe('Test violin defaults', function() {
142142
expect(traceOut.meanline.color).toBe('blue');
143143
expect(traceOut.meanline.width).toBe(10);
144144
});
145+
146+
it('should not coerce *scalegroup* and *scalemode* when *width* is set', function() {
147+
_supply({
148+
y: [1, 2, 1],
149+
width: 1
150+
});
151+
expect(traceOut.scalemode).toBeUndefined();
152+
expect(traceOut.scalegroup).toBeUndefined();
153+
154+
_supply({
155+
y: [1, 2, 1],
156+
// width=0 is ignored during calc
157+
width: 0
158+
});
159+
expect(traceOut.scalemode).toBe('width');
160+
expect(traceOut.scalegroup).toBe('');
161+
});
145162
});
146163

147164
describe('Test violin calc:', function() {
@@ -236,7 +253,7 @@ describe('Test violin calc:', function() {
236253
name: 'one',
237254
y: [0, 0, 0, 0, 10, 10, 10, 10]
238255
});
239-
expect(fullLayout._violinScaleGroupStats.one.maxWidth).toBeCloseTo(0.055);
256+
expect(fullLayout._violinScaleGroupStats.one.maxKDE).toBeCloseTo(0.055);
240257
expect(fullLayout._violinScaleGroupStats.one.maxCount).toBe(8);
241258
});
242259

0 commit comments

Comments
 (0)