Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit bc6bc02

Browse files
committed
implement violin hover
- with three flags: 'violins', 'points' (similar to box traces) and 'kde' which show the point on the kde line along with the line to crosses the hovered-on violin
1 parent dfa918f commit bc6bc02

File tree

6 files changed

+161
-12
lines changed

6 files changed

+161
-12
lines changed

src/traces/box/hover.js

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,23 +51,48 @@ function hoverOnBoxes(pointData, xval, yval, hovermode) {
5151
var ya = pointData.ya;
5252
var trace = cd[0].trace;
5353
var t = cd[0].t;
54+
var isViolin = trace.type === 'violin';
5455
var closeBoxData = [];
5556

5657
var pLetter, vLetter, pAxis, vAxis, vVal, pVal, dx, dy;
5758

5859
// closest mode: handicap box plots a little relative to others
5960
// adjust inbox w.r.t. to calculate box size
60-
var boxDelta = (hovermode === 'closest') ? 2.5 * t.bdPos : t.bdPos;
61+
var boxDelta = (hovermode === 'closest' && !isViolin) ? 2.5 * t.bdPos : t.bdPos;
6162
var shiftPos = function(di) { return di.pos + t.bPos - pVal; };
63+
var dPos;
64+
65+
if(isViolin && trace.side !== 'both') {
66+
if(trace.side === 'positive') {
67+
dPos = function(di) {
68+
var pos = shiftPos(di);
69+
return Fx.inbox(pos, pos + boxDelta);
70+
};
71+
}
72+
if(trace.side === 'negative') {
73+
dPos = function(di) {
74+
var pos = shiftPos(di);
75+
return Fx.inbox(pos - boxDelta, pos);
76+
};
77+
}
78+
} else {
79+
dPos = function(di) {
80+
var pos = shiftPos(di);
81+
return Fx.inbox(pos - boxDelta, pos + boxDelta);
82+
};
83+
}
6284

63-
var dPos = function(di) {
64-
var pos = shiftPos(di);
65-
return Fx.inbox(pos - boxDelta, pos + boxDelta);
66-
};
85+
var dVal;
6786

68-
var dVal = function(di) {
69-
return Fx.inbox(di.min - vVal, di.max - vVal);
70-
};
87+
if(isViolin) {
88+
dVal = function(di) {
89+
return Fx.inbox(di.span[0] - vVal, di.span[1] - vVal);
90+
};
91+
} else {
92+
dVal = function(di) {
93+
return Fx.inbox(di.min - vVal, di.max - vVal);
94+
};
95+
}
7196

7297
if(trace.orientation === 'h') {
7398
vVal = xval;
@@ -115,11 +140,11 @@ function hoverOnBoxes(pointData, xval, yval, hovermode) {
115140
var attrs = ['med', 'min', 'q1', 'q3', 'max'];
116141
var prefixes = ['median', 'min', 'q1', 'q3', 'max'];
117142

118-
if(trace.boxmean) {
143+
if(trace.boxmean || trace.showmeanline) {
119144
attrs.push('mean');
120145
prefixes.push(trace.boxmean === 'sd' ? 'mean ± σ' : 'mean');
121146
}
122-
if(trace.boxpoints) {
147+
if(trace.boxpoints || trace.points) {
123148
attrs.push('lf', 'uf');
124149
prefixes.push('lower fence', 'upper fence');
125150
}

src/traces/violin/attributes.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,6 @@ module.exports = {
181181
editType: 'style',
182182
description: ''
183183
},
184-
hoveron: boxAttrs.hoveron
185184

186185
side: {
187186
valType: 'enumerated',
@@ -196,4 +195,17 @@ module.exports = {
196195
'has `side` set to *positive* and the other to *negative*.'
197196
].join(' ')
198197
},
198+
199+
hoveron: {
200+
valType: 'flaglist',
201+
flags: ['violins', 'points', 'kde'],
202+
dflt: 'violins+points+kde',
203+
extras: ['all'],
204+
role: 'info',
205+
editType: 'style',
206+
description: [
207+
'Do the hover effects highlight individual violins',
208+
'or sample points or the kernel density estimate or any combination of them?'
209+
].join(' ')
210+
}
199211
};

src/traces/violin/helpers.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,10 @@ exports.getPositionOnKdePath = function(calcItem, trace, valuePx) {
6262
return [posOnPath0, posOnPath1];
6363
};
6464

65+
exports.getKdeValue = function(calcItem, trace, valueDist) {
66+
var vals = calcItem.pts.map(exports.extractVal);
67+
var kde = exports.makeKDE(calcItem, trace, vals);
68+
return kde(valueDist) / calcItem.posDensityScale;
69+
};
70+
6571
exports.extractVal = function(o) { return o.v; };

src/traces/violin/hover.js

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/**
2+
* Copyright 2012-2017, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var Lib = require('../../lib');
12+
var Axes = require('../../plots/cartesian/axes');
13+
var boxHoverPoints = require('../box/hover');
14+
var helpers = require('./helpers');
15+
16+
module.exports = function hoverPoints(pointData, xval, yval, hovermode, hoverLayer) {
17+
var cd = pointData.cd;
18+
var trace = cd[0].trace;
19+
var hoveron = trace.hoveron;
20+
var hasHoveronViolins = hoveron.indexOf('violins') !== -1;
21+
var hasHoveronKDE = hoveron.indexOf('kde') !== -1;
22+
var closeData = [];
23+
var closePtData;
24+
var violinLineAttrs;
25+
26+
if(hasHoveronViolins || hasHoveronKDE) {
27+
var closeBoxData = boxHoverPoints.hoverOnBoxes(pointData, xval, yval, hovermode);
28+
29+
if(hasHoveronViolins) {
30+
closeData = closeData.concat(closeBoxData);
31+
}
32+
33+
if(hasHoveronKDE && closeBoxData.length > 0) {
34+
var xa = pointData.xa;
35+
var ya = pointData.ya;
36+
var pLetter, vLetter, pAxis, vAxis, vVal;
37+
38+
if(trace.orientation === 'h') {
39+
vVal = xval;
40+
pLetter = 'y';
41+
pAxis = ya;
42+
vLetter = 'x';
43+
vAxis = xa;
44+
} else {
45+
vVal = yval;
46+
pLetter = 'x';
47+
pAxis = xa;
48+
vLetter = 'y';
49+
vAxis = ya;
50+
}
51+
52+
var di = cd[pointData.index];
53+
54+
if(vVal >= di.span[0] && vVal <= di.span[1]) {
55+
var kdePointData = Lib.extendFlat({}, pointData);
56+
var vValPx = vAxis.c2p(vVal, true);
57+
var kdeVal = helpers.getKdeValue(di, trace, vVal);
58+
var pOnPath = helpers.getPositionOnKdePath(di, trace, vValPx);
59+
var paOffset = pAxis._offset;
60+
var paLength = pAxis._length;
61+
62+
kdePointData[pLetter + '0'] = pOnPath[0];
63+
kdePointData[pLetter + '1'] = pOnPath[1];
64+
kdePointData[vLetter + '0'] = kdePointData[vLetter + '1'] = vValPx;
65+
kdePointData[vLetter + 'Label'] = vLetter + ': ' + Axes.hoverLabelText(vAxis, vVal) + ', kde: ' + kdeVal.toFixed(3);
66+
closeData.push(kdePointData);
67+
68+
violinLineAttrs = {stroke: pointData.color};
69+
violinLineAttrs[pLetter + '1'] = Lib.constrain(paOffset + pOnPath[0], paOffset, paOffset + paLength);
70+
violinLineAttrs[pLetter + '2'] = Lib.constrain(paOffset + pOnPath[1], paOffset, paOffset + paLength);
71+
violinLineAttrs[vLetter + '1'] = violinLineAttrs[vLetter + '2'] = vAxis._offset + vValPx;
72+
}
73+
}
74+
}
75+
76+
if(hoveron.indexOf('points') !== -1) {
77+
closePtData = boxHoverPoints.hoverOnPoints(pointData, xval, yval);
78+
}
79+
80+
// update violin line (if any)
81+
var violinLine = hoverLayer.selectAll('.violinline-' + trace.uid)
82+
.data(violinLineAttrs ? [0] : []);
83+
violinLine.enter().append('line')
84+
.classed('violinline-' + trace.uid, true)
85+
.attr('stroke-width', 1.5);
86+
violinLine.exit().remove();
87+
violinLine.attr(violinLineAttrs);
88+
89+
// same combine logic as box hoverPoints
90+
if(hovermode === 'closest') {
91+
if(closePtData) return [closePtData];
92+
return closeData;
93+
}
94+
if(closePtData) {
95+
closeData.push(closePtData);
96+
return closeData;
97+
}
98+
return closeData;
99+
};

src/traces/violin/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ module.exports = {
1717
setPositions: require('./set_positions'),
1818
plot: require('./plot'),
1919
style: require('./style'),
20-
hoverPoints: require('../box/hover'),
20+
hoverPoints: require('./hover'),
2121
selectPoints: require('../box/select'),
2222

2323
moduleType: 'trace',

src/traces/violin/plot.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,13 @@ module.exports = function plot(gd, plotinfo, cd) {
127127
}
128128
pathSel.attr('d', path);
129129

130+
// save a few things used in getPositionOnKdePath, getKdeValue
131+
// on hover and for showmeanline
132+
d.posCenterPx = posCenterPx;
133+
d.posDensityScale = scale * bdPos;
134+
d.path = pathSel.node();
135+
d.pathLength = d.path.getTotalLength() / (hasBothSides ? 2 : 1);
136+
130137
});
131138

132139
if(trace.showinnerbox) {

0 commit comments

Comments
 (0)