Skip to content

Commit f3eaf4e

Browse files
authored
Refactored to allow tooltip template update on existing graph
Updated tests
1 parent ab4ed40 commit f3eaf4e

15 files changed

+663
-245
lines changed

.pre-commit-config.yaml

Lines changed: 36 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,28 @@
11
repos:
22
- repo: local
33
hooks:
4+
- id: ruff
5+
name: ruff
6+
description: "Run 'ruff' for extremely fast Python linting"
7+
entry: ruff check --force-exclude
8+
language: python
9+
types_or: [python, pyi]
10+
args: []
11+
require_serial: true
12+
additional_dependencies: []
13+
minimum_pre_commit_version: "2.9.2"
14+
15+
- id: ruff-format
16+
name: ruff-format
17+
description: "Run 'ruff format' for extremely fast Python formatting"
18+
entry: ruff format --force-exclude
19+
language: python
20+
types_or: [python, pyi]
21+
args: []
22+
require_serial: true
23+
additional_dependencies: []
24+
minimum_pre_commit_version: "2.9.2"
25+
426
- id: black-fix
527
name: black (auto format)
628
entry: ./env/Scripts/black.exe
@@ -13,20 +35,6 @@ repos:
1335
language: system
1436
types: [python]
1537

16-
- id: pytest-selenium
17-
name: pytest (selenium tests)
18-
entry: ./env/Scripts/pytest.exe
19-
args: ['tests/', '-k', 'selenium', '--webdriver', 'Firefox']
20-
language: system
21-
pass_filenames: false
22-
23-
- id: pytest-non-selenium
24-
name: pytest (non-selenium tests)
25-
entry: ./env/Scripts/pytest.exe
26-
args: ['tests/', '-k', 'not selenium']
27-
language: system
28-
pass_filenames: false
29-
3038
- id: black
3139
name: black
3240
entry: ./env/Scripts/black.exe
@@ -39,30 +47,22 @@ repos:
3947
language: system
4048
types: [python]
4149

42-
- id: ruff
43-
name: ruff
44-
description: "Run 'ruff' for extremely fast Python linting"
45-
entry: ruff check --force-exclude
46-
language: python
47-
types_or: [python, pyi]
48-
args: []
49-
require_serial: true
50-
additional_dependencies: []
51-
minimum_pre_commit_version: "2.9.2"
52-
53-
- id: ruff-format
54-
name: ruff-format
55-
description: "Run 'ruff format' for extremely fast Python formatting"
56-
entry: ruff format --force-exclude
57-
language: python
58-
types_or: [python, pyi]
59-
args: []
60-
require_serial: true
61-
additional_dependencies: []
62-
minimum_pre_commit_version: "2.9.2"
63-
6450
- id: mypy
6551
name: mypy
6652
entry: ./env/Scripts/mypy.exe
6753
language: system
6854
types: [python]
55+
56+
- id: pytest-selenium
57+
name: pytest (selenium tests)
58+
entry: ./env/Scripts/pytest.exe
59+
args: ['tests/', '-k', 'selenium', '--webdriver', 'Firefox', '--testmon']
60+
language: system
61+
pass_filenames: false
62+
63+
- id: pytest-non-selenium
64+
name: pytest (non-selenium tests)
65+
entry: ./env/Scripts/pytest.exe
66+
args: ['tests/', '-k', 'not selenium', '--testmon']
67+
language: system
68+
pass_filenames: false

.python-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.12.0
1+
3.12.3

README.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,117 @@ tooltip(app10, style=custom_style, graph_ids=["graph-id"], template=template, de
135135
For more examples, refer to the provided `dash_tooltip_demo.py` and check out [Plotly’s Text and Annotations documentation](https://plotly.com/python/text-and-annotations/#styling-and-coloring-annotations), which provides a wealth of information on customizing the appearance of annotations.
136136
Refer to the [Plotly Annotation Reference](https://plotly.com/python/reference/layout/annotations/) for a comprehensive guide on available styling attributes and how to apply them.
137137

138+
## Template updating
139+
140+
Tooltip content can be updated to match with selected data in a dynamic Dash app:
141+
```python
142+
GRAPH_ID = "scatter-plot16a"
143+
144+
# Sample DataFrame with DatetimeIndex
145+
date_range = pd.date_range(start="2025-01-01", periods=5)
146+
df = pd.DataFrame(
147+
{
148+
"x": [1, 2, 3, 4, 5],
149+
"y": [2, 4, 6, 8, 10],
150+
"z": [3, 6, 9, 12, 15],
151+
"a": [4, 8, 12, 16, 20],
152+
"b": [5, 10, 15, 20, 25],
153+
},
154+
index=date_range,
155+
)
156+
157+
# Initialize the Dash app
158+
app16 = dash.Dash(__name__)
159+
160+
# Define the layout
161+
app16.layout = html.Div(
162+
[
163+
html.Label("Select X and Y columns:"),
164+
dcc.Dropdown(
165+
id="x-column",
166+
options=[{"label": col, "value": col} for col in df.columns],
167+
placeholder="Select X axis data",
168+
),
169+
dcc.Dropdown(
170+
id="y-column",
171+
options=[{"label": col, "value": col} for col in df.columns],
172+
placeholder="Select Y axis data",
173+
),
174+
dcc.Graph(
175+
id=GRAPH_ID,
176+
style={"width": "600px", "height": "600px"},
177+
config={
178+
"editable": True,
179+
"edits": {"shapePosition": True, "annotationPosition": True},
180+
},
181+
),
182+
]
183+
)
184+
185+
# Create a tooltip instance
186+
tooltip_instance16 = tooltip(app16, graph_ids=[GRAPH_ID])
187+
188+
# Define callback to update the scatter plot
189+
@app16.callback(
190+
Output(GRAPH_ID, "figure", allow_duplicate=True),
191+
[Input("x-column", "value"), Input("y-column", "value")],
192+
prevent_initial_call=True,
193+
)
194+
def update_scatter_plot(x_column, y_column):
195+
if not x_column or not y_column:
196+
raise PreventUpdate # Prevent update if either dropdown is not selected
197+
198+
non_selected_columns = [
199+
col for col in df.columns if col not in [x_column, y_column]
200+
]
201+
customdata = df[non_selected_columns].apply(
202+
lambda row: "<br>".join(
203+
f"{col}: {val}" for col, val in zip(non_selected_columns, row)
204+
),
205+
axis=1,
206+
)
207+
# gives (depending on selected entries):
208+
# 2022-01-01 x: 1<br>z: 3<br>b: 5
209+
# 2022-01-02 x: 2<br>z: 6<br>b: 10
210+
# ...
211+
212+
# New template, to match selected data entries
213+
template = (
214+
"<b>Date</b>: %{customdata}<br>"
215+
+ f"<b>{x_column}: %{{x}}<br>"
216+
+ f"{y_column}: %{{y}}</b><br>"
217+
)
218+
# gives (depending on selected entries):
219+
# <b>Date</b>: %{customdata}<br><b>x: %{x}<br><b>a</b>: %{y}<br>
220+
221+
# Update template for new tooltips
222+
tooltip_instance16.update_template(graph_id=GRAPH_ID, template=template)
223+
224+
trace = go.Scatter(
225+
x=df[x_column],
226+
y=df[y_column],
227+
mode="markers",
228+
marker=dict(color="blue"),
229+
customdata=df.index.strftime("%Y-%m-%d %H:%M:%S") + "<br>" + customdata,
230+
# Include date and time with other data
231+
hovertemplate=template,
232+
)
233+
layout = go.Layout(
234+
title="Scatter Plot",
235+
xaxis=dict(title=x_column),
236+
yaxis=dict(title=y_column),
237+
hovermode="closest",
238+
height=800,
239+
width=800,
240+
)
241+
return {"data": [trace], "layout": layout}
242+
243+
244+
# Run the app
245+
if __name__ == "__main__":
246+
app16.run_server(debug=False, port=8196)
247+
```
248+
138249
## Handling Log Axes
139250

140251
Due to a long-standing bug in Plotly (see [Plotly Issue #2580](https://github.com/plotly/plotly.py/issues/2580)), annotations (`fig.add_annotation`) may not be placed correctly on log-scaled axes. The `dash_tooltip` module provides an option to automatically correct the tooltip placement on log-scaled axes via the `apply_log_fix` argument in the `tooltip` function. By default, `apply_log_fix` is set to `True` to enable the fix.

dash_qt_demo2.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import http.client
2-
import threading
32
import os
43
import sys
4+
import threading
55
import time
66

77
import dash
88
import numpy as np
9+
import plotly.graph_objects as go
910
from dash import dcc, html
1011
from PyQt6.QtCore import QUrl
1112
from PyQt6.QtWebEngineWidgets import QWebEngineView
1213
from PyQt6.QtWidgets import QApplication, QFileDialog, QMainWindow
13-
import plotly.graph_objects as go
14+
1415
from dash_tooltip import tooltip
1516

1617
dash_port = 8050
@@ -89,8 +90,8 @@ def create_dash_app():
8990
layout_margin = {"l": 25, "r": 25, "t": 25, "b": 25}
9091

9192
# Creating a grid of x and y values
92-
x = np.linspace(0, 10, 100)
93-
y = np.linspace(0, 10, 100)
93+
x = np.linspace(0, 10, 400)
94+
y = np.linspace(0, 10, 400)
9495
X, Y = np.meshgrid(x, y)
9596

9697
# Calculate Z as a function of X and Y
@@ -102,7 +103,7 @@ def create_dash_app():
102103
z=Z,
103104
x=x,
104105
y=y,
105-
colorscale="Viridis"
106+
colorscale="Viridis",
106107
# You can change the colorscale as needed
107108
),
108109
layout=go.Layout(margin=layout_margin),
@@ -151,7 +152,10 @@ def create_dash_app():
151152
template1 = "x: %{x:.2f},<br>y: %{y:.2f}"
152153
tooltip(app, template=template1, graph_ids=["example-graph1"])
153154
template2 = "x: %{x:.2f},<br>y: %{y:.2f},<br>z: %{z:.3f}"
154-
tooltip(app, template=template2, graph_ids=["example-graph2"])
155+
tooltip_style = {
156+
"bgcolor": "rgba(255, 255, 255, 0.2)",
157+
}
158+
tooltip(app, style=tooltip_style, template=template2, graph_ids=["example-graph2"])
155159
return app
156160

157161

0 commit comments

Comments
 (0)