-
-
Notifications
You must be signed in to change notification settings - Fork 73
/
Copy path13_coarse_fine.py
188 lines (159 loc) · 6.67 KB
/
13_coarse_fine.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
"""Dash file parquet visualization app example with a coarse and fine-grained view.
In this use case, we have dropdowns which allows end-users to select multiple
parquet files, which are visualized using FigureResampler after clicking on a button.
There a two graphs displayed; a coarse and a dynamic graph. Interactions with the
coarse graph will affect the dynamic graph it's shown range. Note that the autosize
of the coarse graph is not linked.
TODO: add an rectangle on the coarse graph
"""
__author__ = "Jonas Van Der Donckt"
from pathlib import Path
from typing import List
import dash_bootstrap_components as dbc
import plotly.graph_objects as go
from dash import Input, Output, State, callback_context, dcc, html, no_update
from dash_extensions.enrich import (
DashProxy,
ServersideOutput,
ServersideOutputTransform,
)
from trace_updater import TraceUpdater
from utils.callback_helpers import get_selector_states, multiple_folder_file_selector
from utils.graph_construction import visualize_multiple_files
from plotly_resampler import FigureResampler
# --------------------------------------Globals ---------------------------------------
app = DashProxy(
__name__,
suppress_callback_exceptions=True,
external_stylesheets=[dbc.themes.LUX],
transforms=[ServersideOutputTransform()],
)
# --------- File selection configurations ---------
name_folder_list = [
{
# the key-string below is the title which will be shown in the dash app
"example data": {"folder": Path(__file__).parent.parent.joinpath("data")},
"other folder": {"folder": Path(__file__).parent.parent.joinpath("data")},
},
# NOTE: A new item om this level creates a new file-selector card.
# { "PC data": { "folder": Path("/home/jonas/data/wesad/empatica/") } }
# TODO: change the folder path above to a location where you have some
# `.parquet` files stored on your machine.
]
# --------- DASH layout logic ---------
def serve_layout() -> dbc.Container:
"""Constructs the app's layout.
Returns
-------
dbc.Container
A Container withholding the layout.
"""
return dbc.Container(
[
dbc.Container(
html.H1("Data visualization - coarse & dynamic graph"),
style={"textAlign": "center"},
),
html.Hr(),
dbc.Row(
[
# Add file selection layout (+ assign callbacks)
dbc.Col(
multiple_folder_file_selector(
app, name_folder_list, multi=False
),
md=2,
),
# Add the graphs, the dcc.Store (for serialization) and the
# TraceUpdater (for efficient data updating) components
dbc.Col(
[
# The coarse graph whose updates will fetch data for the
dcc.Graph(
id="coarse-graph",
figure=go.Figure(),
config={"modeBarButtonsToAdd": ["drawrect"]},
),
html.Br(),
dcc.Graph(id="plotly-resampler-graph", figure=go.Figure()),
dcc.Loading(dcc.Store(id="store")),
TraceUpdater(
id="trace-updater", gdID="plotly-resampler-graph"
),
],
md=10,
),
],
align="center",
),
],
fluid=True,
)
app.layout = serve_layout()
# ------------------------------------ DASH logic -------------------------------------
# --------- graph construction logic + callback ---------
@app.callback(
[
Output("coarse-graph", "figure"),
Output("plotly-resampler-graph", "figure"),
ServersideOutput("store", "data"),
],
[Input("plot-button", "n_clicks"), *get_selector_states(len(name_folder_list))],
prevent_initial_call=True,
)
def construct_plot_graph(n_clicks, *folder_list):
it = iter(folder_list)
file_list: List[Path] = []
for folder, files in zip(it, it):
if not all((folder, files)):
continue
else:
files = [files] if not isinstance(files, list) else file_list
for file in files:
file_list.append((Path(folder).joinpath(file)))
ctx = callback_context
if len(ctx.triggered) and "plot-button" in ctx.triggered[0]["prop_id"]:
if len(file_list):
# Create two graphs, a dynamic plotly-resampler graph and a coarse graph
dynamic_fig: FigureResampler = visualize_multiple_files(file_list)
coarse_fig: go.Figure = go.Figure(
FigureResampler(dynamic_fig, default_n_shown_samples=3_000)
)
coarse_fig.update_layout(title="<b>coarse view</b>", height=250)
coarse_fig.update_layout(margin=dict(l=0, r=0, b=0, t=40, pad=10))
coarse_fig.update_layout(showlegend=False)
coarse_fig._config = coarse_fig._config.update(
{"modeBarButtonsToAdd": ["drawrect"]}
)
dynamic_fig._global_n_shown_samples = 1000
dynamic_fig.update_layout(title="<b>dynamic view<b>", height=450)
dynamic_fig.update_layout(margin=dict(l=0, r=0, b=40, t=40, pad=10))
dynamic_fig.update_layout(
legend=dict(
orientation="h", y=-0.11, xanchor="right", x=1, font_size=18
)
)
return coarse_fig, dynamic_fig, dynamic_fig
else:
return no_update
# Register the graph update callbacks to the layout
@app.callback(
Output("trace-updater", "updateData"),
Input("coarse-graph", "relayoutData"),
Input("plotly-resampler-graph", "relayoutData"),
State("store", "data"),
prevent_initial_call=True,
)
def update_dynamic_fig(coarse_grained_relayout, fine_grained_relayout, fr_fig):
if fr_fig is None: # When the figure does not exist -> do nothing
return no_update
ctx = callback_context
trigger_id = ctx.triggered[0].get("prop_id", "").split(".")[0]
if trigger_id == "plotly-resampler-graph":
return fr_fig.construct_update_data(fine_grained_relayout)
elif trigger_id == "coarse-graph":
return fr_fig.construct_update_data(coarse_grained_relayout)
return no_update
# --------------------------------- Running the app ---------------------------------
if __name__ == "__main__":
app.run_server(debug=True, port=9023)