Skip to content

Commit 1eb453b

Browse files
committed
split box hover into onBoxes and onPoints routines
- to reuse in violin hover
1 parent a706f2a commit 1eb453b

File tree

2 files changed

+177
-159
lines changed

2 files changed

+177
-159
lines changed

src/traces/box/hover.js

+176-158
Original file line numberDiff line numberDiff line change
@@ -14,190 +14,208 @@ var Fx = require('../../components/fx');
1414
var Color = require('../../components/color');
1515
var fillHoverText = require('../scatter/fill_hover_text');
1616

17-
module.exports = function hoverPoints(pointData, xval, yval, hovermode) {
17+
function hoverPoints(pointData, xval, yval, hovermode) {
1818
var cd = pointData.cd;
19-
var xa = pointData.xa;
20-
var ya = pointData.ya;
21-
2219
var trace = cd[0].trace;
2320
var hoveron = trace.hoveron;
24-
var marker = trace.marker || {};
25-
26-
// output hover points components
2721
var closeBoxData = [];
2822
var closePtData;
29-
// x/y/effective distance functions
30-
var dx, dy, distfn;
31-
// orientation-specific fields
32-
var posLetter, valLetter, posAxis, valAxis;
33-
// calcdata item
34-
var di;
35-
// loop indices
36-
var i, j;
3723

3824
if(hoveron.indexOf('boxes') !== -1) {
39-
var t = cd[0].t;
40-
41-
// closest mode: handicap box plots a little relative to others
42-
// adjust inbox w.r.t. to calculate box size
43-
var boxDelta = (hovermode === 'closest') ? 2.5 * t.bdPos : t.bdPos;
44-
45-
if(trace.orientation === 'h') {
46-
dx = function(di) {
47-
return Fx.inbox(di.min - xval, di.max - xval);
48-
};
49-
dy = function(di) {
50-
var pos = di.pos + t.bPos - yval;
51-
return Fx.inbox(pos - boxDelta, pos + boxDelta);
52-
};
53-
posLetter = 'y';
54-
posAxis = ya;
55-
valLetter = 'x';
56-
valAxis = xa;
57-
} else {
58-
dx = function(di) {
59-
var pos = di.pos + t.bPos - xval;
60-
return Fx.inbox(pos - boxDelta, pos + boxDelta);
61-
};
62-
dy = function(di) {
63-
return Fx.inbox(di.min - yval, di.max - yval);
64-
};
65-
posLetter = 'x';
66-
posAxis = xa;
67-
valLetter = 'y';
68-
valAxis = ya;
69-
}
70-
71-
distfn = Fx.getDistanceFunction(hovermode, dx, dy);
72-
Fx.getClosest(cd, distfn, pointData);
25+
closeBoxData = closeBoxData.concat(hoverOnBoxes(pointData, xval, yval, hovermode));
26+
}
7327

74-
// skip the rest (for this trace) if we didn't find a close point
75-
// and create the item(s) in closedata for this point
76-
if(pointData.index !== false) {
77-
di = cd[pointData.index];
28+
if(hoveron.indexOf('points') !== -1) {
29+
closePtData = hoverOnPoints(pointData, xval, yval);
30+
}
7831

79-
var lc = trace.line.color;
80-
var mc = marker.color;
32+
// If there's a point in range and hoveron has points, show the best single point only.
33+
// If hoveron has boxes and there's no point in range (or hoveron doesn't have points), show the box stats.
34+
if(hovermode === 'closest') {
35+
if(closePtData) return [closePtData];
36+
return closeBoxData;
37+
}
8138

82-
if(Color.opacity(lc) && trace.line.width) pointData.color = lc;
83-
else if(Color.opacity(mc) && trace.boxpoints) pointData.color = mc;
84-
else pointData.color = trace.fillcolor;
39+
// Otherwise in compare mode, allow a point AND the box stats to be labeled
40+
// If there are multiple boxes in range (ie boxmode = 'overlay') we'll see stats for all of them.
41+
if(closePtData) {
42+
closeBoxData.push(closePtData);
43+
return closeBoxData;
44+
}
45+
return closeBoxData;
46+
}
8547

86-
pointData[posLetter + '0'] = posAxis.c2p(di.pos + t.bPos - t.bdPos, true);
87-
pointData[posLetter + '1'] = posAxis.c2p(di.pos + t.bPos + t.bdPos, true);
48+
function hoverOnBoxes(pointData, xval, yval, hovermode) {
49+
var cd = pointData.cd;
50+
var xa = pointData.xa;
51+
var ya = pointData.ya;
52+
var trace = cd[0].trace;
53+
var t = cd[0].t;
54+
var closeBoxData = [];
8855

89-
Axes.tickText(posAxis, posAxis.c2l(di.pos), 'hover').text;
90-
pointData[posLetter + 'LabelVal'] = di.pos;
56+
var pLetter, vLetter, pAxis, vAxis, vVal, pVal, dx, dy;
57+
58+
// closest mode: handicap box plots a little relative to others
59+
// adjust inbox w.r.t. to calculate box size
60+
var boxDelta = (hovermode === 'closest') ? 2.5 * t.bdPos : t.bdPos;
61+
var shiftPos = function(di) { return di.pos + t.bPos - pVal; };
62+
63+
var dPos = function(di) {
64+
var pos = shiftPos(di);
65+
return Fx.inbox(pos - boxDelta, pos + boxDelta);
66+
};
67+
68+
var dVal = function(di) {
69+
return Fx.inbox(di.min - vVal, di.max - vVal);
70+
};
71+
72+
if(trace.orientation === 'h') {
73+
vVal = xval;
74+
pVal = yval;
75+
dx = dVal;
76+
dy = dPos;
77+
pLetter = 'y';
78+
pAxis = ya;
79+
vLetter = 'x';
80+
vAxis = xa;
81+
} else {
82+
vVal = yval;
83+
pVal = xval;
84+
dx = dPos;
85+
dy = dVal;
86+
pLetter = 'x';
87+
pAxis = xa;
88+
vLetter = 'y';
89+
vAxis = ya;
90+
}
9191

92-
// box plots: each "point" gets many labels
93-
var usedVals = {};
94-
var attrs = ['med', 'min', 'q1', 'q3', 'max'];
95-
var prefixes = ['median', 'min', 'q1', 'q3', 'max'];
92+
var distfn = Fx.getDistanceFunction(hovermode, dx, dy);
93+
Fx.getClosest(cd, distfn, pointData);
9694

97-
if(trace.boxmean) {
98-
attrs.push('mean');
99-
prefixes.push(trace.boxmean === 'sd' ? 'mean ± σ' : 'mean');
100-
}
101-
if(trace.boxpoints) {
102-
attrs.push('lf', 'uf');
103-
prefixes.push('lower fence', 'upper fence');
104-
}
95+
// skip the rest (for this trace) if we didn't find a close point
96+
// and create the item(s) in closedata for this point
97+
if(pointData.index === false) return [];
10598

106-
for(i = 0; i < attrs.length; i++) {
107-
var attr = attrs[i];
99+
var di = cd[pointData.index];
100+
var lc = trace.line.color;
101+
var mc = (trace.marker || {}).color;
108102

109-
if(!(attr in di) || (di[attr] in usedVals)) continue;
110-
usedVals[di[attr]] = true;
103+
if(Color.opacity(lc) && trace.line.width) pointData.color = lc;
104+
else if(Color.opacity(mc) && trace.boxpoints) pointData.color = mc;
105+
else pointData.color = trace.fillcolor;
111106

112-
// copy out to a new object for each value to label
113-
var val = di[attr];
114-
var valPx = valAxis.c2p(val, true);
115-
var pointData2 = Lib.extendFlat({}, pointData);
107+
pointData[pLetter + '0'] = pAxis.c2p(di.pos + t.bPos - t.bdPos, true);
108+
pointData[pLetter + '1'] = pAxis.c2p(di.pos + t.bPos + t.bdPos, true);
116109

117-
pointData2[valLetter + '0'] = pointData2[valLetter + '1'] = valPx;
118-
pointData2[valLetter + 'LabelVal'] = val;
119-
pointData2[valLetter + 'Label'] = prefixes[i] + ': ' + Axes.hoverLabelText(valAxis, val);
110+
Axes.tickText(pAxis, pAxis.c2l(di.pos), 'hover').text;
111+
pointData[pLetter + 'LabelVal'] = di.pos;
120112

121-
if(attr === 'mean' && ('sd' in di) && trace.boxmean === 'sd') {
122-
pointData2[valLetter + 'err'] = di.sd;
123-
}
124-
// only keep name on the first item (median)
125-
pointData.name = '';
113+
// box plots: each "point" gets many labels
114+
var usedVals = {};
115+
var attrs = ['med', 'min', 'q1', 'q3', 'max'];
116+
var prefixes = ['median', 'min', 'q1', 'q3', 'max'];
126117

127-
closeBoxData.push(pointData2);
128-
}
129-
}
118+
if(trace.boxmean) {
119+
attrs.push('mean');
120+
prefixes.push(trace.boxmean === 'sd' ? 'mean ± σ' : 'mean');
121+
}
122+
if(trace.boxpoints) {
123+
attrs.push('lf', 'uf');
124+
prefixes.push('lower fence', 'upper fence');
130125
}
131126

132-
if(hoveron.indexOf('points') !== -1) {
133-
var xPx = xa.c2p(xval);
134-
var yPx = ya.c2p(yval);
135-
136-
dx = function(di) {
137-
var rad = Math.max(3, di.mrc || 0);
138-
return Math.max(Math.abs(xa.c2p(di.x) - xPx) - rad, 1 - 3 / rad);
139-
};
140-
dy = function(di) {
141-
var rad = Math.max(3, di.mrc || 0);
142-
return Math.max(Math.abs(ya.c2p(di.y) - yPx) - rad, 1 - 3 / rad);
143-
};
144-
distfn = Fx.quadrature(dx, dy);
145-
146-
// show one point per trace
147-
var ijClosest = false;
148-
var pt;
149-
150-
for(i = 0; i < cd.length; i++) {
151-
di = cd[i];
152-
153-
for(j = 0; j < (di.pts || []).length; j++) {
154-
pt = di.pts[j];
155-
156-
var newDistance = distfn(pt);
157-
if(newDistance <= pointData.distance) {
158-
pointData.distance = newDistance;
159-
ijClosest = [i, j];
160-
}
161-
}
162-
}
127+
for(var i = 0; i < attrs.length; i++) {
128+
var attr = attrs[i];
129+
130+
if(!(attr in di) || (di[attr] in usedVals)) continue;
131+
usedVals[di[attr]] = true;
132+
133+
// copy out to a new object for each value to label
134+
var val = di[attr];
135+
var valPx = vAxis.c2p(val, true);
136+
var pointData2 = Lib.extendFlat({}, pointData);
137+
138+
pointData2[vLetter + '0'] = pointData2[vLetter + '1'] = valPx;
139+
pointData2[vLetter + 'LabelVal'] = val;
140+
pointData2[vLetter + 'Label'] = prefixes[i] + ': ' + Axes.hoverLabelText(vAxis, val);
163141

164-
if(ijClosest) {
165-
di = cd[ijClosest[0]];
166-
pt = di.pts[ijClosest[1]];
167-
168-
var xc = xa.c2p(pt.x, true);
169-
var yc = ya.c2p(pt.y, true);
170-
var rad = pt.mrc || 1;
171-
172-
closePtData = Lib.extendFlat({}, pointData, {
173-
// corresponds to index in x/y input data array
174-
index: pt.i,
175-
color: marker.color,
176-
name: trace.name,
177-
x0: xc - rad,
178-
x1: xc + rad,
179-
xLabelVal: pt.x,
180-
y0: yc - rad,
181-
y1: yc + rad,
182-
yLabelVal: pt.y
183-
});
184-
fillHoverText(pt, trace, closePtData);
142+
if(attr === 'mean' && ('sd' in di) && trace.boxmean === 'sd') {
143+
pointData2[vLetter + 'err'] = di.sd;
185144
}
186-
}
145+
// only keep name on the first item (median)
146+
pointData.name = '';
187147

188-
// In closest mode, show only one point or stats for one box, and points have priority
189-
// If there's a point in range and hoveron has points, show the best single point only.
190-
// If hoveron has boxes and there's no point in range (or hoveron doesn't have points), show the box stats.
191-
if(hovermode === 'closest') {
192-
if(closePtData) return [closePtData];
193-
return closeBoxData;
148+
closeBoxData.push(pointData2);
194149
}
195150

196-
// Otherwise in compare mode, allow a point AND the box stats to be labeled
197-
// If there are multiple boxes in range (ie boxmode = 'overlay') we'll see stats for all of them.
198-
if(closePtData) {
199-
closeBoxData.push(closePtData);
200-
return closeBoxData;
201-
}
202151
return closeBoxData;
152+
}
153+
154+
function hoverOnPoints(pointData, xval, yval) {
155+
var cd = pointData.cd;
156+
var xa = pointData.xa;
157+
var ya = pointData.ya;
158+
var trace = cd[0].trace;
159+
var xPx = xa.c2p(xval);
160+
var yPx = ya.c2p(yval);
161+
var closePtData;
162+
163+
var dx = function(di) {
164+
var rad = Math.max(3, di.mrc || 0);
165+
return Math.max(Math.abs(xa.c2p(di.x) - xPx) - rad, 1 - 3 / rad);
166+
};
167+
var dy = function(di) {
168+
var rad = Math.max(3, di.mrc || 0);
169+
return Math.max(Math.abs(ya.c2p(di.y) - yPx) - rad, 1 - 3 / rad);
170+
};
171+
var distfn = Fx.quadrature(dx, dy);
172+
173+
// show one point per trace
174+
var ijClosest = false;
175+
var di, pt;
176+
177+
for(var i = 0; i < cd.length; i++) {
178+
di = cd[i];
179+
180+
for(var j = 0; j < (di.pts || []).length; j++) {
181+
pt = di.pts[j];
182+
183+
var newDistance = distfn(pt);
184+
if(newDistance <= pointData.distance) {
185+
pointData.distance = newDistance;
186+
ijClosest = [i, j];
187+
}
188+
}
189+
}
190+
191+
if(!ijClosest) return false;
192+
193+
di = cd[ijClosest[0]];
194+
pt = di.pts[ijClosest[1]];
195+
196+
var xc = xa.c2p(pt.x, true);
197+
var yc = ya.c2p(pt.y, true);
198+
var rad = pt.mrc || 1;
199+
200+
closePtData = Lib.extendFlat({}, pointData, {
201+
// corresponds to index in x/y input data array
202+
index: pt.i,
203+
color: (trace.marker || {}).color,
204+
name: trace.name,
205+
x0: xc - rad,
206+
x1: xc + rad,
207+
xLabelVal: pt.x,
208+
y0: yc - rad,
209+
y1: yc + rad,
210+
yLabelVal: pt.y
211+
});
212+
fillHoverText(pt, trace, closePtData);
213+
214+
return closePtData;
215+
}
216+
217+
module.exports = {
218+
hoverPoints: hoverPoints,
219+
hoverOnBoxes: hoverOnBoxes,
220+
hoverOnPoints: hoverOnPoints
203221
};

src/traces/box/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Box.calc = require('./calc');
1818
Box.setPositions = require('./set_positions');
1919
Box.plot = require('./plot').plot;
2020
Box.style = require('./style');
21-
Box.hoverPoints = require('./hover');
21+
Box.hoverPoints = require('./hover').hoverPoints;
2222
Box.selectPoints = require('./select');
2323

2424
Box.moduleType = 'trace';

0 commit comments

Comments
 (0)