-
Notifications
You must be signed in to change notification settings - Fork 1
/
dashboard.py
172 lines (149 loc) · 6.05 KB
/
dashboard.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
"""Code to generate time-series visualization dashboard for the ETRI data."""
import sys
from datetime import datetime
from inspect import getsourcefile
from pathlib import Path
from typing import List, Tuple
import dash
import dash_bootstrap_components as dbc
import pandas as pd
import plotly.graph_objs as go
from dash.dependencies import Input, Output, State
from dash_extensions.enrich import (
DashProxy,
ServersideOutput,
ServersideOutputTransform,
)
from plotly.subplots import make_subplots
from plotly_resampler import FigureResampler
from plotly_resampler.aggregation import MinMaxLTTB
# fmt: off
# isort: off
sys.path.append(str(Path(getsourcefile(lambda: 0)).parent.parent.parent.absolute()))
from code_utils.etri.visualization import add_etri_timeline_to_fig # noqa: E402
from code_utils.path_conf import processed_etri_path # noqa: E402
from code_utils.utils.dash_utils import serve_layout, get_selector_states, _create_subfolder_dict # noqa: E402
# isort: on
# fmt: on
# read the label file, which stems from the parsed ETRI notebook
df_labels = pd.read_parquet(processed_etri_path / "labels.parquet")
# create the Dash app
app = DashProxy(
__name__,
external_stylesheets=[dbc.themes.LUX],
transforms=[ServersideOutputTransform()],
)
# each list is a type
name_folders_list = [
{"etri": {"user_folder": processed_etri_path}},
# { "etri": {"user_folder": processed_etri_path}, },
]
# Construct the layout
app.layout = serve_layout(
app,
title="plotly-resampler ETRI dashboard",
checklist_options=["show timeline"],
name_folders_list=name_folders_list,
)
selector_states = get_selector_states(name_folders_list)
# fmt: off
# --------------------------------- Visualization ---------------------------------
def plot_multi_sensors(
selected_items,
fold_usr_subf_start_end_sens_lst: List[
Tuple[str, str, str, str, str, List[str]]
],
) -> FigureResampler:
selected_items = [i.strip() for i in selected_items]
# create the figure
prev_rows: List[str] = []
# Add additional rows to the figure based on the selecton boxes
if "show timeline" in selected_items:
prev_rows += ["timeline"]
sensors_names = [
" ".join([Path(f).name, sensor])
for f, _, _, _, _, sensors in fold_usr_subf_start_end_sens_lst
for sensor in (sensors if isinstance(sensors, list) else [])
]
total_rows: int = len(prev_rows) + len(sensors_names)
kwargs = {"vertical_spacing": 0.15 / total_rows} if total_rows >= 2 else {}
fig = FigureResampler(
make_subplots(
**dict(rows=total_rows, cols=1, shared_xaxes=True, **kwargs),
subplot_titles=prev_rows + sensors_names,
),
default_downsampler=MinMaxLTTB(parallel=True),
)
fig.update_layout(height=min(1000, 400 * total_rows), template="plotly_white")
# 0. Visualize the ETRI timeline
row_idx = 1
if "show timeline" in selected_items:
df_labels_user = df_labels[df_labels["user"] == fold_usr_subf_start_end_sens_lst[0][1] ]
add_etri_timeline_to_fig(fig, df_labels_user, row=row_idx)
row_idx += 1
# 1. Visualize the sensor data
for (fold, usr, subf, start, end, sensors) in fold_usr_subf_start_end_sens_lst:
sensors = [] if not isinstance(sensors, list) else sensors
for sensor in sensors:
folder_subfolder_dict = _create_subfolder_dict(subf)
sensor_path = Path(fold) / usr / folder_subfolder_dict.get(fold, "")
df_list = []
for dates in pd.date_range(
datetime.strptime(start, "%Y_%m_%d").date(),
datetime.strptime(end, "%Y_%m_%d").date(),
freq="D",
):
sensor_files = list(sensor_path.glob(f"{sensor}_{dates.strftime('%Y_%m_%d')}*"))
if len(sensor_files) != 1:
continue
sensor_file: Path = sensor_files[0]
df_sensor = pd.read_parquet(sensor_file)
if "timestamp" in df_sensor.columns:
df_sensor = df_sensor.set_index("timestamp")
df_list.append(df_sensor)
df_sensor = pd.concat(df_list)
for col in sorted(set(df_sensor.columns.values).difference(["index", "timestamp"])):
print(f"{col}" + f" {len(df_sensor[col]):,} " + "-" * 30)
print(col, df_sensor[col].dtype)
fig.add_trace(
trace=go.Scattergl(x=[], y=[], name=col),
row=row_idx,
col=1,
hf_x=df_sensor[col].index,
hf_y=df_sensor[col],
)
row_idx += 1
return fig
# --------------------------------- Callbacks ---------------------------------
# -------- plot or update graph ---------
@app.callback(
Output("resampled-graph", "figure"),
ServersideOutput("store", "data"),
[
Input("plot-button", "n_clicks"),
State("checklist", "value"),
State("start-date", "value"),
State("end-date", "value"),
*selector_states,
],
)
def plot_or_update_graph(_, selected_items, start_date, end_date, *folder_list):
selected_items = [] if selected_items is None else selected_items
it = iter(folder_list)
folder_user_day_sensor_list = []
for folder, user, subfolder, sensors in zip(it, it, it, it):
if not all((folder, user, start_date, end_date)):
continue
else:
folder_user_day_sensor_list.append((folder, user, subfolder, start_date, end_date, sensors))
ctx = dash.callback_context
if (len(ctx.triggered) and "plot-button" in ctx.triggered[0]["prop_id"] and len(folder_user_day_sensor_list)):
fig = plot_multi_sensors(
selected_items=selected_items,
fold_usr_subf_start_end_sens_lst=folder_user_day_sensor_list,
)
return fig, fig
return dash.no_update, dash.no_update
# ---------------------------------- start server ----------------------------------
if __name__ == "__main__":
app.run_server(port=8022, host="0.0.0.0", debug=True)