GeezerVison is showing github.com — Fix scattergl plot freezing up on hover by emilykl · Pull Request #8052 · plotly/plotly.js
Conversation
emilykl
commented
Sep 17, 2026
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
Closes #5927
Closes #5790
Closes #8036
(Supersedes #7998)
Fix unreasonably slow scattergl hover behavior (to the point of freezing) in the default case. Even with this fix, the hover is still somewhat laggy, but doesn't freeze entirely as in the linked issues.
The scattergl hover logic was following a code path which looks for nearby points to draw spikelines to. This code path was being followed even when spikelines were not enabled. The search is very slow with large numbers of points. This PR adds a guard to skip the "find nearby points" search unless required.
Note: For the diff chunk in src/components/fx/hover.js starting on line 671, the only meaningful change is adding canSpikeToNonHoveredPoint() to the if condition. The rest of that diff is just indentation change from collapsing two nested if statements into 1.
Screen recording
This video shows the performance I see on my machine after this fix, with extremely closely-spaced points.
scattergl-hover.mov
Steps for testing:
- Use the code snippet provided in #8036:
const n = 100000;
Plotly.newPlot('graph', [{
type: 'scattergl',
mode: 'markers',
x0: 0,
dx: 1,
y: Array(n).fill(0)
}]);
- Paste into devtools dashboard on this branch
- Hover over individual points. Notice that the hover is more-or-less functional, although it may be a bit laggy.
- Run the same code snippet in the devtools dashboard on
main. Notice that the entire plot freezes completely when trying to hover.
Caveat
There's an underlying performance issue which is not addressed by this PR, which probably deserves its own issue. #8036 points out that there's a performance cliff which starts at 100k points, even though 99,999 points are fine. At 100k points we exceed the TOO_MANY_POINTS constant defined in src/traces/scattergl/constants.ts, which triggers us to use a tree data structure for searching through points, rather than a plain array.
I haven't fully investigated but there seems to be some performance issues with that tree implementation (which is ours: see https://github.com/plotly/point-cluster), specifically with the .range() function called here. It's extremely slow when given infinite bounds, and seems to be slower than regular array traversal whenever the bounds cover a significant fraction of the total points. Either we're calling the .range() function in a non-optimal way, or there's a bug in the implementation. Hence why even with this fix, the scattergl hover is still somewhat laggy as soon as we exceed 100k points.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
emilykl
changed the title
Fix slow scattergl hover
Improve scattergl performance on hover
Sep 17, 2026
emilykl
changed the title
Improve scattergl performance on hover
Fix scattergl plot freezing up on hover
Sep 17, 2026
Comment on lines
+269
to
+272
|
|
return Boolean(
|
|
|
(xa && xa.showspikes && xa.spikesnap !== 'hovered data') ||
|
|
|
(ya && ya.showspikes && ya.spikesnap !== 'hovered data')
|
|
|
);
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or perhaps
|
return Boolean( |
|
(xa && xa.showspikes && xa.spikesnap !== 'hovered data') || |
|
(ya && ya.showspikes && ya.spikesnap !== 'hovered data') |
|
); |
|
return (xa?.showspikes && xa.spikesnap !== 'hovered data') || |
|
(ya?.showspikes && ya.spikesnap !== 'hovered data'); |
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fine, but let's keep the Boolean() to avoid returning undefined
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The presence of the strict inequality operator (!==) at the end of both clauses forces the final output to always be a boolean.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
emilykl reacted with thumbs up emoji
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, I realized that if xa?.showspikes evaluates to falsy (but not false) then the !== condition will be short-circuited, so may not return a boolean.
I've updated the logic to use the question-mark notation but keep the Boolean().
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
|
spikePoints.vLinePoint = tmpPoint;
|
|
|
}
|
|
|
}
|
|
|
if (closestPoints && closestPoints.length) {
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
if (closestPoints && closestPoints.length) { |
|
if (closestPoints?.length) { |
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW I didn't touch this logic at all; it only shows up in the diff because the indentation changed. I'm fine with making this change though.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
…ver-emilykl
# Conflicts:
# src/components/fx/hover.js
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Reviewers
KoolADE85
Awaiting requested review from KoolADE85
+1 more reviewer
archmoj
archmoj left review comments
Reviewers whose approvals may not affect merge requirements
Uh oh!
There was an error while loading. Please reload this page.
Milestone
No milestone
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
|