Skip to content

Commit

Permalink
Updated readme
Browse files Browse the repository at this point in the history
  • Loading branch information
kb- committed May 5, 2024
1 parent c83211e commit 46f2c52
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 65 deletions.
18 changes: 9 additions & 9 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,44 +25,44 @@ repos:

- id: black-fix
name: black (auto format)
entry: ./env311/Scripts/black.exe
entry: ./env/Scripts/black.exe
language: system
types: [python]

- id: isort-fix
name: isort (auto sort)
entry: ./env311/Scripts/isort.exe
entry: ./env/Scripts/isort.exe
language: system
types: [python]

- id: black
name: black
entry: ./env311/Scripts/black.exe
entry: ./env/Scripts/black.exe
language: system
types: [python]

- id: isort
name: isort
entry: ./env311/Scripts/isort.exe
entry: ./env/Scripts/isort.exe
language: system
types: [python]

- id: mypy
name: mypy
entry: ./env311/Scripts/mypy.exe
entry: ./env/Scripts/mypy.exe
language: system
types: [python]

- id: pytest-selenium
name: pytest (selenium tests)
entry: ./env311/Scripts/pytest.exe
args: ['tests/', '-k', 'selenium', '--webdriver', 'Firefox']
entry: ./env/Scripts/pytest.exe
args: ['tests/', '-k', 'selenium', '--webdriver', 'Firefox', '--testmon']
language: system
pass_filenames: false

- id: pytest-non-selenium
name: pytest (non-selenium tests)
entry: ./env311/Scripts/pytest.exe
args: ['tests/', '-k', 'not selenium']
entry: ./env/Scripts/pytest.exe
args: ['tests/', '-k', 'not selenium', '--testmon']
language: system
pass_filenames: false
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.12.0
3.12.3
111 changes: 111 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,117 @@ tooltip(app10, style=custom_style, graph_ids=["graph-id"], template=template, de
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.
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.

## Template updating

Tooltip content can be updated to match with selected data in a dynamic Dash app:
```python
GRAPH_ID = "scatter-plot16a"

# Sample DataFrame with DatetimeIndex
date_range = pd.date_range(start="2025-01-01", periods=5)
df = pd.DataFrame(
{
"x": [1, 2, 3, 4, 5],
"y": [2, 4, 6, 8, 10],
"z": [3, 6, 9, 12, 15],
"a": [4, 8, 12, 16, 20],
"b": [5, 10, 15, 20, 25],
},
index=date_range,
)

# Initialize the Dash app
app16 = dash.Dash(__name__)

# Define the layout
app16.layout = html.Div(
[
html.Label("Select X and Y columns:"),
dcc.Dropdown(
id="x-column",
options=[{"label": col, "value": col} for col in df.columns],
placeholder="Select X axis data",
),
dcc.Dropdown(
id="y-column",
options=[{"label": col, "value": col} for col in df.columns],
placeholder="Select Y axis data",
),
dcc.Graph(
id=GRAPH_ID,
style={"width": "600px", "height": "600px"},
config={
"editable": True,
"edits": {"shapePosition": True, "annotationPosition": True},
},
),
]
)

# Create a tooltip instance
tooltip_instance16 = tooltip(app16, graph_ids=[GRAPH_ID])

# Define callback to update the scatter plot
@app16.callback(
Output(GRAPH_ID, "figure", allow_duplicate=True),
[Input("x-column", "value"), Input("y-column", "value")],
prevent_initial_call=True,
)
def update_scatter_plot(x_column, y_column):
if not x_column or not y_column:
raise PreventUpdate # Prevent update if either dropdown is not selected

non_selected_columns = [
col for col in df.columns if col not in [x_column, y_column]
]
customdata = df[non_selected_columns].apply(
lambda row: "<br>".join(
f"{col}: {val}" for col, val in zip(non_selected_columns, row)
),
axis=1,
)
# gives (depending on selected entries):
# 2022-01-01 x: 1<br>z: 3<br>b: 5
# 2022-01-02 x: 2<br>z: 6<br>b: 10
# ...

# New template, to match selected data entries
template = (
"<b>Date</b>: %{customdata}<br>"
+ f"<b>{x_column}: %{{x}}<br>"
+ f"{y_column}: %{{y}}</b><br>"
)
# gives (depending on selected entries):
# <b>Date</b>: %{customdata}<br><b>x: %{x}<br><b>a</b>: %{y}<br>

# Update template for new tooltips
tooltip_instance16.update_template(graph_id=GRAPH_ID, template=template)

trace = go.Scatter(
x=df[x_column],
y=df[y_column],
mode="markers",
marker=dict(color="blue"),
customdata=df.index.strftime("%Y-%m-%d %H:%M:%S") + "<br>" + customdata,
# Include date and time with other data
hovertemplate=template,
)
layout = go.Layout(
title="Scatter Plot",
xaxis=dict(title=x_column),
yaxis=dict(title=y_column),
hovermode="closest",
height=800,
width=800,
)
return {"data": [trace], "layout": layout}


# Run the app
if __name__ == "__main__":
app16.run_server(debug=False, port=8196)
```

## Handling Log Axes

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.
Expand Down
109 changes: 54 additions & 55 deletions dash_tooltip_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
import plotly.express as px
import plotly.graph_objects as go
import plotly.io as pio
from dash import Dash, Input, Output, callback_context, dcc, html
from dash import Dash, Input, Output, dcc, html
from dash.exceptions import PreventUpdate
from plotly.subplots import make_subplots
from plotly_resampler import FigureResampler
from trace_updater import TraceUpdater # Assuming you've imported this module
Expand Down Expand Up @@ -402,9 +403,9 @@
# ... any other customization
}
template = "x: %{x},<br>y: %{y},<br>%{customdata[0]}"
mytooltip = tooltip(app5, style=custom_style, template=template)
tooltip_instance5 = tooltip(app5, style=custom_style, template=template)

mytooltip.tooltip_active = False
tooltip_instance5.tooltip_active = False


# Create a callback to toggle the tooltip
Expand All @@ -413,7 +414,7 @@
Input("tooltip-switch", "value"),
)
def toggle_tooltip(value: bool):
mytooltip.tooltip_active = value
tooltip_instance5.tooltip_active = value
return ""


Expand Down Expand Up @@ -1222,9 +1223,9 @@ def interactive_plot(fig, graphid, template):
app15.run(debug=False, port=8095, jupyter_height=800)


# %% jupyter={"source_hidden": true}
# %%
# ---- Test 16: update tooltip template----
GRAPH_ID = "scatter-plot16"
GRAPH_ID = "scatter-plot16a"

# Sample DataFrame with DatetimeIndex
date_range = pd.date_range(start="2025-01-01", periods=5)
Expand All @@ -1249,12 +1250,12 @@ def interactive_plot(fig, graphid, template):
dcc.Dropdown(
id="x-column",
options=[{"label": col, "value": col} for col in df.columns],
value=df.columns[0],
placeholder="Select X axis data",
),
dcc.Dropdown(
id="y-column",
options=[{"label": col, "value": col} for col in df.columns],
value=df.columns[1],
placeholder="Select Y axis data",
),
dcc.Graph(
id=GRAPH_ID,
Expand All @@ -1267,8 +1268,7 @@ def interactive_plot(fig, graphid, template):
]
)

mytooltip = tooltip(app16, debug=True, graph_ids=[GRAPH_ID])
once = False
tooltip_instance16 = tooltip(app16, debug=True, graph_ids=[GRAPH_ID])


# Define callback to update the scatter plot
Expand All @@ -1278,52 +1278,51 @@ def interactive_plot(fig, graphid, template):
prevent_initial_call=True,
)
def update_scatter_plot(x_column, y_column):
global once
triggered_id = callback_context.triggered[0]["prop_id"].split(".")[0]
if not x_column or not y_column:
raise PreventUpdate # Prevent update if either dropdown is not selected

if triggered_id in ["x-column", "y-column"]:
non_selected_columns = [
col for col in df.columns if col not in [x_column, y_column]
]
customdata = df[non_selected_columns].apply(
lambda row: "<br>".join(
f"{col}: {val}" for col, val in zip(non_selected_columns, row)
),
axis=1,
)
# gives (depending on selected entries):
# 2022-01-01 x: 1<br>z: 3<br>b: 5
# 2022-01-02 x: 2<br>z: 6<br>b: 10
# ...

template = (
"<b>Date</b>: %{customdata}<br>"
+ f"<b>{x_column}: %{{x}}<br>"
+ f"{y_column}: %{{y}}</b><br>"
)
# gives (depending on selected entries):
# <b>Date</b>: %{customdata}<br><b>x: %{x}<br><b>a</b>: %{y}<br>

mytooltip.update_template(graph_id=GRAPH_ID, template=template)

trace = go.Scatter(
x=df[x_column],
y=df[y_column],
mode="markers",
marker=dict(color="blue"),
customdata=df.index.strftime("%Y-%m-%d %H:%M:%S") + "<br>" + customdata,
# Include date and time with other data
hovertemplate=template,
)
layout = go.Layout(
title="Scatter Plot",
xaxis=dict(title=x_column),
yaxis=dict(title=y_column),
hovermode="closest",
height=800,
width=800,
)
return {"data": [trace], "layout": layout}
non_selected_columns = [
col for col in df.columns if col not in [x_column, y_column]
]
customdata = df[non_selected_columns].apply(
lambda row: "<br>".join(
f"{col}: {val}" for col, val in zip(non_selected_columns, row)
),
axis=1,
)
# gives (depending on selected entries):
# 2022-01-01 x: 1<br>z: 3<br>b: 5
# 2022-01-02 x: 2<br>z: 6<br>b: 10
# ...

template = (
"<b>Date</b>: %{customdata}<br>"
+ f"<b>{x_column}: %{{x}}<br>"
+ f"{y_column}: %{{y}}</b><br>"
)
# gives (depending on selected entries):
# <b>Date</b>: %{customdata}<br><b>x: %{x}<br><b>a</b>: %{y}<br>

tooltip_instance16.update_template(graph_id=GRAPH_ID, template=template)

trace = go.Scatter(
x=df[x_column],
y=df[y_column],
mode="markers",
marker=dict(color="blue"),
customdata=df.index.strftime("%Y-%m-%d %H:%M:%S") + "<br>" + customdata,
# Include date and time with other data
hovertemplate=template,
)
layout = go.Layout(
title="Scatter Plot",
xaxis=dict(title=x_column),
yaxis=dict(title=y_column),
hovermode="closest",
height=800,
width=800,
)
return {"data": [trace], "layout": layout}


# Run the app
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ trace_updater
numpy
ipython
pytest
pytest-testmon
dash[testing]
pre-commit
black
Expand Down

0 comments on commit 46f2c52

Please sign in to comment.