Skip to content

Fix DataTable tooltip position on paginated pages - #3928

Open
saket3395 wants to merge 3 commits into
plotly:devfrom
saket3395:fix/datatable-tooltip-pagination
Open

Fix DataTable tooltip position on paginated pages#3928
saket3395 wants to merge 3 commits into
plotly:devfrom
saket3395:fix/datatable-tooltip-pagination

Conversation

@saket3395

Copy link
Copy Markdown

Summary

Fixes #1848 — DataTable tooltips render in the wrong position (top-left) on pages other than page 1 when pagination + tooltips are combined.

Root cause: the tooltip row index was resolved from the virtualized (page-local) index in two separate places — once in the cell event handler (cellEvents.ts), and again when matching the tooltip condition/data (tooltip.ts). On page 1 both resolutions happen to agree, hiding the bug. On later pages they diverge, so the tooltip looks up the wrong row.

This ports the fix originally proposed in plotly/dash-table#906 (opened against the standalone dash-table repo before it was merged into dash), applied to the current file paths — the line-level context still matched exactly:

  • components/dash-table/src/dash-table/handlers/cellEvents.ts: handleEnter/handleMove now store the raw virtualized idx in currentTooltip.row, instead of pre-resolving it — consistent with how handleMoveHeader already handles header tooltips.
  • components/dash-table/src/dash-table/derived/table/tooltip.ts: getSelectedTooltip now resolves virtualized.indices[row - offset.rows] once, at lookup time.

As noted on the issue by @alexcjohnson: "should be relatively straightforward to bring it over here and finish it."

Test plan

  • Diffed against dash-table#906 line-by-line — the current code matched the original PR's context exactly, so the port is a faithful line-for-line application, no adaptation needed.
  • Verified handleMoveHeader (unaffected header-tooltip path) already follows the "store raw idx" pattern this PR brings to the row-tooltip path, so the change is internally consistent.
  • Not yet run through the JS build/test suite locally — flagging for maintainer/CI verification. Happy to iterate if CI surfaces anything.

Fixes plotly#1848. Tooltip row index was resolved against the virtualized
(page-local) row twice: once when handling the mouse event, and again
when matching the tooltip condition/data lookup. On page 1 these two
resolutions coincide, masking the bug; on later pages they diverge,
so tooltips render using the wrong row's position/data.

Ports the fix from plotly/dash-table#906 (pre-merge into this repo):
- cellEvents.ts: store the raw virtualized idx in currentTooltip.row
  instead of pre-resolving it, matching how handleMoveHeader already
  behaves for header tooltips.
- tooltip.ts: resolve virtualized.indices[row - offset.rows] once,
  at lookup time in getSelectedTooltip, instead of relying on a
  pre-resolved value.

Manually verified the diff is a faithful port of plotly#906 and consistent
with the surrounding code (handleMoveHeader already stores the raw
idx). Not yet run through the JS build/test suite locally.
@AnnMarieW

Copy link
Copy Markdown
Collaborator

Hi @saket3395

Thanks for the PR! I tested it locally, and it looks like the original solution you ported over from # 906 doesn't quite fix the issue.

You can use the minimal app from the issue to reproduce it. When I run that app with your branch, the tooltip is now positioned correctly on both pages, but on page 2 it displays the tooltip text from page 1 instead of the correct text.

import dash
from dash import html, dash_table

data = [
    {"ID": i, "Name": f"Item {i}", "Description": f"This is item {i}."}
    for i in range(1, 11)
]

app = dash.Dash(__name__)

app.layout = html.Div([
    dash_table.DataTable(
        id="simple-table",
        columns=[
            {"name": "ID", "id": "ID"},
            {"name": "Name", "id": "Name"},
            {"name": "Description", "id": "Description"}
        ],
        data=data,  # All data
        page_size=5,  # Page size is 5
        page_current=0,  # Initial page is the first one
        tooltip_data = [
            {
                'Description': {
                    'value': row['Description'],
                    'type': 'markdown'
                }
            } for row in data
        ],
        tooltip_delay=0,
        tooltip_duration=None,
    ),
])

if __name__ == "__main__":
    app.run(debug=False)

Addresses review feedback from @AnnMarieW: after fixing tooltip
position, tooltip *text* on page 2+ still showed page 1's content.

tooltip_data is indexed by each row's absolute position in the full,
unpaginated dataset. getSelectedTooltip's non-header branch read it as
tooltip_data[row], but row is now the page-local virtualized index
(needed for the position fix). The ifRowIndex check a few lines above
already resolves this same row via virtualized.indices[row - offset.rows]
to get the absolute index -- the tooltip_data lookup needs the same
resolution, which it was missing.
@saket3395

Copy link
Copy Markdown
Author

Thanks for testing this so thoroughly, @AnnMarieW! You were right — the position fix in cellEvents.ts changed currentTooltip.row to store the page-local virtualized index (needed to match the data-dash-row DOM attribute), but I missed that getSelectedTooltip's content lookup at tooltip_data?.[row]?.[id] still needed the absolute dataset row index, since tooltip_data is indexed against the full unpaginated dataset. The ifRowIndex check a few lines above already resolves this correctly via virtualized.indices[row - offset.rows] — I applied the same resolution to the tooltip_data lookup. Pushed the fix; would appreciate if you could re-test with your repro app when you get a chance.

@AnnMarieW

Copy link
Copy Markdown
Collaborator

Hi @saket3395
Thanks for the update!
That fixes the tooltip text in the example app. However, I noticed another issue: if a row is deleted, the tooltip text for the deleted row is still displayed and the tooltips for all subsequent rows are shifted and display the wrong text.

Have you had a chance to try Dash AG Grid? We generally recommend it for new projects since dash-data-table is no longer actively maintained by the Plotly team.

Addresses review feedback from @AnnMarieW: after fixing tooltip
position and content lookup for pagination, deleting a row left the
deleted row's tooltip visible, with all subsequent rows' tooltips
shifted and showing the wrong text.

currentTooltip.row is a virtualized/page-local row index captured at
hover/move time (handleEnter/handleMove in cellEvents.ts). It is never
invalidated when the underlying dataset changes shape -- so after a
row is added, removed, or reordered, the same stored index now maps
to a different row via virtualized.indices, both for DOM position
lookup (data-dash-row) and for the tooltip_data/tooltip_conditional
content lookup in getSelectedTooltip.

Rather than try to remap the stale index (fragile: deletion can shift
every subsequent row by a different amount depending on where it
happened), this clears currentTooltip whenever data or tooltip_data
changes identity while a tooltip is showing -- the same outcome a user
would expect from moving off the cell, since the cell's underlying
data has effectively changed out from under them.

Guarded on 'currentTooltip &&' so this is a no-op (and doesn't loop)
once currentTooltip is already undefined, including on the re-render
triggered by the setState call itself.
@saket3395

Copy link
Copy Markdown
Author

Thanks for catching that too, @AnnMarieW! Root cause: currentTooltip.row is captured once at hover time, but nothing invalidated it when the underlying data changed shape (row deleted/added/reordered) — so the stored index kept pointing at whatever row now happens to sit at that position, both for DOM placement and for the tooltip content lookup.

Rather than try to remap the stale index (fragile — a deletion shifts different rows by different amounts depending on where it happened), I added a check in componentDidUpdate that clears currentTooltip whenever data or tooltip_data changes while a tooltip is showing. That's the same outcome as if the user had moved off the cell, which seems like the right behavior here — the alternative (trying to keep the tooltip showing correctly across a data mutation) seems like a lot of complexity for a hover artifact.

Re dash-data-table's maintenance status — that's good to know, thank you. Happy to keep iterating on this PR if it's still useful, or if you'd rather close it in favor of steering people to AG Grid, that's your call as maintainer.

@sonarqubecloud

sonarqubecloud Bot commented Aug 4, 2026

Copy link
Copy Markdown

@AnnMarieW

Copy link
Copy Markdown
Collaborator

Hi @saket3395

Thanks for being willing to keep working on it.

Just to clarify, I'm a community member helping with the initial review of the PR, not one of the Dash maintainers, so I can't make the decision about whether it gets merged. That said, I think it would be good to get this one across the finish line if you're interested in continuing.

One question: are you testing the changes locally by running the minimal example from the issue and the test suite? Your approach of clearing currentTooltip in componentDidUpdate seems like a good approach, but it doesn't build.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

dashtable with pagination: tooltips appear in wrong place

2 participants