Skip to content

Commit 4e48cd7

Browse files
committed
fix violin box/meanline/pts removal
1 parent 47349c7 commit 4e48cd7

File tree

4 files changed

+149
-85
lines changed

4 files changed

+149
-85
lines changed

src/traces/box/plot.js

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -73,18 +73,9 @@ function plot(gd, plotinfo, cdbox, boxLayer) {
7373
// always split the distance to the closest box
7474
t.wHover = t.dPos * (group ? groupFraction / numBoxes : 1);
7575

76-
// boxes and whiskers
7776
plotBoxAndWhiskers(sel, {pos: posAxis, val: valAxis}, trace, t);
78-
79-
// draw points, if desired
80-
if(trace.boxpoints) {
81-
plotPoints(sel, {x: xa, y: ya}, trace, t);
82-
}
83-
84-
// draw mean (and stdev diamond) if desired
85-
if(trace.boxmean) {
86-
plotBoxMean(sel, {pos: posAxis, val: valAxis}, trace, t);
87-
}
77+
plotPoints(sel, {x: xa, y: ya}, trace, t);
78+
plotBoxMean(sel, {pos: posAxis, val: valAxis}, trace, t);
8879
});
8980
}
9081

@@ -109,7 +100,14 @@ function plotBoxAndWhiskers(sel, axes, trace, t) {
109100
bdPos1 = t.bdPos;
110101
}
111102

112-
var paths = sel.selectAll('path.box').data(Lib.identity);
103+
var fn;
104+
if(trace.type === 'box' ||
105+
(trace.type === 'violin' && (trace.box || {}).visible)
106+
) {
107+
fn = Lib.identity;
108+
}
109+
110+
var paths = sel.selectAll('path.box').data(fn || []);
113111

114112
paths.enter().append('path')
115113
.style('vector-effect', 'non-scaling-stroke')
@@ -187,16 +185,18 @@ function plotPoints(sel, axes, trace, t) {
187185
// repeatable pseudo-random number generator
188186
Lib.seedPseudoRandom();
189187

190-
var gPoints = sel.selectAll('g.points')
191-
// since box plot points get an extra level of nesting, each
192-
// box needs the trace styling info
193-
.data(function(d) {
194-
d.forEach(function(v) {
195-
v.t = t;
196-
v.trace = trace;
197-
});
198-
return d;
188+
// since box plot points get an extra level of nesting, each
189+
// box needs the trace styling info
190+
var fn = function(d) {
191+
d.forEach(function(v) {
192+
v.t = t;
193+
v.trace = trace;
199194
});
195+
return d;
196+
};
197+
198+
var gPoints = sel.selectAll('g.points')
199+
.data(mode ? fn : []);
200200

201201
gPoints.enter().append('g')
202202
.attr('class', 'points');
@@ -306,7 +306,14 @@ function plotBoxMean(sel, axes, trace, t) {
306306
bdPos1 = t.bdPos;
307307
}
308308

309-
var paths = sel.selectAll('path.mean').data(Lib.identity);
309+
var fn;
310+
if(trace.type === 'box' && trace.boxmean ||
311+
(trace.type === 'violin' && (trace.box || {}).visible && (trace.meanline || {}).visible)
312+
) {
313+
fn = Lib.identity;
314+
}
315+
316+
var paths = sel.selectAll('path.mean').data(fn || []);
310317

311318
paths.enter().append('path')
312319
.attr('class', 'mean')

src/traces/violin/plot.js

Lines changed: 52 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,6 @@ module.exports = function plot(gd, plotinfo, cd, violinLayer) {
6969
var hasBothSides = trace.side === 'both';
7070
var hasPositiveSide = hasBothSides || trace.side === 'positive';
7171
var hasNegativeSide = hasBothSides || trace.side === 'negative';
72-
var hasBox = trace.box && trace.box.visible;
73-
var hasMeanLine = trace.meanline && trace.meanline.visible;
7472
var groupStats = fullLayout._violinScaleGroupStats[trace.scalegroup];
7573

7674
var violins = sel.selectAll('path.violin').data(Lib.identity);
@@ -149,66 +147,61 @@ module.exports = function plot(gd, plotinfo, cd, violinLayer) {
149147
d.pathLength = d.path.getTotalLength() / (hasBothSides ? 2 : 1);
150148
});
151149

152-
if(hasBox) {
153-
var boxWidth = trace.box.width;
154-
var boxLineWidth = trace.box.line.width;
155-
var bdPosScaled;
156-
var bPosPxOffset;
150+
var boxAttrs = trace.box || {};
151+
var boxWidth = boxAttrs.width;
152+
var boxLineWidth = (boxAttrs.line || {}).width;
153+
var bdPosScaled;
154+
var bPosPxOffset;
155+
156+
if(hasBothSides) {
157+
bdPosScaled = bdPos * boxWidth;
158+
bPosPxOffset = 0;
159+
} else if(hasPositiveSide) {
160+
bdPosScaled = [0, bdPos * boxWidth / 2];
161+
bPosPxOffset = -boxLineWidth;
162+
} else {
163+
bdPosScaled = [bdPos * boxWidth / 2, 0];
164+
bPosPxOffset = boxLineWidth;
165+
}
157166

158-
if(hasBothSides) {
159-
bdPosScaled = bdPos * boxWidth;
160-
bPosPxOffset = 0;
161-
} else if(hasPositiveSide) {
162-
bdPosScaled = [0, bdPos * boxWidth / 2];
163-
bPosPxOffset = -boxLineWidth;
164-
} else {
165-
bdPosScaled = [bdPos * boxWidth / 2, 0];
166-
bPosPxOffset = boxLineWidth;
167-
}
167+
// inner box
168+
boxPlot.plotBoxAndWhiskers(sel, {pos: posAxis, val: valAxis}, trace, {
169+
bPos: bPos,
170+
bdPos: bdPosScaled,
171+
bPosPxOffset: bPosPxOffset
172+
});
168173

169-
boxPlot.plotBoxAndWhiskers(sel, {pos: posAxis, val: valAxis}, trace, {
170-
bPos: bPos,
171-
bdPos: bdPosScaled,
172-
bPosPxOffset: bPosPxOffset
173-
});
174-
175-
// if both box and meanline are visible, show mean line inside box
176-
if(hasMeanLine) {
177-
boxPlot.plotBoxMean(sel, {pos: posAxis, val: valAxis}, trace, {
178-
bPos: bPos,
179-
bdPos: bdPosScaled,
180-
bPosPxOffset: bPosPxOffset
181-
});
182-
}
183-
}
184-
else {
185-
if(hasMeanLine) {
186-
var meanPaths = sel.selectAll('path.mean').data(Lib.identity);
187-
188-
meanPaths.enter().append('path')
189-
.attr('class', 'mean')
190-
.style({
191-
fill: 'none',
192-
'vector-effect': 'non-scaling-stroke'
193-
});
194-
195-
meanPaths.exit().remove();
196-
197-
meanPaths.each(function(d) {
198-
var v = valAxis.c2p(d.mean, true);
199-
var p = helpers.getPositionOnKdePath(d, trace, v);
200-
201-
d3.select(this).attr('d',
202-
trace.orientation === 'h' ?
203-
'M' + v + ',' + p[0] + 'V' + p[1] :
204-
'M' + p[0] + ',' + v + 'H' + p[1]
205-
);
206-
});
207-
}
208-
}
174+
// meanline insider box
175+
boxPlot.plotBoxMean(sel, {pos: posAxis, val: valAxis}, trace, {
176+
bPos: bPos,
177+
bdPos: bdPosScaled,
178+
bPosPxOffset: bPosPxOffset
179+
});
209180

210-
if(trace.points) {
211-
boxPlot.plotPoints(sel, {x: xa, y: ya}, trace, t);
181+
var fn;
182+
if(!(trace.box || {}).visible && (trace.meanline || {}).visible) {
183+
fn = Lib.identity;
212184
}
185+
186+
// N.B. use different class name than boxPlot.plotBoxMean,
187+
// to avoid selectAll conflict
188+
var meanPaths = sel.selectAll('path.meanline').data(fn || []);
189+
meanPaths.enter().append('path')
190+
.attr('class', 'meanline')
191+
.style('fill', 'none')
192+
.style('vector-effect', 'non-scaling-stroke');
193+
meanPaths.exit().remove();
194+
meanPaths.each(function(d) {
195+
var v = valAxis.c2p(d.mean, true);
196+
var p = helpers.getPositionOnKdePath(d, trace, v);
197+
198+
d3.select(this).attr('d',
199+
trace.orientation === 'h' ?
200+
'M' + v + ',' + p[0] + 'V' + p[1] :
201+
'M' + p[0] + ',' + v + 'H' + p[1]
202+
);
203+
});
204+
205+
boxPlot.plotPoints(sel, {x: xa, y: ya}, trace, t);
213206
});
214207
};

src/traces/violin/style.js

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,17 @@ module.exports = function style(gd, cd) {
3535
.call(Color.stroke, boxLine.color)
3636
.call(Color.fill, box.fillcolor);
3737

38+
var meanLineStyle = {
39+
'stroke-width': meanLineWidth + 'px',
40+
'stroke-dasharray': (2 * meanLineWidth) + 'px,' + meanLineWidth + 'px'
41+
};
42+
3843
sel.selectAll('path.mean')
39-
.style({
40-
'stroke-width': meanLineWidth + 'px',
41-
'stroke-dasharray': (2 * meanLineWidth) + 'px,' + meanLineWidth + 'px'
42-
})
44+
.style(meanLineStyle)
45+
.call(Color.stroke, meanline.color);
46+
47+
sel.selectAll('path.meanline')
48+
.style(meanLineStyle)
4349
.call(Color.stroke, meanline.color);
4450

4551
stylePoints(sel, trace, gd);

test/jasmine/tests/violin_test.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,3 +493,61 @@ describe('Test violin hover:', function() {
493493
});
494494
});
495495
});
496+
497+
describe('Test violin restyle:', function() {
498+
var gd;
499+
500+
beforeEach(function() {
501+
gd = createGraphDiv();
502+
});
503+
504+
afterEach(destroyGraphDiv);
505+
506+
it('should be able to add/remove innner parts', function(done) {
507+
var fig = Lib.extendDeep({}, require('@mocks/violin_old-faithful.json'));
508+
// start with just 1 violin
509+
delete fig.data[0].meanline;
510+
delete fig.data[0].points;
511+
512+
function _assertOne(msg, exp, trace3, k, query) {
513+
expect(trace3.selectAll(query).size())
514+
.toBe(exp[k] || 0, k + ' - ' + msg);
515+
}
516+
517+
function _assert(msg, exp) {
518+
var trace3 = d3.select(gd).select('.violinlayer > .trace');
519+
_assertOne(msg, exp, trace3, 'violinCnt', 'path.violin');
520+
_assertOne(msg, exp, trace3, 'boxCnt', 'path.box');
521+
_assertOne(msg, exp, trace3, 'meanlineInBoxCnt', 'path.mean');
522+
_assertOne(msg, exp, trace3, 'meanlineOutOfBoxCnt', 'path.meanline');
523+
_assertOne(msg, exp, trace3, 'ptsCnt', 'path.point');
524+
}
525+
526+
Plotly.plot(gd, fig)
527+
.then(function() {
528+
_assert('base', {violinCnt: 1});
529+
})
530+
.then(function() { return Plotly.restyle(gd, 'box.visible', true); })
531+
.then(function() {
532+
_assert('with inner box', {violinCnt: 1, boxCnt: 1});
533+
})
534+
.then(function() { return Plotly.restyle(gd, 'meanline.visible', true); })
535+
.then(function() {
536+
_assert('with inner box & meanline', {violinCnt: 1, boxCnt: 1, meanlineInBoxCnt: 1});
537+
})
538+
.then(function() { return Plotly.restyle(gd, 'box.visible', false); })
539+
.then(function() {
540+
_assert('with meanline', {violinCnt: 1, meanlineOutOfBoxCnt: 1});
541+
})
542+
.then(function() { return Plotly.restyle(gd, 'points', 'all'); })
543+
.then(function() {
544+
_assert('with meanline & pts', {violinCnt: 1, meanlineOutOfBoxCnt: 1, ptsCnt: 272});
545+
})
546+
.then(function() { return Plotly.restyle(gd, 'meanline.visible', false); })
547+
.then(function() {
548+
_assert('with pts', {violinCnt: 1, ptsCnt: 272});
549+
})
550+
.catch(failTest)
551+
.then(done);
552+
});
553+
});

0 commit comments

Comments
 (0)