Skip to content

Commit 2ec23ff

Browse files
author
Eric Smyth
committed
Refactor callbacks into services. Create Page class
1 parent 677f96c commit 2ec23ff

File tree

6 files changed

+220
-179
lines changed

6 files changed

+220
-179
lines changed

src/chime_dash/app/components/__init__.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
have callbacks.
55
66
To add or remove components, adjust the `setup`.
7-
If callbacks are present, also adjust `CALLBACK_INPUTS`, `CALLBACK_OUTPUTS` and
8-
`callback_body`.
97
"""
108
from collections import OrderedDict
119

@@ -57,8 +55,7 @@ def get_html(self):
5755
children=self.components["navbar"].html + [
5856
Div(
5957
className="app-content",
60-
children=
61-
self.components["sidebar"].html
58+
children=self.components["sidebar"].html
6259
+ self.components["index"].html
6360
)
6461
]
@@ -67,8 +64,7 @@ def get_html(self):
6764
def get_html_old(self):
6865
"""Glues individual setup components together
6966
"""
70-
return Div(children=
71-
self.components["navbar"].html
67+
return Div(children=self.components["navbar"].html
7268
+ [Container(
7369
children=Row(self.components["sidebar"].html + [Div(
7470
id="page-wrapper",

src/chime_dash/app/components/base.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,17 @@
44
55
#! candidate for moving into utils/components
66
"""
7-
from typing import List, Dict, Any, Union
7+
from typing import List, Dict, Union
88

99
from abc import ABC
1010

11-
from dash import Dash
1211
from dash.development.base_component import ComponentMeta
1312
from dash_html_components import Div
1413

1514
from penn_chime.parameters import Parameters
1615
from penn_chime.settings import DEFAULTS
1716

1817
from chime_dash.app.utils.templates import read_localization_yml, read_localization_markdown
19-
from chime_dash.app.utils.callbacks import ChimeCallback, register_callbacks
2018

2119

2220
class Component(ABC):
@@ -32,19 +30,13 @@ class Component(ABC):
3230
external_stylesheets: List[str] = []
3331
external_scripts: List[str] = []
3432

35-
def __init__(
36-
self,
37-
language: str = "en",
38-
defaults: Parameters = DEFAULTS,
39-
callbacks: List[ChimeCallback] = None
40-
):
33+
def __init__(self, language: str = "en", defaults: Parameters = DEFAULTS):
4134
"""Initializes the component
4235
"""
4336
self.language = language
4437
self.defaults = defaults
4538
self._content = None
4639
self._html = None
47-
register_callbacks(callbacks)
4840

4941
def get_html(self) -> List[ComponentMeta]: # pylint: disable=R0201
5042
"""Function which is called to render html elements.
@@ -97,6 +89,14 @@ def content(self) -> Union[str, Dict[str, str], None]:
9789
return self._content
9890

9991

92+
class Page(Component):
93+
callbacks_cls = None
94+
95+
def __init__(self, language: str = "en", defaults: Parameters = DEFAULTS):
96+
super().__init__(language, defaults)
97+
self.callbacks_cls(self)
98+
99+
100100
class HTMLComponentError(Exception):
101101
"""Custom exception for errors when rendering component html.
102102

src/chime_dash/app/pages/index.py

Lines changed: 8 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,29 @@
11
"""pages/index
22
Homepage
33
"""
4-
54
from collections import OrderedDict
65

76
from dash_html_components import Main
87
from dash_bootstrap_components import Container
98

10-
from chime_dash.app.components.base import Component
9+
from chime_dash.app.components.base import Page
1110
from chime_dash.app.components.footer import Footer
1211
from chime_dash.app.components.header import Header
1312
from chime_dash.app.components.tool_details import ToolDetails
1413
from chime_dash.app.components.intro import Intro
1514
from chime_dash.app.components.visualizations import Visualizations
15+
from chime_dash.app.services.callbacks import IndexCallbacks
1616

17-
from chime_dash.app.utils import get_n_switch_values, parameters_deserializer, prepare_visualization_group
18-
from chime_dash.app.utils.callbacks import ChimeCallback
19-
20-
from penn_chime.models import SimSirModel
2117

22-
23-
class Index(Component):
18+
class Index(Page):
2419
"""
2520
"""
26-
27-
@staticmethod
28-
def toggle_tool_details(switch_value):
29-
return get_n_switch_values(switch_value, 1)
30-
31-
@staticmethod
32-
def toggle_tables(switch_value):
33-
return get_n_switch_values(switch_value, 3)
21+
callbacks_cls = IndexCallbacks
3422

3523
def __init__(self, language, defaults):
3624
"""
3725
"""
38-
39-
def handle_model_change_helper(pars_json):
40-
model = {}
41-
pars = None
42-
result = []
43-
viz_kwargs = {}
44-
if pars_json:
45-
pars = parameters_deserializer(pars_json)
46-
model = SimSirModel(pars)
47-
viz_kwargs = dict(
48-
labels=pars.labels,
49-
table_mod=7,
50-
max_y_axis=pars.max_y_axis,
51-
)
52-
result.extend(self.components["intro"].build(model, pars))
53-
result.extend(self.components["tool_details"].build(model, pars))
54-
for df_key in ["admits_df", "census_df", "sim_sir_w_date_df"]:
55-
df = None
56-
if model:
57-
df = model.__dict__.get(df_key, None)
58-
result.extend(prepare_visualization_group(df, **viz_kwargs))
59-
return result
60-
61-
super().__init__(language, defaults, [
62-
ChimeCallback( # If user toggles show_additional_projections, show/hide the additional intro content
63-
changed_elements=OrderedDict(show_tool_details="value"),
64-
dom_updates=OrderedDict(more_intro_wrapper="hidden"),
65-
callback_fn=Index.toggle_tool_details
66-
),
67-
ChimeCallback( # If user toggles show_tables, show/hide tables
68-
changed_elements=OrderedDict(show_tables="value"),
69-
dom_updates=OrderedDict(
70-
SIR_table_container="hidden",
71-
new_admissions_table_container="hidden",
72-
admitted_patients_table_container="hidden",
73-
),
74-
callback_fn=Index.toggle_tables
75-
),
76-
ChimeCallback( # If the parameters or model change, update the text
77-
changed_elements=OrderedDict(pars="children"),
78-
dom_updates=OrderedDict(
79-
intro="children",
80-
more_intro="children",
81-
new_admissions_graph="figure",
82-
new_admissions_table="children",
83-
new_admissions_download="href",
84-
admitted_patients_graph="figure",
85-
admitted_patients_table="children",
86-
admitted_patients_download="href",
87-
SIR_graph="figure",
88-
SIR_table="children",
89-
SIR_download="href",
90-
),
91-
callback_fn=handle_model_change_helper
92-
)
93-
])
26+
super().__init__()
9427
self.components = OrderedDict(
9528
header=Header(language, defaults),
9629
intro=Intro(language, defaults),
@@ -108,17 +41,14 @@ def get_html(self):
10841
"marginLeft": "320px",
10942
"marginTop": "56px"
11043
},
111-
children=
112-
[Container(
113-
children=
114-
self.components["header"].html
44+
children=[Container(
45+
children=self.components["header"].html
11546
+ self.components["intro"].html
11647
+ self.components["tool_details"].html
11748
)]
11849
+ self.components["visualizations"].html
11950
+ [Container(
120-
children=
121-
self.components["footer"].html
51+
children=self.components["footer"].html
12252
)],
12353
)
12454

src/chime_dash/app/pages/sidebar.py

Lines changed: 22 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,29 @@
11
"""components/sidebar
22
Initializes the side bar containing the various inputs for the model
33
4-
#! _INPUTS should be considered for moving else where
4+
#! _SIDEBAR_ELEMENTS should be considered for moving else where
55
"""
6-
from dash_html_components import Nav, Div
7-
8-
from typing import List, Dict, Any, Tuple
6+
from typing import List
97
from collections import OrderedDict
108
from datetime import date, datetime
119

1210
from dash.development.base_component import ComponentMeta
13-
from dash_html_components import Nav, Div, Hr
14-
15-
from penn_chime.parameters import Parameters, Disposition
11+
from dash_html_components import Nav, Div
1612

17-
from chime_dash.app.components.base import Component
18-
from chime_dash.app.utils import parameters_serializer
19-
from chime_dash.app.utils.callbacks import ChimeCallback
13+
from chime_dash.app.components.base import Page
14+
from chime_dash.app.utils import ReadOnlyDict
2015
from chime_dash.app.utils.templates import (
2116
create_switch_input,
2217
create_number_input,
2318
create_date_input,
2419
create_header,
2520
)
21+
from chime_dash.app.services.callbacks import SidebarCallbacks
2622

2723
FLOAT_INPUT_MIN = 0.001
2824
FLOAT_INPUT_STEP = "any"
2925

30-
_INPUTS = OrderedDict(
26+
_SIDEBAR_ELEMENTS = ReadOnlyDict(OrderedDict(
3127
###
3228
hospital_parameters={"type": "header", "size": "h3"},
3329
population={"type": "number", "min": 1, "step": 1},
@@ -94,94 +90,37 @@
9490
max_y_axis_value={"type": "number", "min": 10, "step": 10, "value": None},
9591
show_tables={"type": "switch", "value": False},
9692
show_tool_details={"type": "switch", "value": False},
97-
)
93+
))
9894

99-
# Different kind of inputs store different kind of "values"
100-
# This tells the callback output for which field to look
101-
_PROPERTY_OUTPUT_MAP = {
102-
"number": "value",
103-
"date": "date",
104-
}
10595

106-
107-
class Sidebar(Component):
96+
class Sidebar(Page):
10897
"""Sidebar to the left of the screen
10998
contains the various inputs used to interact
11099
with the model.
111100
"""
101+
callbacks_cls = SidebarCallbacks
112102

113103
# localization temp. for widget descriptions
114104
localization_file = "sidebar.yml"
115-
116-
@staticmethod
117-
def get_ordered_input_keys():
118-
return [key for key in _INPUTS if _INPUTS[key]["type"] not in ("header", )]
119-
120-
@staticmethod
121-
def get_formated_values(input_values):
122-
result = dict(zip(Sidebar.get_ordered_input_keys(), input_values))
123-
# todo remove this hack needed because of how Checklist type used for switch input returns values
124-
for key in _INPUTS:
125-
if _INPUTS[key]["type"] == "switch":
126-
value = False
127-
if result[key] == [True]:
128-
value = True
129-
result[key] = value
130-
elif _INPUTS[key]["type"] == "date":
131-
value = result[key]
132-
result[key] = datetime.strptime(value, "%Y-%m-%d").date() if value else value
133-
return result
134-
135-
@staticmethod
136-
def update_parameters(*input_values, **kwargs) -> List[str]:
137-
"""Reads html form outputs and converts them to a parameter instance
138-
139-
Returns Parameters
140-
"""
141-
inputs_dict = Sidebar.get_formated_values(input_values)
142-
dt = inputs_dict["doubling_time"] if inputs_dict["doubling_time"] else None
143-
dfh = inputs_dict["date_first_hospitalized"] if not dt else None
144-
pars = Parameters(
145-
population=inputs_dict["population"],
146-
current_hospitalized=inputs_dict["current_hospitalized"],
147-
date_first_hospitalized=dfh,
148-
doubling_time=dt,
149-
hospitalized=Disposition(
150-
inputs_dict["hospitalized_rate"] / 100, inputs_dict["hospitalized_los"]
151-
),
152-
icu=Disposition(inputs_dict["icu_rate"] / 100, inputs_dict["icu_los"]),
153-
infectious_days=inputs_dict["infectious_days"],
154-
market_share=inputs_dict["market_share"] / 100,
155-
n_days=inputs_dict["n_days"],
156-
relative_contact_rate=inputs_dict["relative_contact_rate"] / 100,
157-
ventilated=Disposition(
158-
inputs_dict["ventilated_rate"] / 100, inputs_dict["ventilated_los"]
159-
),
160-
max_y_axis=inputs_dict.get("max_y_axis_value", None),
161-
)
162-
return [parameters_serializer(pars)]
163-
164-
def __init__(self, language, defaults):
165-
changed_elements = OrderedDict(
166-
(key, _PROPERTY_OUTPUT_MAP.get(_INPUTS[key]["type"], "value"))
167-
for key in _INPUTS
168-
if _INPUTS[key]["type"] not in ("header",)
169-
)
170-
171-
input_change_callback = ChimeCallback(
172-
changed_elements=changed_elements,
173-
dom_updates=OrderedDict(pars="children"),
174-
callback_fn=Sidebar.update_parameters,
175-
)
176-
super().__init__(language, defaults, [input_change_callback])
105+
# Different kind of inputs store different kind of "values"
106+
# This tells the callback output for which field to look
107+
input_type_map = ReadOnlyDict(OrderedDict(
108+
(key, value["type"])
109+
for key, value in _SIDEBAR_ELEMENTS.items()
110+
if value["type"] not in ("header",)
111+
))
112+
input_value_map = ReadOnlyDict(OrderedDict(
113+
(key, {"number": "value", "date": "date"}.get(value, "value"))
114+
for key, value in input_type_map.items()
115+
))
177116

178117
def get_html(self) -> List[ComponentMeta]:
179118
"""Initializes the view
180119
"""
181120
elements = [
182121
Div(id='pars', style={'display': 'none'})
183122
]
184-
for idx, data in _INPUTS.items():
123+
for idx, data in _SIDEBAR_ELEMENTS.items():
185124
if data["type"] == "number":
186125
element = create_number_input(idx, data, self.content, self.defaults)
187126
elif data["type"] == "switch":
@@ -216,4 +155,3 @@ def get_html(self) -> List[ComponentMeta]:
216155
)
217156

218157
return [sidebar]
219-

0 commit comments

Comments
 (0)