Hello everyone,
I have started using dash for couple for couple of weeks to bring some of my data scientist work to the next level.
I am currently working on a little (simple) application with many user input based features. Overall what my application is doing / needs to do is the following:
1: The application is initialized with some features: a title, a drop-down list, a date-range selector, a container
output = []
output.extend([html.H1(children='Custom time-series visualization',
style={'textAlign': 'center',
#'color': colors['text']
}
),
html.Div(children='Dynamic and interactive visualization is awesome!',
style={'textAlign': 'center',
#'color': colors['text']
}
),
html.Div([
dcc.Dropdown(id='dropdown-input-filename',
options=[{'label': i, 'value': i} for i in files],
searchable=False,
clearable=True,
placeholder="Select an input configuration file",
),],
style={"width": "25%",
"padding": 50,
},
),
html.Div(children=[html.Div(children="Date Range",
className="menu-title"
),
dcc.DatePickerRange(id="date-range",
#min_date_allowed=cache_data_object.data.index.min().date(),
#max_date_allowed=cache_data_object.data.index.max().date(),
#start_date=cache_data_object.data.index.min().date(),
#end_date=cache_data_object.data.index.max().date(),
),],
),
html.Div(id='container', children=[]),
])
app.layout = html.Div(id="layout-div", children=output)
2: The user selects a configuration file from a drop-down list which updates the content of the Div with id:"container"
@app.callback(
# Ouputs
[
Output("container", "children"),
],
# Inputs
[Input("dropdown-input-filename", "value")],
# State
[
State("container", "children"),
],
# Properties
prevent_initial_call=True,
)
def initialize_layout(file_name, children):
if file_name is None:
children=[]
else:
CWD = os.getcwd()
input_data_path = CWD + "/Inputs"
file_path = input_data_path + "/{0}".format(file_name)
cache_data_object = DataCachingClassV2(input_csv=file_path, start_date=default_start_date, stop_date=default_stop_date)
blocks_count = 1
for block in cache_data_object.data.keys():
figure = make_graph_figure(cache_data_object=cache_data_object, block=block, block_number=blocks_count, start_date=default_start_date, stop_date=default_stop_date)
new_element = html.Div(children=figure)
children.append(new_element)
blocks_count += 1
return children
3: The aforementioned triggers the generation/building of the application layout with several figures.
The problem I am facing is updating the figures with the dates selected afterwards from the date-range selector. I use a custom made class (DataCachingClassV2()) which queries the data from a DB according to the configuration file chosen by the user, formats the data and store it in a nice data-structure at the class level. This results in a cache_data_object which I can’t access anymore once this initial callback has been performed.
Therefore, I am wondering how I could structure my application to update the dynamically generated figures (I do not know how many of them will be before hand), based on the date-range selector?
Maybe I am doing everything completely wrong, so I am also open to re-thinking the entire structure of my application.
EDIT 1
For those wondering what the make_graph_figure function looks like, here it is
def make_graph_figure(cache_data_object, block, block_number, start_date, stop_date):
### Unpacking data
temp_data = cache_data_object.data[block]["DATA"][default_start_date:default_stop_date]
### Data binning
y_axis_bins = {"0_10": {"channels": [],
"min": pd.Series(dtype=float),
"max": pd.Series(dtype=float),
}, # order 0
"10_100": {"channels": [],
"min": pd.Series(dtype=float),
"max": pd.Series(dtype=float),
}, # order 1
"100_1k": {"channels": [],
"min": pd.Series(dtype=float),
"max": pd.Series(dtype=float),
}, # order 2
"1k_10k": {"channels": [],
"min": pd.Series(dtype=float),
"max": pd.Series(dtype=float),
}, # order 3
"10k_100k": {"channels": [],
"min": pd.Series(dtype=float),
"max": pd.Series(dtype=float),
}, # order 4
"100k_1M": {"channels": [],
"min": pd.Series(dtype=float),
"max": pd.Series(dtype=float),
}, # order 5
"1M_": {"channels": [],
"min": pd.Series(dtype=float),
"max": pd.Series(dtype=float),
}, # order 6 or more
}
order_to_key_dict = {0: "0_10",
1: "10_100",
2: "100_1k",
3: "1k_10k",
4: "10k_100k",
5: "100k_1M",
6: "1M_",
}
for time_serie_name, time_serie_properties in temp_data.describe().items():
max_val = time_serie_properties["max"]
min_val = time_serie_properties["min"]
mean_val = time_serie_properties["mean"]
OOM = None
# Identify order of magnitude
if max_val <= 0:
OOM = 0
else:
OOM = order_of_magnitude.order_of_magnitude(round(max_val,1))
y_axis_bins[order_to_key_dict[OOM]]["min"] = y_axis_bins[order_to_key_dict[OOM]]["min"].append(pd.Series(float(min_val)))
y_axis_bins[order_to_key_dict[OOM]]["max"] = y_axis_bins[order_to_key_dict[OOM]]["max"].append(pd.Series(float(max_val)))
plotly_data = []
plotly_layout = go.Layout(hovermode = 'x')
layout_kwargs = {"title": "Block {0} figure".format(block_number),
'xaxis': {'domain': [0, 0.7]},
"colorway": px.colors.qualitative.D3}
y_axes_counter = 0
for time_serie_name, time_serie_data in temp_data.items():
OOM = order_of_magnitude.order_of_magnitude(max(time_serie_data))
y_min_limit = y_axis_bins[order_to_key_dict[OOM]]["min"].min()
y_max_limit = y_axis_bins[order_to_key_dict[OOM]]["max"].max()
axis_name = 'yaxis' + str(y_axes_counter + 1) * (y_axes_counter > 0)
yaxis = 'y' + str(y_axes_counter + 1) * (y_axes_counter > 0)
plotly_data.append(go.Scatter(
x=temp_data.index,
y=time_serie_data.values,
name=time_serie_name,
)
)
layout_kwargs[axis_name] = {#"title": axis_name,
#"titlefont": dict(
#color="#9467bd"
#),
"tickfont": dict(
#color=px.colors.qualitative.Plotly[y_axes_counter]
color=px.colors.qualitative.D3[y_axes_counter]
),
#"anchor": "free",
#"side": "right",
"position": 1 - y_axes_counter * 0.04,
#"range": [min(time_serie_data.values) * 0.9, max(time_serie_data.values) * 1.1]
"range": [y_min_limit * 0.9, y_max_limit * 1.1],
"showline": True
}
plotly_data[y_axes_counter]['yaxis'] = yaxis
if y_axes_counter > 0:
layout_kwargs[axis_name]['overlaying'] = 'y'
y_axes_counter += 1
temp_figure = go.Figure(data=plotly_data, layout=go.Layout(**layout_kwargs))
### Figure creation
figure = dcc.Graph(id={
"name": "block-{0}-figure".format(block_number),
"type": "dates-dependant-figure",
"start_date": start_date,
"stop_date": stop_date
},
figure=temp_figure,
config={"displaylogo": False,
"responsive": True
}
)
return figure
Thank you for your time, help and thoughts
R3s0luti0n