Skip to content

Commit

Permalink
Merge pull request #5512 from plotly/external-hover-2
Browse files Browse the repository at this point in the history
Output bounding box to hover event data
  • Loading branch information
archmoj committed Aug 26, 2021
2 parents 164d760 + 6ba88db commit ed63090
Show file tree
Hide file tree
Showing 22 changed files with 270 additions and 127 deletions.
1 change: 1 addition & 0 deletions draftlogs/5512_add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Provide bounding box positions in hover event data [[#5512](https://github.com/plotly/plotly.js/pull/5512)]
78 changes: 74 additions & 4 deletions src/components/fx/hover.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,48 @@ exports.loneHover = function loneHover(hoverItems, opts) {
hoverItems = [hoverItems];
}

var gd = opts.gd;
var gTop = getTopOffset(gd);
var gLeft = getLeftOffset(gd);

var pointsData = hoverItems.map(function(hoverItem) {
var _x0 = hoverItem._x0 || hoverItem.x0 || hoverItem.x || 0;
var _x1 = hoverItem._x1 || hoverItem.x1 || hoverItem.x || 0;
var _y0 = hoverItem._y0 || hoverItem.y0 || hoverItem.y || 0;
var _y1 = hoverItem._y1 || hoverItem.y1 || hoverItem.y || 0;

var eventData = hoverItem.eventData;
if(eventData) {
var x0 = Math.min(_x0, _x1);
var x1 = Math.max(_x0, _x1);
var y0 = Math.min(_y0, _y1);
var y1 = Math.max(_y0, _y1);

var trace = hoverItem.trace;
if(Registry.traceIs(trace, 'gl3d')) {
var container = gd._fullLayout[trace.scene]._scene.container;
var dx = container.offsetLeft;
var dy = container.offsetTop;
x0 += dx;
x1 += dx;
y0 += dy;
y1 += dy;
} // TODO: handle heatmapgl

eventData.bbox = {
x0: x0 + gLeft,
x1: x1 + gLeft,
y0: y0 + gTop,
y1: y1 + gTop
};

if(opts.inOut_bbox) {
opts.inOut_bbox.push(eventData.bbox);
}
} else {
eventData = false;
}

return {
color: hoverItem.color || Color.defaultLine,
x0: hoverItem.x0 || hoverItem.x || 0,
Expand Down Expand Up @@ -158,8 +199,9 @@ exports.loneHover = function loneHover(hoverItems, opts) {
index: 0,

hovertemplate: hoverItem.hovertemplate || false,
eventData: hoverItem.eventData || false,
hovertemplateLabels: hoverItem.hovertemplateLabels || false,

eventData: eventData
};
});

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

var hoverLabel = createHoverText(pointsData, fullOpts, opts.gd);
var hoverLabel = createHoverText(pointsData, fullOpts, gd);

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

var scaleX = opts.gd._fullLayout._invScaleX;
var scaleY = opts.gd._fullLayout._invScaleY;
var scaleX = gd._fullLayout._invScaleX;
var scaleY = gd._fullLayout._invScaleY;
alignHoverText(hoverLabel, fullOpts.rotateLabels, scaleX, scaleY);

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

var gTop = getTopOffset(gd);
var gLeft = getLeftOffset(gd);

// pull out just the data that's useful to
// other people and send it to the event
for(itemnum = 0; itemnum < hoverData.length; itemnum++) {
Expand All @@ -746,6 +791,25 @@ function _hover(gd, evt, subplot, noHoverEvent) {
pt.hovertemplate = ht || pt.trace.hovertemplate || false;
}

if(pt.xa && pt.ya) {
var _x0 = pt.x0 + pt.xa._offset;
var _x1 = pt.x1 + pt.xa._offset;
var _y0 = pt.y0 + pt.ya._offset;
var _y1 = pt.y1 + pt.ya._offset;

var x0 = Math.min(_x0, _x1);
var x1 = Math.max(_x0, _x1);
var y0 = Math.min(_y0, _y1);
var y1 = Math.max(_y0, _y1);

eventData.bbox = {
x0: x0 + gLeft,
x1: x1 + gLeft,
y0: y0 + gTop,
y1: y1 + gTop
};
}

pt.eventData = [eventData];
newhoverdata.push(eventData);
}
Expand Down Expand Up @@ -2033,3 +2097,9 @@ function getCoord(axLetter, winningPoint, fullLayout) {

return val;
}

// Top/left hover offsets relative to graph div. As long as hover content is
// a sibling of the graph div, it will be positioned correctly relative to
// the offset parent, whatever that may be.
function getTopOffset(gd) { return gd.offsetTop + gd.clientTop; }
function getLeftOffset(gd) { return gd.offsetLeft + gd.clientLeft; }
6 changes: 5 additions & 1 deletion src/plots/gl3d/scene.js
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ proto.render = function() {
var eventData = {points: [pointData]};

if(scene.fullSceneLayout.hovermode) {
var bbox = [];
Fx.loneHover({
trace: traceNow,
x: (0.5 + 0.5 * pdata[0] / pdata[3]) * width,
Expand All @@ -442,8 +443,11 @@ proto.render = function() {
eventData: [pointData]
}, {
container: svgContainer,
gd: gd
gd: gd,
inOut_bbox: bbox
});

pointData.bbox = bbox[0];
}

if(selection.buttons && selection.distance < 5) {
Expand Down
6 changes: 6 additions & 0 deletions src/traces/icicle/draw_descendants.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ module.exports = function drawDescendants(gd, cd, entry, slices, opts) {
}

updateSlices.each(function(pt) {
// for bbox
pt._x0 = viewX(pt.x0);
pt._x1 = viewX(pt.x1);
pt._y0 = viewY(pt.y0);
pt._y1 = viewY(pt.y1);

pt._hoverX = viewX(pt.x1 - trace.tiling.pad),
pt._hoverY = hasBottom ?
viewY(pt.y1 - trace.tiling.pad / 2) :
Expand Down
1 change: 1 addition & 0 deletions src/traces/pie/event_data.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ module.exports = function eventData(pt, trace) {
value: pt.v,
percent: pt.percent,
text: pt.text,
bbox: pt.bbox,

// pt.v (and pt.i below) for backward compatibility
v: pt.v
Expand Down
12 changes: 10 additions & 2 deletions src/traces/pie/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -344,9 +344,10 @@ function plotTextLines(slices, trace) {

function attachFxHandlers(sliceTop, gd, cd) {
var cd0 = cd[0];
var trace = cd0.trace;
var cx = cd0.cx;
var cy = cd0.cy;
var trace = cd0.trace;
var isFunnelArea = trace.type === 'funnelarea';

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

var bbox = [];
Fx.loneHover({
trace: trace,
x0: hoverCenterX - rInscribed * cd0.r,
x1: hoverCenterX + rInscribed * cd0.r,
y: hoverCenterY,
_x0: isFunnelArea ? cx + pt.TL[0] : hoverCenterX - rInscribed * cd0.r,
_x1: isFunnelArea ? cx + pt.TR[0] : hoverCenterX + rInscribed * cd0.r,
_y0: isFunnelArea ? cy + pt.TL[1] : hoverCenterY - rInscribed * cd0.r,
_y1: isFunnelArea ? cy + pt.BL[1] : hoverCenterY + rInscribed * cd0.r,
text: text.join('<br>'),
name: (trace2.hovertemplate || hoverinfo.indexOf('name') !== -1) ? trace2.name : undefined,
idealAlign: pt.pxmid[0] < 0 ? 'left' : 'right',
Expand All @@ -425,8 +431,10 @@ function attachFxHandlers(sliceTop, gd, cd) {
}, {
container: fullLayout2._hoverlayer.node(),
outerContainer: fullLayout2._paper.node(),
gd: gd
gd: gd,
inOut_bbox: bbox
});
pt.bbox = bbox[0];

trace._hasHoverLabel = true;
}
Expand Down
17 changes: 14 additions & 3 deletions src/traces/sunburst/fx.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ module.exports = function attachFxHandlers(sliceTop, entry, gd, cd, opts) {
var hoverinfo = Fx.castHoverinfo(traceNow, fullLayoutNow, ptNumber);
var separators = fullLayoutNow.separators;

var eventData;

if(hovertemplate || (hoverinfo && hoverinfo !== 'none' && hoverinfo !== 'skip')) {
var hoverCenterX;
var hoverCenterY;
Expand Down Expand Up @@ -125,9 +127,15 @@ module.exports = function attachFxHandlers(sliceTop, entry, gd, cd, opts) {
if(Lib.isValidTextValue(tx)) thisText.push(tx);
}

eventData = [makeEventData(pt, traceNow, opts.eventDataKeys)];

var hoverItems = {
trace: traceNow,
y: hoverCenterY,
_x0: pt._x0,
_x1: pt._x1,
_y0: pt._y0,
_y1: pt._y1,
text: thisText.join('<br>'),
name: (hovertemplate || hasFlag('name')) ? traceNow.name : undefined,
color: _cast('hoverlabel.bgcolor') || cdi.color,
Expand All @@ -139,7 +147,7 @@ module.exports = function attachFxHandlers(sliceTop, entry, gd, cd, opts) {
textAlign: _cast('hoverlabel.align'),
hovertemplate: hovertemplate,
hovertemplateLabels: hoverPt,
eventData: [makeEventData(pt, traceNow, opts.eventDataKeys)]
eventData: eventData
};

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

var bbox = [];
Fx.loneHover(hoverItems, {
container: fullLayoutNow._hoverlayer.node(),
outerContainer: fullLayoutNow._paper.node(),
gd: gd
gd: gd,
inOut_bbox: bbox
});
eventData[0].bbox = bbox[0];

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

trace._hasHoverEvent = true;
gd.emit('plotly_hover', {
points: [makeEventData(pt, traceNow, opts.eventDataKeys)],
points: eventData || [makeEventData(pt, traceNow, opts.eventDataKeys)],
event: d3.event
});
};
Expand Down
6 changes: 6 additions & 0 deletions src/traces/treemap/draw_ancestors.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ module.exports = function drawAncestors(gd, cd, entry, slices, opts) {
}

updateSlices.each(function(pt) {
// for bbox
pt._x0 = viewX(pt.x0);
pt._x1 = viewX(pt.x1);
pt._y0 = viewY(pt.y0);
pt._y1 = viewY(pt.y1);

pt._hoverX = viewX(pt.x1 - Math.min(width, height) / 2);
pt._hoverY = viewY(pt.y1 - height / 2);

Expand Down
6 changes: 6 additions & 0 deletions src/traces/treemap/draw_descendants.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ module.exports = function drawDescendants(gd, cd, entry, slices, opts) {
updateSlices.each(function(pt) {
var isHeader = helpers.isHeader(pt, trace);

// for bbox
pt._x0 = viewX(pt.x0);
pt._x1 = viewX(pt.x1);
pt._y0 = viewY(pt.y0);
pt._y1 = viewY(pt.y1);

pt._hoverX = viewX(pt.x1 - trace.marker.pad.r),
pt._hoverY = hasBottom ?
viewY(pt.y1 - trace.marker.pad.b / 2) :
Expand Down
11 changes: 10 additions & 1 deletion test/jasmine/tests/cartesian_interact_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2422,6 +2422,12 @@ describe('Cartesian plots with css transforms', function() {
}
};

var bbox = {
one: { x0: 20, x1: 180, y0: 273.33, y1: 273.33 },
two: { x0: 220, x1: 380, y0: 146.67, y1: 146.67 },
three: { x0: 420, x1: 580, y0: 20, y1: 20 }
};

[{
transform: 'scaleX(0.5)',
hovered: 1,
Expand All @@ -2436,12 +2442,14 @@ describe('Cartesian plots with css transforms', function() {
selected: {numPoints: 3, selectedLabels: ['one', 'two', 'three']}
}].forEach(function(t) {
var transform = t.transform;

it('hover behaves correctly after css transform: ' + transform, function(done) {
var _bboxRecordings = {};

function _hoverAndAssertEventOccurred(point, label) {
return _hover(point)
.then(function() {
expect(eventRecordings[label]).toBe(t.hovered);
expect(_bboxRecordings[label]).toEqual(bbox[label]);
})
.then(function() {
_unhover(point);
Expand All @@ -2454,6 +2462,7 @@ describe('Cartesian plots with css transforms', function() {

gd.on('plotly_hover', function(d) {
eventRecordings[d.points[0].x] = 1;
_bboxRecordings[d.points[0].x] = d.points[0].bbox;
});
})
.then(function() {_hoverAndAssertEventOccurred(points[0], xLabels[0]);})
Expand Down
Loading

0 comments on commit ed63090

Please sign in to comment.