Skip to content

Commit cbafcdd

Browse files
adibiasioAndrew DiBiasioBrianThomasRoss
authored
557 Dash lock plot zoom
* [Dash] Lock Zoom Zoom Persists on parameter updates when the "Lock Zoom" button is in the on position Removed "max_y_axis" feature * Update sidebar.py Co-authored-by: Andrew DiBiasio <andrew@localhost> Co-authored-by: Brian Ross <[email protected]>
1 parent cecdd01 commit cbafcdd

File tree

6 files changed

+114
-31
lines changed

6 files changed

+114
-31
lines changed

src/chime_dash/app/components/visualizations.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from dash.development.base_component import ComponentMeta
1111
from dash_html_components import H2, Div, A
1212
from dash_core_components import Markdown, Graph
13-
from dash_bootstrap_components import Table, Container
13+
from dash_bootstrap_components import Table, Container, Button
1414

1515
from chime_dash.app.components.base import Component
1616

@@ -50,6 +50,16 @@ def get_html(self) -> List[ComponentMeta]:
5050
target="_blank",
5151
className="btn btn-sm btn-info",
5252
),
53+
Div(
54+
children=Button(
55+
"Lock Zoom",
56+
id="new_admissions_lock_zoom",
57+
color="info",
58+
outline=False,
59+
className="btn btn-sm"
60+
),
61+
style={"display": "inline-block", "padding": 10}
62+
),
5363
Div(
5464
className="row justify-content-center",
5565
children=Div(
@@ -79,6 +89,16 @@ def get_html(self) -> List[ComponentMeta]:
7989
target="_blank",
8090
className="btn btn-sm btn-info",
8191
),
92+
Div(
93+
children=Button(
94+
"Lock Zoom",
95+
id="admitted_patients_lock_zoom",
96+
color="info",
97+
outline=False,
98+
className="btn btn-sm"
99+
),
100+
style={"display": "inline-block", "padding": 10}
101+
),
82102
Div(
83103
className="row justify-content-center",
84104
children=Div(
@@ -108,6 +128,16 @@ def get_html(self) -> List[ComponentMeta]:
108128
target="_blank",
109129
className="btn btn-sm btn-info my-4",
110130
),
131+
Div(
132+
children=Button(
133+
"Lock Zoom",
134+
id="SIR_lock_zoom",
135+
color="info",
136+
outline=False,
137+
className="btn btn-sm"
138+
),
139+
style={"display": "inline-block", "padding": 10}
140+
),
111141
Div(
112142
className="row justify-content-center",
113143
children=Div(

src/chime_dash/app/services/callbacks.py

Lines changed: 69 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,14 @@ def toggle_tables(switch_value):
3030
return get_n_switch_values(not switch_value, 3)
3131

3232
@staticmethod
33-
def handle_model_change(i, sidebar_data):
33+
def change_btn_color(n_clicks):
34+
try:
35+
return [False] if n_clicks % 2 == 0 else [True]
36+
except:
37+
return [False]
38+
39+
@staticmethod
40+
def handle_model_change(i, sidebar_data, lock_zoom_clicks, graphs_relayout_data):
3441
model = {}
3542
pars = None
3643
result = []
@@ -44,7 +51,6 @@ def handle_model_change(i, sidebar_data):
4451
viz_kwargs = dict(
4552
labels=pars.labels,
4653
table_mod=7,
47-
max_y_axis=pars.max_y_axis,
4854
content=vis_content
4955
)
5056
result.extend(i.components["intro"].build(model, pars))
@@ -53,11 +59,42 @@ def handle_model_change(i, sidebar_data):
5359
if model:
5460
df = model.__dict__.get(df_key, None)
5561
result.extend(prepare_visualization_group(df, **viz_kwargs))
62+
63+
figures = [result[1], result[4], result[7]]
64+
65+
for n_clicks, relayout_data, figure in zip(lock_zoom_clicks, graphs_relayout_data, figures):
66+
if relayout_data:
67+
if n_clicks == None or n_clicks % 2 == 0:
68+
# Set plot_data figure coordinates
69+
if "xaxis.range[0]" in relayout_data:
70+
figure["layout"]["xaxis"]["range"] = [
71+
relayout_data["xaxis.range[0]"],
72+
relayout_data["xaxis.range[1]"]
73+
]
74+
if "yaxis.range[0]" in relayout_data:
75+
figure["layout"]["yaxis"]["range"] = [
76+
relayout_data["yaxis.range[0]"],
77+
relayout_data["yaxis.range[1]"]
78+
]
79+
5680
return result
5781

5882
def __init__(self, component_instance):
59-
def handle_model_change_helper(sidebar_mod, sidebar_data):
60-
return IndexCallbacks.handle_model_change(component_instance, sidebar_data)
83+
def handle_model_change_helper(
84+
sidebar_mod,
85+
new_admissions_lock_zoom,
86+
admitted_patients_lock_zoom,
87+
SIR_lock_zoom,
88+
sidebar_data,
89+
new_admissions_relayout_data,
90+
admitted_patients_relayout_data,
91+
SIR_relayout_data
92+
):
93+
# parameter order: Inputs (sidebar_mod and all lock_zooms) followed by States (sidebar_data and all relayout_datas)
94+
# Order matters; callback_wrapper passes in Inputs before States
95+
lock_zoom_clicks = [new_admissions_lock_zoom, admitted_patients_lock_zoom, SIR_lock_zoom]
96+
graphs_relayout_data = [new_admissions_relayout_data, admitted_patients_relayout_data, SIR_relayout_data]
97+
return IndexCallbacks.handle_model_change(component_instance, sidebar_data, lock_zoom_clicks, graphs_relayout_data)
6198

6299
super().__init__(
63100
component_instance=component_instance,
@@ -72,7 +109,12 @@ def handle_model_change_helper(sidebar_mod, sidebar_data):
72109
callback_fn=IndexCallbacks.toggle_tables
73110
),
74111
ChimeCallback( # If the parameters or model change, update the text
75-
changed_elements={"sidebar-store": "modified_timestamp"},
112+
changed_elements={
113+
"sidebar-store": "modified_timestamp",
114+
"new_admissions_lock_zoom": "n_clicks",
115+
"admitted_patients_lock_zoom": "n_clicks",
116+
"SIR_lock_zoom": "n_clicks"
117+
},
76118
dom_updates={
77119
"intro": "children",
78120
"new_admissions_graph": "figure",
@@ -85,8 +127,28 @@ def handle_model_change_helper(sidebar_mod, sidebar_data):
85127
"SIR_table": "children",
86128
"SIR_download": "href",
87129
},
88-
callback_fn=handle_model_change_helper,
130+
states={
131+
"new_admissions_graph": "relayoutData",
132+
"admitted_patients_graph": "relayoutData",
133+
"SIR_graph": "relayoutData"
134+
},
89135
stores=["sidebar-store"],
136+
callback_fn=handle_model_change_helper
137+
),
138+
ChimeCallback( # If user presses the Lock Zoom Button, update outline / solid color
139+
changed_elements={"new_admissions_lock_zoom": "n_clicks"},
140+
dom_updates={"new_admissions_lock_zoom": "outline"},
141+
callback_fn=IndexCallbacks.change_btn_color
142+
),
143+
ChimeCallback(
144+
changed_elements={"admitted_patients_lock_zoom": "n_clicks"},
145+
dom_updates={"admitted_patients_lock_zoom": "outline"},
146+
callback_fn=IndexCallbacks.change_btn_color
147+
),
148+
ChimeCallback(
149+
changed_elements={"SIR_lock_zoom": "n_clicks"},
150+
dom_updates={"SIR_lock_zoom": "outline"},
151+
callback_fn=IndexCallbacks.change_btn_color
90152
)
91153
]
92154
)
@@ -136,8 +198,7 @@ def update_parameters(i, *input_values) -> List[dict]:
136198
ventilated=Disposition.create(
137199
days=inputs_dict["ventilated_los"],
138200
rate=inputs_dict["ventilated_rate"] / 100,
139-
),
140-
max_y_axis=inputs_dict.get("max_y_axis_value", None),
201+
)
141202
)
142203
return [{"inputs_dict": inputs_dict, "parameters": parameters_serializer(pars)}]
143204

src/chime_dash/app/services/plotting.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,13 @@
77
from pandas import DataFrame
88

99

10-
def plot_dataframe(
11-
dataframe: DataFrame,
12-
max_y_axis: int = None,
13-
) -> Dict[str, Any]:
10+
def plot_dataframe(dataframe: DataFrame) -> Dict[str, Any]:
1411
"""Returns dictionary used for plotly graphs
1512
1613
Arguments:
1714
dataframe: The dataframe to plot. Plots all columns as y, index is x.
18-
max_y_axis: Maximal value on y-axis.
1915
"""
2016

21-
if max_y_axis is None:
22-
yaxis = {}
23-
else:
24-
yaxis = {"range": (0, max_y_axis), "autorange": False}
25-
2617
return {
2718
"data": [
2819
{
@@ -34,7 +25,8 @@ def plot_dataframe(
3425
for col in dataframe.columns
3526
],
3627
"layout": {
37-
"yaxis": yaxis,
28+
"xaxis": {},
29+
"yaxis": {},
3830
"legend": {"orientation": "h"},
3931
},
4032
}

src/chime_dash/app/templates/en/sidebar.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@ ventilated_los: Average days on ventilator
2323
display_parameters: Display Parameters
2424
n_days: Number of days to project
2525
current_date: Current date (default is today)
26-
max_y_axis: Set the Y-axis on graphs to a static
27-
max_y_axis_value: Maximal y-axis value (leave blank for no max value)
2826
show_tables: Show tables with data
2927
show_tool_details: Show more info about this tool
3028
show_additional_projections: Show additional projections

src/chime_dash/app/utils/__init__.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ def parameters_deserializer(p_json: str):
8282
date_first_hospitalized=dates["date_first_hospitalized"],
8383
doubling_time=values["doubling_time"],
8484
market_share=values["market_share"],
85-
max_y_axis=values["max_y_axis"],
8685
mitigation_date=dates["mitigation_date"],
8786
n_days=values["n_days"],
8887
population=values["population"],
@@ -116,8 +115,6 @@ def prepare_visualization_group(df: DataFrame = None, **kwargs) -> List[Any]:
116115
df: The Dataframe to plot
117116
content: Dict[str, str]
118117
Mapping for translating columns and index.
119-
max_y_axis: int
120-
Maximal value on y-axis
121118
labels: List[str]
122119
Columns to display
123120
table_mod: int
@@ -144,11 +141,9 @@ def prepare_visualization_group(df: DataFrame = None, **kwargs) -> List[Any]:
144141
day_column = content.get(day_column, day_column)
145142

146143
plot_data = plot_dataframe(
147-
df.dropna().set_index(date_column).drop(columns=[day_column]),
148-
max_y_axis=kwargs.get("max_y_axis", None),
144+
df.dropna().set_index(date_column).drop(columns=[day_column])
149145
)
150146

151-
152147
# translate back for backwards compability of build_table
153148
column_map = {day_column: "day", date_column: "date"}
154149
table = (

src/chime_dash/app/utils/callbacks.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ def __init__(self,
1212
dom_updates: Mapping = None,
1313
dom_states: Mapping = None,
1414
stores: Iterable = None,
15+
states: Mapping = None,
1516
memoize: bool = True
1617
):
1718
self.inputs = [
@@ -20,6 +21,7 @@ def __init__(self,
2021
]
2122
self.outputs = []
2223
self.stores = []
24+
self.states = []
2325
self.callback_fn = callback_fn
2426
self.memoize = memoize
2527
if dom_updates:
@@ -34,21 +36,26 @@ def __init__(self,
3436
)
3537

3638
if stores:
37-
self.stores.extend(
39+
self.states.extend(
3840
State(component_id=component_id, component_property="data")
3941
for component_id in stores
4042
)
43+
if states:
44+
self.states.extend(
45+
State(component_id=component_id, component_property=component_property)
46+
for component_id, component_property in states.items()
47+
)
4148

4249
def wrap(self, app: Dash):
4350
print(f'Registering callback: \nOutputs: \n{self.outputs}, \nInputs:\n{self.inputs}, \nStore: \n{self.stores} \nUsing: {self.callback_fn}\n\n')
4451
if self.memoize:
4552
@lru_cache(maxsize=32)
46-
@app.callback(self.outputs, self.inputs, self.stores)
53+
@app.callback(self.outputs, self.inputs, self.states)
4754
def callback_wrapper(*args, **kwargs):
4855
print(str(self.callback_fn))
4956
return self.callback_fn(*args, **kwargs)
5057
else:
51-
@app.callback(self.outputs, self.inputs, self.stores)
58+
@app.callback(self.outputs, self.inputs, self.states)
5259
def callback_wrapper(*args, **kwargs):
5360
return self.callback_fn(*args, **kwargs)
5461

0 commit comments

Comments
 (0)