Skip to content

Commit c31fee7

Browse files
authored
Merge pull request #8052 from emilykl/fix-slow-scattergl-hover-emilykl
Fix scattergl plot freezing up on hover
2 parents d78bd01 + 7bfe478 commit c31fee7

3 files changed

Lines changed: 533 additions & 439 deletions

File tree

‎draftlogs/8052_fix.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Fix issue causing `scattergl` plot to freeze up when hovering over points [[#8052](https://github.com/plotly/plotly.js/pull/8052)], with thanks to @emilykl for the contribution!

‎src/components/fx/hover.js‎

Lines changed: 69 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,16 @@ exports.loneHover = function loneHover(hoverItems, opts) {
278278
return multiHover ? hoverLabel : hoverLabel.node();
279279
};
280280

281+
// Helper function for use with spikeline logic
282+
// Returns true if either axis has spikeline settings which allow a spikeline to be drawn
283+
// to a point that is not currently hovered over; returns false otherwise.
284+
function canSpikeToNonHoveredPoint(xa, ya) {
285+
return Boolean(
286+
(xa?.showspikes && xa.spikesnap !== 'hovered data') ||
287+
(ya?.showspikes && ya.spikesnap !== 'hovered data')
288+
);
289+
}
290+
281291
// The actual implementation is here:
282292
function _hover(gd, evt, subplot, noHoverEvent, eventTarget) {
283293
if (!subplot) subplot = 'xy';
@@ -708,53 +718,58 @@ function _hover(gd, evt, subplot, noHoverEvent, eventTarget) {
708718
}
709719
}
710720

711-
// Now if there is range to look in, find the points to draw the spikelines
712-
// Do it only if there is no hoverData
713-
if (hasCartesian && spikedistance !== 0) {
714-
if (hoverData.length === 0) {
715-
pointData.distance = spikedistance;
716-
pointData.index = false;
717-
var closestPoints = trace._module.hoverPoints(pointData, xval, yval, 'closest', {
718-
hoverLayer: fullLayout._hoverlayer
721+
// If there is no hoverData, and if the axis spikeline settings permit us to draw
722+
// a spikeline to the nearest non-hovered point, search for points to spike to.
723+
// This search is _expensive_ (especially when spikedistance is -1 which requires
724+
// a search over all points), so we should only do it when absolutely required
725+
if (hasCartesian
726+
&& hoverData.length === 0
727+
&& spikedistance !== 0
728+
&& canSpikeToNonHoveredPoint(pointData.xa, pointData.ya)
729+
) {
730+
// Find the points to draw the spikelines
731+
pointData.distance = spikedistance;
732+
pointData.index = false;
733+
var closestPoints = trace._module.hoverPoints(pointData, xval, yval, 'closest', {
734+
hoverLayer: fullLayout._hoverlayer
735+
});
736+
if (closestPoints) {
737+
closestPoints = closestPoints.filter(function (point) {
738+
// some hover points, like scatter fills, do not allow spikes,
739+
// so will generate a hover point but without a valid spikeDistance
740+
return point.spikeDistance <= spikedistance;
719741
});
720-
if (closestPoints) {
721-
closestPoints = closestPoints.filter(function (point) {
722-
// some hover points, like scatter fills, do not allow spikes,
723-
// so will generate a hover point but without a valid spikeDistance
724-
return point.spikeDistance <= spikedistance;
725-
});
726-
}
727-
if (closestPoints && closestPoints.length) {
728-
var tmpPoint;
729-
var closestVPoints = closestPoints.filter(function (point) {
730-
return point.xa.showspikes && point.xa.spikesnap !== 'hovered data';
731-
});
732-
if (closestVPoints.length) {
733-
var closestVPt = closestVPoints[0];
734-
if (isNumeric(closestVPt.x0) && isNumeric(closestVPt.y0)) {
735-
tmpPoint = fillSpikePoint(closestVPt);
736-
if (
737-
!spikePoints.vLinePoint ||
738-
spikePoints.vLinePoint.spikeDistance > tmpPoint.spikeDistance
739-
) {
740-
spikePoints.vLinePoint = tmpPoint;
741-
}
742+
}
743+
if (closestPoints?.length) {
744+
var tmpPoint;
745+
var closestVPoints = closestPoints.filter(function (point) {
746+
return point.xa.showspikes && point.xa.spikesnap !== 'hovered data';
747+
});
748+
if (closestVPoints.length) {
749+
var closestVPt = closestVPoints[0];
750+
if (isNumeric(closestVPt.x0) && isNumeric(closestVPt.y0)) {
751+
tmpPoint = fillSpikePoint(closestVPt);
752+
if (
753+
!spikePoints.vLinePoint ||
754+
spikePoints.vLinePoint.spikeDistance > tmpPoint.spikeDistance
755+
) {
756+
spikePoints.vLinePoint = tmpPoint;
742757
}
743758
}
759+
}
744760

745-
var closestHPoints = closestPoints.filter(function (point) {
746-
return point.ya.showspikes && point.ya.spikesnap !== 'hovered data';
747-
});
748-
if (closestHPoints.length) {
749-
var closestHPt = closestHPoints[0];
750-
if (isNumeric(closestHPt.x0) && isNumeric(closestHPt.y0)) {
751-
tmpPoint = fillSpikePoint(closestHPt);
752-
if (
753-
!spikePoints.hLinePoint ||
754-
spikePoints.hLinePoint.spikeDistance > tmpPoint.spikeDistance
755-
) {
756-
spikePoints.hLinePoint = tmpPoint;
757-
}
761+
var closestHPoints = closestPoints.filter(function (point) {
762+
return point.ya.showspikes && point.ya.spikesnap !== 'hovered data';
763+
});
764+
if (closestHPoints.length) {
765+
var closestHPt = closestHPoints[0];
766+
if (isNumeric(closestHPt.x0) && isNumeric(closestHPt.y0)) {
767+
tmpPoint = fillSpikePoint(closestHPt);
768+
if (
769+
!spikePoints.hLinePoint ||
770+
spikePoints.hLinePoint.spikeDistance > tmpPoint.spikeDistance
771+
) {
772+
spikePoints.hLinePoint = tmpPoint;
758773
}
759774
}
760775
}
@@ -1425,12 +1440,12 @@ function createHoverText(hoverData, opts) {
14251440
var mainText = !unifiedhovertitleText
14261441
? t0
14271442
: Lib.hovertemplateString({
1428-
data:
1429-
hovermode === 'x unified' ? [{ xa: item0.xa, x: item0.xVal }] : [{ ya: item0.ya, y: item0.yVal }],
1430-
fallback: item0.trace.hovertemplatefallback,
1431-
locale: fullLayout._d3locale,
1432-
template: unifiedhovertitleText
1433-
});
1443+
data:
1444+
hovermode === 'x unified' ? [{ xa: item0.xa, x: item0.xVal }] : [{ ya: item0.ya, y: item0.yVal }],
1445+
fallback: item0.trace.hovertemplatefallback,
1446+
locale: fullLayout._d3locale,
1447+
template: unifiedhovertitleText
1448+
});
14341449

14351450
var mockLayoutIn = {
14361451
showlegend: true,
@@ -2329,12 +2344,12 @@ function cleanPoint(d, hovermode) {
23292344

23302345
var getVal = Array.isArray(index)
23312346
? function (calcKey, traceKey) {
2332-
var v = Lib.castOption(cd0, index, calcKey);
2333-
return pass(v) ? v : Lib.extractOption({}, trace, '', traceKey);
2334-
}
2347+
var v = Lib.castOption(cd0, index, calcKey);
2348+
return pass(v) ? v : Lib.extractOption({}, trace, '', traceKey);
2349+
}
23352350
: function (calcKey, traceKey) {
2336-
return Lib.extractOption(cd, trace, calcKey, traceKey);
2337-
};
2351+
return Lib.extractOption(cd, trace, calcKey, traceKey);
2352+
};
23382353

23392354
function fill(key, calcKey, traceKey) {
23402355
var val = getVal(calcKey, traceKey);

0 commit comments

Comments
 (0)