Skip to content

[BUG]: scattergl and splom hover ignores marker size for overlapping points #8082

Open

Description

@camdecoster

Description

If markers of different sizes overlap, scatter and scattergl show different hover labels. scatter includes the marker radius in the hover distance, and a smaller marker wins while the pointer is near its center (src/traces/scatter/hover.js). scattergl and splom measure only the distance to the marker center, so the marker drawn on top always wins. scatterpolargl inherits this through scattergl.

scattergl also uses a plain |dx| in x hovermode, but scatter changes the shape of dx inside the marker radius. That difference can rank close points differently.

This is a follow-up to #8077, which fixed ties between equal distances.

Steps to reproduce

  • Open Plotly devtools
  • Run this snippet in the browser devtools
Plotly.newPlot(gd, {
    data: [
        { type: 'scatter', name: 'A', mode: 'markers', marker: { size: 6 }, x: [1], y: [0] },
        { type: 'scatter', name: 'B', mode: 'markers', marker: { size: 20 }, x: [1], y: [0] }
    ],
    layout: { hovermode: 'closest' }
});
  • Hover the center of the shared point
  • Note that the label shows A
  • Change both traces to type: 'scattergl' and hover again
  • Note that the label shows B

Notes

  • A fix probably means sharing the scoring functions in scatter/hover.js with scattergl and splom
  • Each marker's radius can be taken from the sizes that scattergl/convert.js already computes
  • Large traces search a spatial tree within hoverdistance of the pointer, so the search box also has to grow by the largest marker radius
  • This would changed hover results for every WebGL plot with markers of different sizes, so we need to be careful about the blast radius

Activity

  1. adimalkar commented on Nov 11, 2026

    @adimalkar

    I reproduced this on main (90f954a) with real pointer events in Chrome, and I have a fix ready if you'd like a PR.

    It follows your notes:

    • I moved the existing dx/dy/dxy math in scatter/hover.js into two exported functions, getAxisDistance and getPointDistance, without changing it. scattergl and splom now score points with them. Each radius is half of the sizes/size that convert already computes for the WebGL markers.
    • When scattergl uses the spatial index, the search box grows by the largest marker radius.
    • scatterpolargl gets the fix through scattergl.

    Before and after, comparing the WebGL trace with its SVG counterpart:

    Case SVG WebGL on main WebGL with the fix
    your snippet (A size 6, B size 20, pointer at the center) A B A
    one trace, sizes [6, 20] at the same point small big small
    big marker drawn first, pointer 8px from the center B A B
    splom, sizes [6, 20] — big small
    scatterpolargl vs scatterpolar, sizes [6, 20] small big small
    1e5-point trace, pointer 30px inside an 80px marker big no hover big

    About the blast radius: with markers of equal size, ties still go to the trace drawn last (#8077). Two things change for every WebGL plot with markers:

    • A point counts as in range when the pointer is within hoverdistance of the marker's edge, as in scatter.
    • spikedistance and the label order in x hovermode use the same scoring as scatter.
    • When the pointer is inside several markers of the same size, the point drawn last wins, as in scatter. Before, WebGL picked the nearest center. This changed five expectations in scatterpolargl_test.js. Their default pointer is 3.7px from point 2 and 5.8px from point 26 of 'Trial 3', and both markers have a 6px radius. scatterpolar picks point 26 for the same figure, and scatterpolargl now does too.

    The hover label still anchors at the marker center in WebGL. scatter anchors it at the edge. I left that out to keep this change focused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions