Welcome to Dash Crush, your walk-through tutorial to advanced interactive Data Viz dashboards using Dash Plotly.
In the previous parts, we learned how to create dynamic layouts and retrieve the state from them.
In this chapter, we will expand the topic with serialization.
TL;DR
In this chapter:
- How to save the state of the app to a file
- How to download the file from the server
Introduction
Dash is a fantastic tool for creating interactive data-centric apps. However, in its free form, it is not a tool for collaboration and sharing your results with the audience or co-workers. The Enterprise version of Dash implements the snapshot mechanism, allowing preserving and sharing the state of the app. Nevertheless, it is a paid solution. (See https://plotly.com/dash/snapshot-engine/ for details).
In this chapter, we will learn how to implement preserving the state of your app (or serialization) using JSON files.
The code
We will reuse the code from the previous chapter and add a serialization mechanism on top of it.
Install packages
Create and activate your virtual environment, and install the following packages:
pip install dash pandas dash-bootstrap-components colorlover
Layout
Our layout remains mostly unchanged. We will add only a JSON export button to the sidebar.
Sidebar
We will add a "Download config" button, dcc.Store
for the intermediate storage of config, and dcc.Download
component for getting a file to a local machine from a server. The code will be located in src/layout/sidebar.py
# src/layout/sidebar.py
from dash import html, dcc
import dash_bootstrap_components as dbc
from src.layout.grid import spacer
def sidebar_menu():
"""Sidebar menu"""
return html.Div(
[
spacer(10),
_buttons(),
]
)
def _buttons():
return html.Div(
[
dbc.Button(
[html.I(className="fi-save"), " Download config"],
id="download_button",
color="primary",
outline=True,
),
dbc.Button("Plot", id="plot_button", color="primary"),
dcc.Store(id="save_config_store", data={}),
dcc.Download(id="save_config_download"),
],
className="d-grid gap-2",
)
More details about downloading files can be found in the Dash documentation: https://dash.plotly.com/dash-core-components/download
Callbacks
In the usual case, not all components require serialization. Some may depend on others and be set in a callback, so it may be better to serialize only a subset of a component that is sufficient to restore the state of the app. Of course, how to proceed will depend on your use case. Let's define the config structure for easier tracking of which components we want to serialize.
Default config
Let's add the following content to src/utils/config.py
:
# src/utils/config.py
from src.layout.dropdowns import BULKS
JSON_VERSION = 1
DEFAULT_OPTIONS = {
"substrate_value": "GaAs",
"substrate_options": BULKS,
}
DEFAULT_CONFIG = {
"version": JSON_VERSION,
"cards": [],
"options": deepcopy(DEFAULT_OPTIONS),
}
In our case, we would like to preserve the substrate dropdown and layer cards' state.
Additionally, as a rule of thumb, I add a version field to each config I ever create. If you bring your dashboard to production, share it with users, and foresee it may evolve, the version field may save you from hours of debugging later on.
Serialization
Let's create a callback that will serialize the state of our app to config dict. The code will be placed in src/callbacks/serializer.py
# src/callbacks/serializer.py
from copy import deepcopy
import json
from dash import dcc, Input, Output, State, ALL
from src.utils.config import build_layer_config, DEFAULT_OPTIONS, JSON_VERSION
def create_callbacks(app):
"""Create serialization callback"""
@app.callback(
Output("save_config_store", "data"),
Input("download_button", "n_clicks"),
State("bulk_material", "value"),
State("bulk_material", "options"),
State({"role": "material_dropdown", "index": ALL}, "value"),
State({"role": "material_dropdown", "index": ALL}, "id"),
State({"role": "param", "index": ALL, "name": ALL, "material": ALL}, "value"),
State({"role": "param", "index": ALL, "name": ALL, "material": ALL}, "id"),
prevent_initial_call=True,
)
def _serialize(
_, substrate, substrate_options, dropdowns, dropdown_ids, params, param_ids
): # pylint: disable=too-many-arguments
def _list(list_of_dicts, field="value"):
return [entry[field] for entry in list_of_dicts]
def _dict(list_of_dicts):
return {entry["value"]: entry["label"] for entry in list_of_dicts}
options = deepcopy(DEFAULT_OPTIONS)
options["substrate_value"] = substrate
options["substrate_options"] = _list(substrate_options)
config = {
"version": JSON_VERSION,
"cards": build_layer_config(dropdowns, dropdown_ids, params, param_ids),
"options": options,
}
return dcc.send_string(json.dumps(config, indent=4), "dc-config.json")
We reuse build_layer_config
defined in the previous chapter to create layer config. Additionally, we store value
and options
of the bulk card dropdown.
We have implemented two nested helper methods: _list
and _dict
for converting lists of dictionaries to plain list or dictionary respectively.
The final step is downloading config to your local machine. We use dcc.send_string
for that.
Using callback
Finally, we need to create all the callbacks. In dashboard.py
modify callback creation with:
# dashboard.py
import src.callbacks.serializer
and in create_callbacks(app)
:
# Append at the end of the function
src.callbacks.serializer.create_callbacks(app)
Starting the app:
Start the app with python dashboard.py
. You should see an indication of a running app in your console. The dashboard is available under http://localhost:8050.
Summary:
Quick recap:
- For serialization, collect all relevant properties in the dictionary
- Use
dcc.Download
to download the config to your local machine - Config versioning may save you hours of debugging
The complete code from this chapter can be found on GitHub:
https://github.com/ptrhbt/dash-crush/tree/6-serialization
There is also the requirements.txt
file which lists all packages used. Just install it to your virtual env with pip install -r requirements.txt
.
I would be happy to hear your opinions about Dash Crush.
See you in the next chapter of Dash Crush! We will implement complementary functionality, namely deserialization app state from the config file.