Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 13daed2

Browse files
authoredJan 21, 2019
Merge pull request #3234 from plotly/joyplots2
Joyplots2
2 parents cc45972 + 2517ed0 commit 13daed2

38 files changed

+455
-117
lines changed
 

‎src/traces/box/attributes.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,20 @@ module.exports = {
171171
'the vertical (horizontal).'
172172
].join(' ')
173173
},
174+
175+
width: {
176+
valType: 'number',
177+
min: 0,
178+
role: 'info',
179+
dflt: 0,
180+
editType: 'calc',
181+
description: [
182+
'Sets the width of the box in data coordinate',
183+
'If *0* (default value) the width is automatically selected based on the positions',
184+
'of other box traces in the same subplot.'
185+
].join(' ')
186+
},
187+
174188
marker: {
175189
outliercolor: {
176190
valType: 'color',
@@ -244,7 +258,6 @@ module.exports = {
244258
marker: scatterAttrs.unselected.marker,
245259
editType: 'style'
246260
},
247-
248261
hoveron: {
249262
valType: 'flaglist',
250263
flags: ['boxes', 'points'],

‎src/traces/box/calc.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,17 +64,21 @@ module.exports = function calc(gd, trace) {
6464
}
6565
}
6666

67+
var cdi;
68+
var ptFilterFn = (trace.boxpoints || trace.points) === 'all' ?
69+
Lib.identity :
70+
function(pt) { return (pt.v < cdi.lf || pt.v > cdi.uf); };
71+
6772
// build calcdata trace items, one item per distinct position
6873
for(i = 0; i < pLen; i++) {
6974
if(ptsPerBin[i].length > 0) {
7075
var pts = ptsPerBin[i].sort(sortByVal);
7176
var boxVals = pts.map(extractVal);
7277
var bvLen = boxVals.length;
7378

74-
var cdi = {
75-
pos: posDistinct[i],
76-
pts: pts
77-
};
79+
cdi = {};
80+
cdi.pos = posDistinct[i];
81+
cdi.pts = pts;
7882

7983
cdi.min = boxVals[0];
8084
cdi.max = boxVals[bvLen - 1];
@@ -110,13 +114,14 @@ module.exports = function calc(gd, trace) {
110114
cdi.lo = 4 * cdi.q1 - 3 * cdi.q3;
111115
cdi.uo = 4 * cdi.q3 - 3 * cdi.q1;
112116

113-
114117
// lower and upper notches ~95% Confidence Intervals for median
115118
var iqr = cdi.q3 - cdi.q1;
116119
var mci = 1.57 * iqr / Math.sqrt(bvLen);
117120
cdi.ln = cdi.med - mci;
118121
cdi.un = cdi.med + mci;
119122

123+
cdi.pts2 = pts.filter(ptFilterFn);
124+
120125
cd.push(cdi);
121126
}
122127
}

‎src/traces/box/cross_trace_calc.js

Lines changed: 133 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ function crossTraceCalc(gd, plotinfo) {
2222
var orientation = orientations[i];
2323
var posAxis = orientation === 'h' ? ya : xa;
2424
var boxList = [];
25-
var minPad = 0;
26-
var maxPad = 0;
2725

2826
// make list of boxes / candlesticks
2927
// For backward compatibility, candlesticks are treated as if they *are* box traces here
@@ -40,72 +38,173 @@ function crossTraceCalc(gd, plotinfo) {
4038
trace.yaxis === ya._id
4139
) {
4240
boxList.push(j);
43-
44-
if(trace.boxpoints) {
45-
minPad = Math.max(minPad, trace.jitter - trace.pointpos - 1);
46-
maxPad = Math.max(maxPad, trace.jitter + trace.pointpos - 1);
47-
}
4841
}
4942
}
5043

51-
setPositionOffset('box', gd, boxList, posAxis, [minPad, maxPad]);
44+
setPositionOffset('box', gd, boxList, posAxis);
5245
}
5346
}
5447

55-
function setPositionOffset(traceType, gd, boxList, posAxis, pad) {
48+
function setPositionOffset(traceType, gd, boxList, posAxis) {
5649
var calcdata = gd.calcdata;
5750
var fullLayout = gd._fullLayout;
58-
var pointList = [];
51+
var axId = posAxis._id;
52+
var axLetter = axId.charAt(0);
5953

6054
// N.B. reused in violin
6155
var numKey = traceType === 'violin' ? '_numViolins' : '_numBoxes';
6256

6357
var i, j, calcTrace;
58+
var pointList = [];
59+
var shownPts = 0;
6460

6561
// make list of box points
6662
for(i = 0; i < boxList.length; i++) {
6763
calcTrace = calcdata[boxList[i]];
6864
for(j = 0; j < calcTrace.length; j++) {
6965
pointList.push(calcTrace[j].pos);
66+
shownPts += (calcTrace[j].pts2 || []).length;
7067
}
7168
}
7269

7370
if(!pointList.length) return;
7471

7572
// box plots - update dPos based on multiple traces
76-
// and then use for posAxis autorange
7773
var boxdv = Lib.distinctVals(pointList);
78-
var dPos = boxdv.minDiff / 2;
79-
80-
// if there's no duplication of x points,
81-
// disable 'group' mode by setting counter to 1
82-
if(pointList.length === boxdv.vals.length) {
83-
fullLayout[numKey] = 1;
84-
}
74+
var dPos0 = boxdv.minDiff / 2;
8575

8676
// check for forced minimum dtick
8777
Axes.minDtick(posAxis, boxdv.minDiff, boxdv.vals[0], true);
8878

89-
var gap = fullLayout[traceType + 'gap'];
90-
var groupgap = fullLayout[traceType + 'groupgap'];
91-
var padfactor = (1 - gap) * (1 - groupgap) * dPos / fullLayout[numKey];
92-
93-
// autoscale the x axis - including space for points if they're off the side
94-
// TODO: this will overdo it if the outermost boxes don't have
95-
// their points as far out as the other boxes
96-
var extremes = Axes.findExtremes(posAxis, boxdv.vals, {
97-
vpadminus: dPos + pad[0] * padfactor,
98-
vpadplus: dPos + pad[1] * padfactor
99-
});
79+
var num = fullLayout[numKey];
80+
var group = (fullLayout[traceType + 'mode'] === 'group' && num > 1);
81+
var groupFraction = 1 - fullLayout[traceType + 'gap'];
82+
var groupGapFraction = 1 - fullLayout[traceType + 'groupgap'];
10083

10184
for(i = 0; i < boxList.length; i++) {
10285
calcTrace = calcdata[boxList[i]];
103-
// set the width of all boxes
104-
calcTrace[0].t.dPos = dPos;
105-
// link extremes to all boxes
106-
calcTrace[0].trace._extremes[posAxis._id] = extremes;
107-
}
10886

87+
var trace = calcTrace[0].trace;
88+
var t = calcTrace[0].t;
89+
var width = trace.width;
90+
var side = trace.side;
91+
92+
// position coordinate delta
93+
var dPos;
94+
// box half width;
95+
var bdPos;
96+
// box center offset
97+
var bPos;
98+
// half-width within which to accept hover for this box/violin
99+
// always split the distance to the closest box/violin
100+
var wHover;
101+
102+
if(width) {
103+
dPos = bdPos = wHover = width / 2;
104+
bPos = 0;
105+
} else {
106+
dPos = dPos0;
107+
bdPos = dPos * groupFraction * groupGapFraction / (group ? num : 1);
108+
bPos = group ? 2 * dPos * (-0.5 + (t.num + 0.5) / num) * groupFraction : 0;
109+
wHover = dPos * (group ? groupFraction / num : 1);
110+
}
111+
t.dPos = dPos;
112+
t.bPos = bPos;
113+
t.bdPos = bdPos;
114+
t.wHover = wHover;
115+
116+
// box/violin-only value-space push value
117+
var pushplus;
118+
var pushminus;
119+
// edge of box/violin
120+
var edge = bPos + bdPos;
121+
var edgeplus;
122+
var edgeminus;
123+
124+
if(side === 'positive') {
125+
pushplus = dPos * (width ? 1 : 0.5);
126+
edgeplus = edge;
127+
pushminus = edgeplus = bPos;
128+
} else if(side === 'negative') {
129+
pushplus = edgeplus = bPos;
130+
pushminus = dPos * (width ? 1 : 0.5);
131+
edgeminus = edge;
132+
} else {
133+
pushplus = pushminus = dPos;
134+
edgeplus = edgeminus = edge;
135+
}
136+
137+
// value-space padding
138+
var vpadplus;
139+
var vpadminus;
140+
// pixel-space padding
141+
var ppadplus;
142+
var ppadminus;
143+
// do we add 5% of both sides (for points beyond box/violin)
144+
var padded = false;
145+
// does this trace show points?
146+
var hasPts = (trace.boxpoints || trace.points) && (shownPts > 0);
147+
148+
if(hasPts) {
149+
var pointpos = trace.pointpos;
150+
var jitter = trace.jitter;
151+
var ms = trace.marker.size / 2;
152+
153+
var pp = 0;
154+
if((pointpos + jitter) >= 0) {
155+
pp = edge * (pointpos + jitter);
156+
if(pp > pushplus) {
157+
// (++) beyond plus-value, use pp
158+
padded = true;
159+
ppadplus = ms;
160+
vpadplus = pp;
161+
} else if(pp > edgeplus) {
162+
// (+), use push-value (it's bigger), but add px-pad
163+
ppadplus = ms;
164+
vpadplus = pushplus;
165+
}
166+
}
167+
if(pp <= pushplus) {
168+
// (->) fallback to push value
169+
vpadplus = pushplus;
170+
}
171+
172+
var pm = 0;
173+
if((pointpos - jitter) <= 0) {
174+
pm = -edge * (pointpos - jitter);
175+
if(pm > pushminus) {
176+
// (--) beyond plus-value, use pp
177+
padded = true;
178+
ppadminus = ms;
179+
vpadminus = pm;
180+
} else if(pm > edgeminus) {
181+
// (-), use push-value (it's bigger), but add px-pad
182+
ppadminus = ms;
183+
vpadminus = pushminus;
184+
}
185+
}
186+
if(pm <= pushminus) {
187+
// (<-) fallback to push value
188+
vpadminus = pushminus;
189+
}
190+
} else {
191+
vpadplus = pushplus;
192+
vpadminus = pushminus;
193+
}
194+
195+
// calcdata[i][j] are in ascending order
196+
var firstPos = calcTrace[0].pos;
197+
var lastPos = calcTrace[calcTrace.length - 1].pos;
198+
199+
trace._extremes[axId] = Axes.findExtremes(posAxis, [firstPos, lastPos], {
200+
padded: padded,
201+
vpadminus: vpadminus,
202+
vpadplus: vpadplus,
203+
// N.B. SVG px-space positive/negative
204+
ppadminus: {x: ppadminus, y: ppadplus}[axLetter],
205+
ppadplus: {x: ppadplus, y: ppadminus}[axLetter],
206+
});
207+
}
109208
}
110209

111210
module.exports = {

‎src/traces/box/defaults.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
2828

2929
coerce('whiskerwidth');
3030
coerce('boxmean');
31+
coerce('width');
3132

3233
var notched = coerce('notched', traceIn.notchwidth !== undefined);
3334
if(notched) coerce('notchwidth');

‎src/traces/box/layout_attributes.js

Lines changed: 6 additions & 3 deletions
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

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,18 @@ var JITTERCOUNT = 5; // points either side of this to include
1818
var JITTERSPREAD = 0.01; // fraction of IQR to count as "dense"
1919

2020
function plot(gd, plotinfo, cdbox, boxLayer) {
21-
var fullLayout = gd._fullLayout;
2221
var xa = plotinfo.xaxis;
2322
var ya = plotinfo.yaxis;
24-
var numBoxes = fullLayout._numBoxes;
25-
var groupFraction = (1 - fullLayout.boxgap);
26-
var group = (fullLayout.boxmode === 'group' && numBoxes > 1);
2723

2824
Lib.makeTraceGroups(boxLayer, cdbox, 'trace boxes').each(function(cd) {
2925
var plotGroup = d3.select(this);
3026
var cd0 = cd[0];
3127
var t = cd0.t;
3228
var trace = cd0.trace;
3329
if(!plotinfo.isRangePlot) cd0.node3 = plotGroup;
34-
// box half width
35-
var bdPos = t.dPos * groupFraction * (1 - fullLayout.boxgroupgap) / (group ? numBoxes : 1);
36-
// box center offset
37-
var bPos = group ? 2 * t.dPos * (-0.5 + (t.num + 0.5) / numBoxes) * groupFraction : 0;
30+
3831
// whisker width
39-
var wdPos = bdPos * trace.whiskerwidth;
32+
t.wdPos = t.bdPos * trace.whiskerwidth;
4033

4134
if(trace.visible !== true || t.empty) {
4235
plotGroup.remove();
@@ -53,14 +46,6 @@ function plot(gd, plotinfo, cdbox, boxLayer) {
5346
valAxis = ya;
5447
}
5548

56-
// save the box size and box position for use by hover
57-
t.bPos = bPos;
58-
t.bdPos = bdPos;
59-
t.wdPos = wdPos;
60-
// half-width within which to accept hover for this box
61-
// always split the distance to the closest box
62-
t.wHover = t.dPos * (group ? groupFraction / numBoxes : 1);
63-
6449
plotBoxAndWhiskers(plotGroup, {pos: posAxis, val: valAxis}, trace, t);
6550
plotPoints(plotGroup, {x: xa, y: ya}, trace, t);
6651
plotBoxMean(plotGroup, {pos: posAxis, val: valAxis}, trace, t);
@@ -192,10 +177,7 @@ function plotPoints(sel, axes, trace, t) {
192177
var paths = gPoints.selectAll('path')
193178
.data(function(d) {
194179
var i;
195-
196-
var pts = mode === 'all' ?
197-
d.pts :
198-
d.pts.filter(function(pt) { return (pt.v < d.lf || pt.v > d.uf); });
180+
var pts = d.pts2;
199181

200182
// normally use IQR, but if this is 0 or too small, use max-min
201183
var typicalSpread = Math.max((d.max - d.min) / 10, d.q3 - d.q1);

‎src/traces/violin/attributes.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,15 @@ module.exports = {
135135
'right (left) for vertical violins and above (below) for horizontal violins.'
136136
].join(' ')
137137
}),
138+
139+
width: extendFlat({}, boxAttrs.width, {
140+
description: [
141+
'Sets the width of the violin in data coordinates.',
142+
'If *0* (default value) the width is automatically selected based on the positions',
143+
'of other violin traces in the same subplot.',
144+
].join(' ')
145+
}),
146+
138147
marker: boxAttrs.marker,
139148
text: boxAttrs.text,
140149

@@ -220,7 +229,7 @@ module.exports = {
220229
values: ['both', 'positive', 'negative'],
221230
dflt: 'both',
222231
role: 'info',
223-
editType: 'plot',
232+
editType: 'calc',
224233
description: [
225234
'Determines on which side of the position value the density function making up',
226235
'one half of a violin is plotted.',

‎src/traces/violin/calc.js

Lines changed: 22 additions & 13 deletions
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/cross_trace_calc.js

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ module.exports = function crossTraceCalc(gd, plotinfo) {
2020
var orientation = orientations[i];
2121
var posAxis = orientation === 'h' ? ya : xa;
2222
var violinList = [];
23-
var minPad = 0;
24-
var maxPad = 0;
2523

2624
for(var j = 0; j < calcdata.length; j++) {
2725
var cd = calcdata[j];
@@ -35,14 +33,9 @@ module.exports = function crossTraceCalc(gd, plotinfo) {
3533
trace.yaxis === ya._id
3634
) {
3735
violinList.push(j);
38-
39-
if(trace.points !== false) {
40-
minPad = Math.max(minPad, trace.jitter - trace.pointpos - 1);
41-
maxPad = Math.max(maxPad, trace.jitter + trace.pointpos - 1);
42-
}
4336
}
4437
}
4538

46-
setPositionOffset('violin', gd, violinList, posAxis, [minPad, maxPad]);
39+
setPositionOffset('violin', gd, violinList, posAxis);
4740
}
4841
};

‎src/traces/violin/defaults.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,14 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
2626
if(traceOut.visible === false) return;
2727

2828
coerce('bandwidth');
29-
coerce('scalegroup', traceOut.name);
30-
coerce('scalemode');
3129
coerce('side');
3230

31+
var width = coerce('width');
32+
if(!width) {
33+
coerce('scalegroup', traceOut.name);
34+
coerce('scalemode');
35+
}
36+
3337
var span = coerce('span');
3438
var spanmodeDflt;
3539
if(Array.isArray(span)) spanmodeDflt = 'manual';

‎src/traces/violin/layout_attributes.js

Lines changed: 6 additions & 3 deletions
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

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -39,28 +39,19 @@ module.exports = function plot(gd, plotinfo, cdViolins, violinLayer) {
3939
var t = cd0.t;
4040
var trace = cd0.trace;
4141
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;
45-
// violin max half width
46-
var bdPos = t.bdPos = t.dPos * groupFraction * (1 - fullLayout.violingroupgap) / (group ? numViolins : 1);
47-
// violin center offset
48-
var bPos = t.bPos = group ? 2 * t.dPos * (-0.5 + (t.num + 0.5) / numViolins) * groupFraction : 0;
49-
// half-width within which to accept hover for this violin
50-
// always split the distance to the closest violin
51-
t.wHover = t.dPos * (group ? groupFraction / numViolins : 1);
5242

5343
if(trace.visible !== true || t.empty) {
5444
plotGroup.remove();
5545
return;
5646
}
5747

48+
var bPos = t.bPos;
49+
var bdPos = t.bdPos;
5850
var valAxis = plotinfo[t.valLetter + 'axis'];
5951
var posAxis = plotinfo[t.posLetter + 'axis'];
6052
var hasBothSides = trace.side === 'both';
6153
var hasPositiveSide = hasBothSides || trace.side === 'positive';
6254
var hasNegativeSide = hasBothSides || trace.side === 'negative';
63-
var groupStats = fullLayout._violinScaleGroupStats[trace.scalegroup];
6455

6556
var violins = plotGroup.selectAll('path.violin').data(Lib.identity);
6657

@@ -76,15 +67,15 @@ module.exports = function plot(gd, plotinfo, cdViolins, violinLayer) {
7667
var len = density.length;
7768
var posCenter = d.pos + bPos;
7869
var posCenterPx = posAxis.c2p(posCenter);
79-
var scale;
8070

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;
71+
var scale;
72+
if(trace.width) {
73+
scale = t.maxKDE / bdPos;
74+
} else {
75+
var groupStats = fullLayout._violinScaleGroupStats[trace.scalegroup];
76+
scale = trace.scalemode === 'count' ?
77+
(groupStats.maxKDE / bdPos) * (groupStats.maxCount / d.pts.length) :
78+
groupStats.maxKDE / bdPos;
8879
}
8980

9081
var pathPos, pathNeg, path;

‎test/image/baselines/10.png

-698 Bytes
Loading

‎test/image/baselines/31.png

215 Bytes
Loading

‎test/image/baselines/box_grouped.png

8 Bytes
Loading
422 Bytes
Loading
Loading
16.5 KB
Loading
-189 Bytes
Loading
8 Bytes
Loading
-198 Bytes
Loading
Loading
1.65 KB
Loading
-465 Bytes
Loading
Loading
1.85 KB
Loading
Loading
Loading
248 Bytes
Loading
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"data": [{
3+
"type": "box",
4+
"x0": 1,
5+
"y": [1, 2, 1, 2, 1, 2, 3, 4, 4]
6+
}, {
7+
"type": "box",
8+
"x0": 2,
9+
"y": [2, 1, 2, 3, 3, 1, 0, 0, 1]
10+
}],
11+
"layout": {
12+
"title": {"text": "single-box groups!"},
13+
"boxmode": "group",
14+
"showlegend": false
15+
}
16+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
{
2+
"data": [{
3+
"type": "violin",
4+
"width": 0.4,
5+
"name": "width: 0.4",
6+
"x": [0, 5, 7, 8],
7+
"side": "positive",
8+
"line": {
9+
"color": "black"
10+
},
11+
"fillcolor": "#8dd3c7",
12+
"opacity": 0.6,
13+
"y0": 0.0
14+
}, {
15+
"type": "violin",
16+
"name": "auto",
17+
"x": [0, 5, 7, 8],
18+
"side": "positive",
19+
"line": {
20+
"color": "black"
21+
},
22+
"fillcolor": "#d3c78d",
23+
"opacity": 0.6,
24+
"y0": 0.1
25+
}, {
26+
"type": "box",
27+
"width": 0.6,
28+
"name": "width: 0.6",
29+
"x": [0, 5, 7, 8],
30+
"side": "positive",
31+
"line": {
32+
"color": "black"
33+
},
34+
"fillcolor": "#c78dd3",
35+
"opacity": 0.6,
36+
"y0": 0.2
37+
}, {
38+
"type": "violin",
39+
"width": 0.4,
40+
"name": "width: 0.4 (solo)",
41+
"x": [0, 5, 7, 8],
42+
"side": "positive",
43+
"line": {
44+
"color": "black"
45+
},
46+
"fillcolor": "#8dd3c7",
47+
"opacity": 0.6,
48+
"y0": 0.0,
49+
"xaxis": "x2",
50+
"yaxis": "y2"
51+
}],
52+
"layout": {
53+
"grid": {"rows": 1, "columns": 2, "pattern": "independent"},
54+
"title": {"text" :"Violins/boxes - with multiple widths", "x": 0, "xref": "paper"},
55+
"legend": {"x": 1, "y": 1, "xanchor": "right", "yanchor": "bottom"},
56+
"xaxis": {"zeroline": false},
57+
"yaxis": {"dtick": 0.1, "gridcolor": "black"}
58+
}
59+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"data": [{
3+
"type": "violin",
4+
"points": "all",
5+
"pointpos": 1.5,
6+
"marker": {"size": 12},
7+
"jitter": 0,
8+
"x": [0, 5, 7, 8],
9+
"side": "negative",
10+
"line": {
11+
"color": "black"
12+
},
13+
"fillcolor": "#d3c78d",
14+
"opacity": 0.6,
15+
"y0": 0.0
16+
}],
17+
"layout": {
18+
"title": "Violins - negative sided with positive points",
19+
"legend": {"x": 0},
20+
"xaxis": {"zeroline": false},
21+
"yaxis": {"dtick": 0.1, "gridcolor": "black"}
22+
}
23+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"data": [{
3+
"type": "violin",
4+
"points": "all",
5+
"pointpos": -0.3,
6+
"jitter": 0,
7+
"x": [0, 5, 7, 8],
8+
"side": "positive",
9+
"line": {
10+
"color": "black"
11+
},
12+
"fillcolor": "#d3c78d",
13+
"opacity": 0.6,
14+
"y0": 0.0
15+
},{
16+
"type": "violin",
17+
"points": "all",
18+
"pointpos": 0,
19+
"jitter": 0,
20+
"x": [20, 25, 27, 28],
21+
"side": "negative",
22+
"line": {
23+
"color": "black"
24+
},
25+
"fillcolor": "#d3c78d",
26+
"opacity": 0.6,
27+
"y0": 0.0
28+
}],
29+
"layout": {
30+
"title": "Violins - positive and negative",
31+
"showlegend": false,
32+
"xaxis": {"zeroline": false},
33+
"yaxis": {"dtick": 0.1, "gridcolor": "black"}
34+
}
35+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"data": [{
3+
"type": "violin",
4+
"points": "all",
5+
"pointpos": -0.4,
6+
"jitter": 0,
7+
"x": [0, 5, 7, 8],
8+
"side": "positive",
9+
"line": {
10+
"color": "black"
11+
},
12+
"fillcolor": "#8dd3c7",
13+
"opacity": 0.6,
14+
"y0": 0.0
15+
}, {
16+
"type": "violin",
17+
"points": "all",
18+
"pointpos": -0.1,
19+
"jitter": 0,
20+
"x": [20, 25, 27, 28],
21+
"side": "positive",
22+
"line": {
23+
"color": "black"
24+
},
25+
"fillcolor": "#d3c78d",
26+
"opacity": 0.6,
27+
"y0": 0.0
28+
}],
29+
"layout": {
30+
"title": "Violins - only positive sided",
31+
"legend": {"x": 0},
32+
"xaxis": {"zeroline": false},
33+
"yaxis": {"dtick": 0.1, "gridcolor": "black"}
34+
}
35+
}

‎test/jasmine/tests/box_test.js

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ describe('Test box hover:', function() {
240240
trace.hoveron = 'points';
241241
});
242242
fig.layout.hovermode = 'closest';
243+
fig.layout.xaxis = {range: [-0.565, 1.5]};
243244
return fig;
244245
},
245246
nums: '(day 1, 0.7)',
@@ -252,6 +253,7 @@ describe('Test box hover:', function() {
252253
trace.hoveron = 'points';
253254
});
254255
fig.layout.hovermode = 'x';
256+
fig.layout.xaxis = {range: [-0.565, 1.5]};
255257
return fig;
256258
},
257259
nums: '0.7',
@@ -265,6 +267,7 @@ describe('Test box hover:', function() {
265267
trace.hoveron = 'points+boxes';
266268
});
267269
fig.layout.hovermode = 'x';
270+
fig.layout.xaxis = {range: [-0.565, 1.5]};
268271
return fig;
269272
},
270273
pos: [215, 200],
@@ -294,6 +297,7 @@ describe('Test box hover:', function() {
294297
trace.text = trace.y.map(function(v) { return 'look:' + v; });
295298
});
296299
fig.layout.hovermode = 'closest';
300+
fig.layout.xaxis = {range: [-0.565, 1.5]};
297301
return fig;
298302
},
299303
nums: '(day 1, 0.7)\nlook:0.7',
@@ -308,6 +312,7 @@ describe('Test box hover:', function() {
308312
trace.hoverinfo = 'text';
309313
});
310314
fig.layout.hovermode = 'closest';
315+
fig.layout.xaxis = {range: [-0.565, 1.5]};
311316
return fig;
312317
},
313318
nums: 'look:0.7',
@@ -449,7 +454,7 @@ describe('Test box restyle:', function() {
449454
});
450455
})
451456
.then(function() {
452-
_assert('auto rng / all boxpoints', [-0.695, 0.5], [-0.555, 10.555]);
457+
_assert('auto rng / all boxpoints', [-0.5055, 0.5], [-0.555, 10.555]);
453458
return Plotly.restyle(gd, 'boxpoints', false);
454459
})
455460
.then(function() {
@@ -458,4 +463,36 @@ describe('Test box restyle:', function() {
458463
.catch(failTest)
459464
.then(done);
460465
});
466+
467+
it('should be able to change axis range when the number of distinct positions changes', function(done) {
468+
function _assert(msg, xrng, yrng) {
469+
var fullLayout = gd._fullLayout;
470+
expect(fullLayout.xaxis.range).toBeCloseToArray(xrng, 2, msg + ' xrng');
471+
expect(fullLayout.yaxis.range).toBeCloseToArray(yrng, 2, msg + ' yrng');
472+
}
473+
474+
Plotly.plot(gd, [{
475+
type: 'box',
476+
width: 0.4,
477+
y: [0, 5, 7, 8],
478+
y0: 0
479+
}, {
480+
type: 'box',
481+
y: [0, 5, 7, 8],
482+
y0: 0.1
483+
}])
484+
.then(function() {
485+
_assert('base', [-0.2, 1.5], [-0.444, 8.444]);
486+
return Plotly.restyle(gd, 'visible', [true, 'legendonly']);
487+
})
488+
.then(function() {
489+
_assert('only trace0 visible', [-0.2, 0.2], [-0.444, 8.444]);
490+
return Plotly.restyle(gd, 'visible', ['legendonly', true]);
491+
})
492+
.then(function() {
493+
_assert('only trace1 visible', [-0.5, 0.5], [-0.444, 8.444]);
494+
})
495+
.catch(failTest)
496+
.then(done);
497+
});
461498
});

‎test/jasmine/tests/cartesian_interact_test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,10 @@ describe('main plot pan', function() {
7777
var mock = require('@mocks/10.json');
7878
var precision = 5;
7979

80-
var originalX = [-0.6225, 5.5];
80+
var originalX = [-0.5251046025104602, 5.5];
8181
var originalY = [-1.6340975059013805, 7.166241526218911];
8282

83-
var newX = [-2.0255729166666665, 4.096927083333333];
83+
var newX = [-1.905857740585774, 4.119246861924687];
8484
var newY = [-0.3769062155984817, 8.42343281652181];
8585

8686
function _drag(x0, y0, x1, y1) {

‎test/jasmine/tests/select_test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2294,6 +2294,7 @@ describe('Test select box and lasso per trace:', function() {
22942294
fig.layout.dragmode = 'lasso';
22952295
fig.layout.width = 600;
22962296
fig.layout.height = 500;
2297+
fig.layout.xaxis = {range: [-0.565, 1.5]};
22972298
addInvisible(fig);
22982299

22992300
Plotly.plot(gd, fig)

‎test/jasmine/tests/violin_test.js

Lines changed: 24 additions & 4 deletions
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

@@ -482,7 +499,10 @@ describe('Test violin hover:', function() {
482499
patch: function(fig) {
483500
fig.data[0].x = fig.data[0].y;
484501
delete fig.data[0].y;
485-
fig.layout = {hovermode: 'closest'};
502+
fig.layout = {
503+
hovermode: 'closest',
504+
yaxis: {range: [-0.696, 0.5]}
505+
};
486506
return fig;
487507
},
488508
pos: [539, 293],
@@ -567,7 +587,7 @@ describe('Test violin hover:', function() {
567587

568588
Plotly.plot(gd, fig).then(function() {
569589
mouseEvent('mousemove', 300, 250);
570-
assertViolinHoverLine([299.35, 250, 250, 250]);
590+
assertViolinHoverLine([277.3609, 250, 80, 250]);
571591
})
572592
.catch(failTest)
573593
.then(done);
@@ -578,7 +598,7 @@ describe('Test violin hover:', function() {
578598

579599
Plotly.plot(gd, fig).then(function() {
580600
mouseEvent('mousemove', 200, 250);
581-
assertViolinHoverLine([200.65, 250, 250, 250]);
601+
assertViolinHoverLine([222.6391, 250, 420, 250]);
582602
})
583603
.catch(failTest)
584604
.then(done);

0 commit comments

Comments
 (0)
Please sign in to comment.