Fix DataTable tooltip position on paginated pages - #3928
Conversation
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.
|
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.
|
Thanks for testing this so thoroughly, @AnnMarieW! You were right — the position fix in cellEvents.ts changed |
|
Hi @saket3395 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.
|
Thanks for catching that too, @AnnMarieW! Root cause: 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 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. |
|
|
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 |
|
@saket3395 thank you for the work on this! The PR does look quite good and normally we would definitely want to get it over the finish line; but since we're steering people towards AG-Grid for more feature rich tables at the moment I think it would make sense to close this. Leaving it open in case you or @AnnMarieW disagree or want to discuss further, but feel free to close it if that makes sense to you as well. |



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:
As noted on the issue by @alexcjohnson: "should be relatively straightforward to bring it over here and finish it."
Test plan