Tooltip bbox position is incorrect when inside a dcc.Loading component
First of all, I want to express my appreciation for this excellent product.
Context
I have a graph that takes a long time to render, and I want to display a loading indicator before it renders.
dash 2.18.1
dash-bootstrap-components 1.6.0
dash-core-components 2.0.0
dash-html-components 2.0.0
dash-table 5.0.0
- OS: Windows 11 Pro 23H2
- Browser: Chrome
- Version: 128.0.6613.139
Bug
When the graph is nested inside a dcc.Loading component, the tooltip's bbox position is misaligned with the plot when displayed.
From my investigation, in Dash 2.17.0, the tooltip's bbox position is correct regardless of whether it's nested inside a loading component.
However, starting from Dash 2.17.1, the tooltip's bbox position becomes incorrect when nested.
Expected behavior
The tooltip's bbox position should be correct regardless of whether the graph is nested inside a dcc.Loading component.
Screenshots
Here I show the tooltip's bbox position problem using my minimal reproduction example.
Dash 2.17.0
Dash 2.17.1
Dash 2.18.1
Minimal Reproduction Example
poetry install
poetry run python main.py
poetry.lock
[tool.poetry]
name = "dash-tooltip-test"
version = "0.1.0"
description = ""
authors = ["Your Name <[email protected]>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.12"
dash = "=2.18.1"
plotly = "^5.24.1"
pandas = "^2.2.3"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
main.py
from typing import Optional
import plotly.express as px
from dash import Dash, Input, Output, dcc, html, no_update
APP = Dash()
APP.title = "Tooltip Test"
APP.layout = html.Div(
[
html.H1("Tooltip Test (Dash 2.18.1)"),
html.H2("With Loading"),
dcc.Loading(
id="loading",
type="default",
children=[
dcc.Graph(
id="graph-with-loading",
figure=px.scatter(x=[0, 1, 2, 3, 4], y=[0, 1, 4, 9, 16]),
),
],
),
dcc.Tooltip(id="tooltip-with-loading"),
html.H2("Without Loading"),
dcc.Graph(
id="graph-without-loading",
figure=px.scatter(x=[0, 1, 2, 3, 4], y=[0, 1, 4, 9, 16]),
),
dcc.Tooltip(id="tooltip-without-loading"),
]
)
@APP.callback(
Output("tooltip-with-loading", "show"),
Output("tooltip-with-loading", "bbox"),
Output("tooltip-with-loading", "children"),
Input("graph-with-loading", "hoverData"),
)
def display_tooltip_with_loading(hoverData: Optional[dict]) -> tuple[bool, dict, str]:
if hoverData is None:
return False, no_update, no_update
bbox = hoverData["points"][0]["bbox"]
return (
True,
bbox,
f"x: {hoverData['points'][0]['x']}, y: {hoverData['points'][0]['y']}",
)
@APP.callback(
Output("tooltip-without-loading", "show"),
Output("tooltip-without-loading", "bbox"),
Output("tooltip-without-loading", "children"),
Input("graph-without-loading", "hoverData"),
)
def display_tooltip_without_loading(
hoverData: Optional[dict],
) -> tuple[bool, dict, str]:
if hoverData is None:
return False, no_update, no_update
bbox = hoverData["points"][0]["bbox"]
return (
True,
bbox,
f"x: {hoverData['points'][0]['x']}, y: {hoverData['points'][0]['y']}",
)
if __name__ == "__main__":
APP.run_server(debug=True)
Hi @TenteEEEE
Thanks for the great write-up and the sample app! 🏆
I was able to reproduce the issue. I would have expected to see this issue in 2.17.0 as well, because the dcc.Tooltip should be included as child of the dcc.Loading component.
This worked as expected in the current version of Dash. I'm not sure if this should be consider a bug.
dcc.Loading(
id="loading",
type="default",
delay_show=1000,
children=[
dcc.Graph(
id="graph-with-loading",
figure=px.scatter(x=[0, 1, 2, 3, 4], y=[0, 1, 4, 9, 16]),
),
dcc.Tooltip(id="tooltip-with-loading"),
],
),
Update - it should probably work both ways, so the above might just be considered a workaround.
@AnnMarieW
Thank you for your response!
I sincerely appreciate your guidance on the appropriate workaround. In my application, displaying the tooltip is also a time-consuming process that involves loading images. If the dcc.Tooltip is placed inside the dcc.Loading, it displays an excessive loading overlay, so I placed the dcc.Tooltip outside the dcc.Loading. My understanding is that the correct approach would be to use target_components and implement it as shown below.
dcc.Loading(
id="loading",
type="default",
children=[
dcc.Graph(
id="graph-with-loading",
figure=px.scatter(x=[0, 1, 2, 3, 4], y=[0, 1, 4, 9, 16]),
),
dcc.Tooltip(
id="tooltip-with-loading",
),
],
target_components={"graph-with-loading": "children"},
),
Is this correct?
My personal issue has been completely resolved with the above solution, so I now understand that this issue is not a bug but a specification. If the maintainers also consider this issue to be a specification, I would appreciate it if the issue could be closed.
Once again, thank you for your support!