Skip to content

Commit ed63090

Browse files
authored
Merge pull request #5512 from plotly/external-hover-2
Output bounding box to hover event data
2 parents 164d760 + 6ba88db commit ed63090

22 files changed

+270
-127
lines changed

draftlogs/5512_add.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Provide bounding box positions in hover event data [[#5512](https://github.com/plotly/plotly.js/pull/5512)]

src/components/fx/hover.js

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,48 @@ exports.loneHover = function loneHover(hoverItems, opts) {
126126
hoverItems = [hoverItems];
127127
}
128128

129+
var gd = opts.gd;
130+
var gTop = getTopOffset(gd);
131+
var gLeft = getLeftOffset(gd);
132+
129133
var pointsData = hoverItems.map(function(hoverItem) {
134+
var _x0 = hoverItem._x0 || hoverItem.x0 || hoverItem.x || 0;
135+
var _x1 = hoverItem._x1 || hoverItem.x1 || hoverItem.x || 0;
136+
var _y0 = hoverItem._y0 || hoverItem.y0 || hoverItem.y || 0;
137+
var _y1 = hoverItem._y1 || hoverItem.y1 || hoverItem.y || 0;
138+
139+
var eventData = hoverItem.eventData;
140+
if(eventData) {
141+
var x0 = Math.min(_x0, _x1);
142+
var x1 = Math.max(_x0, _x1);
143+
var y0 = Math.min(_y0, _y1);
144+
var y1 = Math.max(_y0, _y1);
145+
146+
var trace = hoverItem.trace;
147+
if(Registry.traceIs(trace, 'gl3d')) {
148+
var container = gd._fullLayout[trace.scene]._scene.container;
149+
var dx = container.offsetLeft;
150+
var dy = container.offsetTop;
151+
x0 += dx;
152+
x1 += dx;
153+
y0 += dy;
154+
y1 += dy;
155+
} // TODO: handle heatmapgl
156+
157+
eventData.bbox = {
158+
x0: x0 + gLeft,
159+
x1: x1 + gLeft,
160+
y0: y0 + gTop,
161+
y1: y1 + gTop
162+
};
163+
164+
if(opts.inOut_bbox) {
165+
opts.inOut_bbox.push(eventData.bbox);
166+
}
167+
} else {
168+
eventData = false;
169+
}
170+
130171
return {
131172
color: hoverItem.color || Color.defaultLine,
132173
x0: hoverItem.x0 || hoverItem.x || 0,
@@ -158,8 +199,9 @@ exports.loneHover = function loneHover(hoverItems, opts) {
158199
index: 0,
159200

160201
hovertemplate: hoverItem.hovertemplate || false,
161-
eventData: hoverItem.eventData || false,
162202
hovertemplateLabels: hoverItem.hovertemplateLabels || false,
203+
204+
eventData: eventData
163205
};
164206
});
165207

@@ -174,7 +216,7 @@ exports.loneHover = function loneHover(hoverItems, opts) {
174216
outerContainer: outerContainer3
175217
};
176218

177-
var hoverLabel = createHoverText(pointsData, fullOpts, opts.gd);
219+
var hoverLabel = createHoverText(pointsData, fullOpts, gd);
178220

179221
// Fix vertical overlap
180222
var tooltipSpacing = 5;
@@ -199,8 +241,8 @@ exports.loneHover = function loneHover(hoverItems, opts) {
199241
d.offset -= anchor;
200242
});
201243

202-
var scaleX = opts.gd._fullLayout._invScaleX;
203-
var scaleY = opts.gd._fullLayout._invScaleY;
244+
var scaleX = gd._fullLayout._invScaleX;
245+
var scaleY = gd._fullLayout._invScaleY;
204246
alignHoverText(hoverLabel, fullOpts.rotateLabels, scaleX, scaleY);
205247

206248
return multiHover ? hoverLabel : hoverLabel.node();
@@ -732,6 +774,9 @@ function _hover(gd, evt, subplot, noHoverEvent) {
732774
var oldhoverdata = gd._hoverdata;
733775
var newhoverdata = [];
734776

777+
var gTop = getTopOffset(gd);
778+
var gLeft = getLeftOffset(gd);
779+
735780
// pull out just the data that's useful to
736781
// other people and send it to the event
737782
for(itemnum = 0; itemnum < hoverData.length; itemnum++) {
@@ -746,6 +791,25 @@ function _hover(gd, evt, subplot, noHoverEvent) {
746791
pt.hovertemplate = ht || pt.trace.hovertemplate || false;
747792
}
748793

794+
if(pt.xa && pt.ya) {
795+
var _x0 = pt.x0 + pt.xa._offset;
796+
var _x1 = pt.x1 + pt.xa._offset;
797+
var _y0 = pt.y0 + pt.ya._offset;
798+
var _y1 = pt.y1 + pt.ya._offset;
799+
800+
var x0 = Math.min(_x0, _x1);
801+
var x1 = Math.max(_x0, _x1);
802+
var y0 = Math.min(_y0, _y1);
803+
var y1 = Math.max(_y0, _y1);
804+
805+
eventData.bbox = {
806+
x0: x0 + gLeft,
807+
x1: x1 + gLeft,
808+
y0: y0 + gTop,
809+
y1: y1 + gTop
810+
};
811+
}
812+
749813
pt.eventData = [eventData];
750814
newhoverdata.push(eventData);
751815
}
@@ -2033,3 +2097,9 @@ function getCoord(axLetter, winningPoint, fullLayout) {
20332097

20342098
return val;
20352099
}
2100+
2101+
// Top/left hover offsets relative to graph div. As long as hover content is
2102+
// a sibling of the graph div, it will be positioned correctly relative to
2103+
// the offset parent, whatever that may be.
2104+
function getTopOffset(gd) { return gd.offsetTop + gd.clientTop; }
2105+
function getLeftOffset(gd) { return gd.offsetLeft + gd.clientLeft; }

src/plots/gl3d/scene.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,7 @@ proto.render = function() {
421421
var eventData = {points: [pointData]};
422422

423423
if(scene.fullSceneLayout.hovermode) {
424+
var bbox = [];
424425
Fx.loneHover({
425426
trace: traceNow,
426427
x: (0.5 + 0.5 * pdata[0] / pdata[3]) * width,
@@ -442,8 +443,11 @@ proto.render = function() {
442443
eventData: [pointData]
443444
}, {
444445
container: svgContainer,
445-
gd: gd
446+
gd: gd,
447+
inOut_bbox: bbox
446448
});
449+
450+
pointData.bbox = bbox[0];
447451
}
448452

449453
if(selection.buttons && selection.distance < 5) {

src/traces/icicle/draw_descendants.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,12 @@ module.exports = function drawDescendants(gd, cd, entry, slices, opts) {
116116
}
117117

118118
updateSlices.each(function(pt) {
119+
// for bbox
120+
pt._x0 = viewX(pt.x0);
121+
pt._x1 = viewX(pt.x1);
122+
pt._y0 = viewY(pt.y0);
123+
pt._y1 = viewY(pt.y1);
124+
119125
pt._hoverX = viewX(pt.x1 - trace.tiling.pad),
120126
pt._hoverY = hasBottom ?
121127
viewY(pt.y1 - trace.tiling.pad / 2) :

src/traces/pie/event_data.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ module.exports = function eventData(pt, trace) {
1616
value: pt.v,
1717
percent: pt.percent,
1818
text: pt.text,
19+
bbox: pt.bbox,
1920

2021
// pt.v (and pt.i below) for backward compatibility
2122
v: pt.v

src/traces/pie/plot.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -344,9 +344,10 @@ function plotTextLines(slices, trace) {
344344

345345
function attachFxHandlers(sliceTop, gd, cd) {
346346
var cd0 = cd[0];
347-
var trace = cd0.trace;
348347
var cx = cd0.cx;
349348
var cy = cd0.cy;
349+
var trace = cd0.trace;
350+
var isFunnelArea = trace.type === 'funnelarea';
350351

351352
// hover state vars
352353
// have we drawn a hover label, so it should be cleared later
@@ -404,11 +405,16 @@ function attachFxHandlers(sliceTop, gd, cd) {
404405
var hoverLabel = trace2.hoverlabel;
405406
var hoverFont = hoverLabel.font;
406407

408+
var bbox = [];
407409
Fx.loneHover({
408410
trace: trace,
409411
x0: hoverCenterX - rInscribed * cd0.r,
410412
x1: hoverCenterX + rInscribed * cd0.r,
411413
y: hoverCenterY,
414+
_x0: isFunnelArea ? cx + pt.TL[0] : hoverCenterX - rInscribed * cd0.r,
415+
_x1: isFunnelArea ? cx + pt.TR[0] : hoverCenterX + rInscribed * cd0.r,
416+
_y0: isFunnelArea ? cy + pt.TL[1] : hoverCenterY - rInscribed * cd0.r,
417+
_y1: isFunnelArea ? cy + pt.BL[1] : hoverCenterY + rInscribed * cd0.r,
412418
text: text.join('<br>'),
413419
name: (trace2.hovertemplate || hoverinfo.indexOf('name') !== -1) ? trace2.name : undefined,
414420
idealAlign: pt.pxmid[0] < 0 ? 'left' : 'right',
@@ -425,8 +431,10 @@ function attachFxHandlers(sliceTop, gd, cd) {
425431
}, {
426432
container: fullLayout2._hoverlayer.node(),
427433
outerContainer: fullLayout2._paper.node(),
428-
gd: gd
434+
gd: gd,
435+
inOut_bbox: bbox
429436
});
437+
pt.bbox = bbox[0];
430438

431439
trace._hasHoverLabel = true;
432440
}

src/traces/sunburst/fx.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ module.exports = function attachFxHandlers(sliceTop, entry, gd, cd, opts) {
5252
var hoverinfo = Fx.castHoverinfo(traceNow, fullLayoutNow, ptNumber);
5353
var separators = fullLayoutNow.separators;
5454

55+
var eventData;
56+
5557
if(hovertemplate || (hoverinfo && hoverinfo !== 'none' && hoverinfo !== 'skip')) {
5658
var hoverCenterX;
5759
var hoverCenterY;
@@ -125,9 +127,15 @@ module.exports = function attachFxHandlers(sliceTop, entry, gd, cd, opts) {
125127
if(Lib.isValidTextValue(tx)) thisText.push(tx);
126128
}
127129

130+
eventData = [makeEventData(pt, traceNow, opts.eventDataKeys)];
131+
128132
var hoverItems = {
129133
trace: traceNow,
130134
y: hoverCenterY,
135+
_x0: pt._x0,
136+
_x1: pt._x1,
137+
_y0: pt._y0,
138+
_y1: pt._y1,
131139
text: thisText.join('<br>'),
132140
name: (hovertemplate || hasFlag('name')) ? traceNow.name : undefined,
133141
color: _cast('hoverlabel.bgcolor') || cdi.color,
@@ -139,7 +147,7 @@ module.exports = function attachFxHandlers(sliceTop, entry, gd, cd, opts) {
139147
textAlign: _cast('hoverlabel.align'),
140148
hovertemplate: hovertemplate,
141149
hovertemplateLabels: hoverPt,
142-
eventData: [makeEventData(pt, traceNow, opts.eventDataKeys)]
150+
eventData: eventData
143151
};
144152

145153
if(isSunburst) {
@@ -152,11 +160,14 @@ module.exports = function attachFxHandlers(sliceTop, entry, gd, cd, opts) {
152160
hoverItems.idealAlign = hoverCenterX < 0 ? 'left' : 'right';
153161
}
154162

163+
var bbox = [];
155164
Fx.loneHover(hoverItems, {
156165
container: fullLayoutNow._hoverlayer.node(),
157166
outerContainer: fullLayoutNow._paper.node(),
158-
gd: gd
167+
gd: gd,
168+
inOut_bbox: bbox
159169
});
170+
eventData[0].bbox = bbox[0];
160171

161172
trace._hasHoverLabel = true;
162173
}
@@ -170,7 +181,7 @@ module.exports = function attachFxHandlers(sliceTop, entry, gd, cd, opts) {
170181

171182
trace._hasHoverEvent = true;
172183
gd.emit('plotly_hover', {
173-
points: [makeEventData(pt, traceNow, opts.eventDataKeys)],
184+
points: eventData || [makeEventData(pt, traceNow, opts.eventDataKeys)],
174185
event: d3.event
175186
});
176187
};

src/traces/treemap/draw_ancestors.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ module.exports = function drawAncestors(gd, cd, entry, slices, opts) {
8989
}
9090

9191
updateSlices.each(function(pt) {
92+
// for bbox
93+
pt._x0 = viewX(pt.x0);
94+
pt._x1 = viewX(pt.x1);
95+
pt._y0 = viewY(pt.y0);
96+
pt._y1 = viewY(pt.y1);
97+
9298
pt._hoverX = viewX(pt.x1 - Math.min(width, height) / 2);
9399
pt._hoverY = viewY(pt.y1 - height / 2);
94100

src/traces/treemap/draw_descendants.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,12 @@ module.exports = function drawDescendants(gd, cd, entry, slices, opts) {
124124
updateSlices.each(function(pt) {
125125
var isHeader = helpers.isHeader(pt, trace);
126126

127+
// for bbox
128+
pt._x0 = viewX(pt.x0);
129+
pt._x1 = viewX(pt.x1);
130+
pt._y0 = viewY(pt.y0);
131+
pt._y1 = viewY(pt.y1);
132+
127133
pt._hoverX = viewX(pt.x1 - trace.marker.pad.r),
128134
pt._hoverY = hasBottom ?
129135
viewY(pt.y1 - trace.marker.pad.b / 2) :

test/jasmine/tests/cartesian_interact_test.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2422,6 +2422,12 @@ describe('Cartesian plots with css transforms', function() {
24222422
}
24232423
};
24242424

2425+
var bbox = {
2426+
one: { x0: 20, x1: 180, y0: 273.33, y1: 273.33 },
2427+
two: { x0: 220, x1: 380, y0: 146.67, y1: 146.67 },
2428+
three: { x0: 420, x1: 580, y0: 20, y1: 20 }
2429+
};
2430+
24252431
[{
24262432
transform: 'scaleX(0.5)',
24272433
hovered: 1,
@@ -2436,12 +2442,14 @@ describe('Cartesian plots with css transforms', function() {
24362442
selected: {numPoints: 3, selectedLabels: ['one', 'two', 'three']}
24372443
}].forEach(function(t) {
24382444
var transform = t.transform;
2439-
24402445
it('hover behaves correctly after css transform: ' + transform, function(done) {
2446+
var _bboxRecordings = {};
2447+
24412448
function _hoverAndAssertEventOccurred(point, label) {
24422449
return _hover(point)
24432450
.then(function() {
24442451
expect(eventRecordings[label]).toBe(t.hovered);
2452+
expect(_bboxRecordings[label]).toEqual(bbox[label]);
24452453
})
24462454
.then(function() {
24472455
_unhover(point);
@@ -2454,6 +2462,7 @@ describe('Cartesian plots with css transforms', function() {
24542462

24552463
gd.on('plotly_hover', function(d) {
24562464
eventRecordings[d.points[0].x] = 1;
2465+
_bboxRecordings[d.points[0].x] = d.points[0].bbox;
24572466
});
24582467
})
24592468
.then(function() {_hoverAndAssertEventOccurred(points[0], xLabels[0]);})

0 commit comments

Comments
 (0)