small library for OOP dashboard building blocks
Project description
dash_oop_components
dash_oop_components
is a small helper library with object-oriented dashboard building blocks for the plotly dash library
Install
pip install dash_oop_components
Documentation
Documentation can be found at: https://oegedijk.github.io/dash_oop_components/
Purpose
Plotly's dash is an awesome library that allows you to build rich interactive data driven web apps with pure python code. However the default style of dash apps is quite declarative, which for large projects can lead to code that becomes unwieldy, hard to maintain, and hard to collaborate on.
This library provides a number object-oriented wrappers for organizing your dash code that allow you to write clean, modular, composable, re-usable and fully configurable dash code.
It includes:
DashFigureFactory
: a wrapper for your data/plotting functionality, keeping data/plotting logic seperate from your dashboard interaction logic.DashComponent
: a self-contained, modular, configurable unit that combines a dash layout with dash callbacks.- Keeps layout and callbacks in one place, grouped together.
- Makes use of a
DashFigureFactory
for plots or other data output DashComponents
are composable, meaning that you can nest them into new composite components.- You can store component configuration to yaml, and then rebuild from yaml.
- You can use
DashConnectors
to connect callbacks between components
DashApp
: Build a dashboard out of aDashComponent
and run it.- Includes the possibility of tracking dashboard state in the querystring url,
allowing for shareable stateful urls.
- Using
DashComponentTabs
you can also track state for current tab only
- Using
- Includes the possibility of tracking dashboard state in the querystring url,
allowing for shareable stateful urls.
All wrappers:
- Automatically store all params to attributes and to a ._stored_params dict
- Allow you to store its' config to a
.yaml
file, including import details, and can then
be fully reloaded from a config file.
This allows you to:
- Seperate the data/plotting logic from the dashboard interactions logic, by putting all
the plotting functionality inside a
DashFigureFactory
and all the dashboard layout and callback logic intoDashComponents
. - Build self-contained, configurable, re-usable
DashComponents
- Compose dashboards that consists of multiple
DashComponents
that each may consists of multiple nestedDashComponents
, etc. - Store all the configuration needed to rebuild and run a particular dashboard to
a single configuration
.yaml
file - Parametrize your dashboard so that you (or others) can make change to the dashboard without having to edit the code.
- Plus: track the state of your dashboard with querystrings and reload the state from url!
- And: launch from the commandline with the
dashapp
CLI!
Example
An example covid tracking dashboard has been deployed to dash-oop-demo.herokuapp.com (code at github.com/oegedijk/dash_oop_demo), showcasing:
- The use of re-usable components
- Keeping track of state in the querystring
- Seperating data from dashboard logic
- Loading the dashboard from a config yaml file
Example Code
A full example dashboard can be found at github.com/oegedijk/dash_oop_demo and has been deployed to https://dash-oop-demo.herokuapp.com/
Below is the code for similar but slightly simpler example. Full explanation for the dash_oop_demo
dashboard can be found in the example documentation.
The example is a rewrite of this Charming Data dash instruction video (go check out his other vids, they're awesome!).
import dash_html_components as html
import dash_core_components as dcc
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
from dash_oop_components import DashFigureFactory, DashComponent, DashApp
import pandas as pd
import plotly.express as px
CovidPlots: a DashFigureFactory
First we define a basic DashFigureFactory
that loads a covid dataset, and provides a single plotting functionality, namely plot_time_series(countries, metric)
. Make sure to call super().__init__()
in order to store params to attributes (that's how the datafile parameters gets automatically assigned to self.datafile for example), and store them to a ._stored_params
dict so that they can later be exported to a config file.
class CovidPlots(DashFigureFactory):
def __init__(self, datafile="covid.csv"):
super().__init__()
self.df = pd.read_csv(self.datafile)
self.countries = self.df.countriesAndTerritories.unique().tolist()
self.metrics = ['cases', 'deaths']
def plot_time_series(self, countries, metric):
return px.line(
data_frame=self.df[self.df.countriesAndTerritories.isin(countries)],
x='dateRep',
y=metric,
color='countriesAndTerritories',
labels={'countriesAndTerritories':'Countries', 'dateRep':'date'},
)
figure_factory = CovidPlots("covid.csv")
print(figure_factory.to_yaml())
dash_figure_factory:
class_name: CovidPlots
module: __main__
params:
datafile: covid.csv
CovidTimeSeries: a DashComponent
Then we define a DashComponent
that takes a plot_factory and build a layout with two dropdowns and a graph.
- By calling
super().__init__()
all parameters are automatically stored to attributes (so that we can access e.g.self.hide_country_dropdown
), and to a._stored_params
dict. - This layout makes use of the
make_hideable()
staticmethod, to conditionally wrap certain layout elements in a hidden div. - We track the state of the dropdowns "value" attribute by wrapping it in
self.querystring(params)(dcc.Dropdown)(..)
, and passing the params down to the layout function. - Note that the layout element id's add
+self.name
to ensure they are unique in every layoutself.name
gets assigned a uuid random string of length 10 bysuper().__init__()
.
- Note that the callbacks are registered using
_register_callbacks(self, app)
(note the underscore!) - Note that the callback uses the
plot_factory
for the plotting logic.
class CovidTimeSeries(DashComponent):
def __init__(self, plot_factory,
hide_country_dropdown=False, countries=None,
hide_metric_dropdown=False, metric='cases', name=None):
super().__init__()
if not self.countries:
self.countries = self.plot_factory.countries
def layout(self, params=None):
return dbc.Container([
dbc.Row([
dbc.Col([
html.H3("Covid Time Series"),
self.make_hideable(
self.querystring(params)(dcc.Dropdown)(
id='timeseries-metric-dropdown-'+self.name,
options=[{'label': metric, 'value': metric} for metric in ['cases', 'deaths']],
value=self.metric,
), hide=self.hide_metric_dropdown),
self.make_hideable(
self.querystring(params)(dcc.Dropdown)(
id='timeseries-country-dropdown-'+self.name,
options=[{'label': country, 'value': country} for country in self.plot_factory.countries],
value=self.countries,
multi=True,
), hide=self.hide_country_dropdown),
dcc.Graph(id='timeseries-figure-'+self.name)
]),
])
])
def _register_callbacks(self, app):
@app.callback(
Output('timeseries-figure-'+self.name, 'figure'),
Input('timeseries-country-dropdown-'+self.name, 'value'),
Input('timeseries-metric-dropdown-'+self.name, 'value')
)
def update_timeseries_plot(countries, metric):
if countries and metric is not None:
return self.plot_factory.plot_time_series(countries, metric)
raise PreventUpdate
DuoPlots: a composition of two subcomponents
A composite DashComponent
that combines two CovidTimeSeries
into a single layout.
Both subcomponents are passed the same plot_factory
but assigned different initial values.
- The layouts of subcomponents can be included in the composite layout with
self.plot_left.layout(params)
andself.plot_right.layout(params)
- Composite callbacks should again be defined under
self._register_callbacks(app)
(note the underscore!)- calling
.register_callbacks(app)
first registers all callbacks of subcomponents, and then calls_register_callbacks(app)
. - composite callbacks can access elements of subcomponents by using the
subcomponent.name
fields in the ids.
- calling
- When tracking the state of the dashboard in the querystring it is important to name your components, so that
the next time you start the dashboard the elements will have the same id's. In this case we
pass
name="left"
andname="right"
. - Make sure to pass the params parameter of the layout down to the subcomponent layouts!
class DuoPlots(DashComponent):
def __init__(self, plot_factory):
super().__init__()
self.plot_left = CovidTimeSeries(plot_factory,
countries=['China', 'Vietnam', 'Taiwan'],
metric='cases', name='left')
self.plot_right = CovidTimeSeries(plot_factory,
countries=['Italy', 'Germany', 'Sweden'],
metric='deaths', name='right')
def layout(self, params=None):
return dbc.Container([
dbc.Row([
dbc.Col([
self.plot_left.layout(params)
]),
dbc.Col([
self.plot_right.layout(params)
])
])
], fluid=True)
dashboard = DuoPlots(figure_factory)
print(dashboard.to_yaml())
dash_component:
class_name: DuoPlots
module: __main__
params:
plot_factory:
dash_figure_factory:
class_name: CovidPlots
module: __main__
params:
datafile: covid.csv
name: FBhmLSses6
Build and start DashApp
:
Pass the dashboard
to the DashApp
to create a dash flask application.
- You can pass
mode='inline'
,'external'
or'jupyterlab'
when you are working in a notebook in order to keep the notebook interactive while the app is running - By passing
querystrings=True
you automatically keep track of the state of the dashboard int the url querystring - By passing
bootstrap=True
the default bootstrap css gets automatically included. You can also choose particular themes, e.g.bootstrap=dbc.themes.FLATLY
- You can pass other dash parameters in the
**kwargs
app = DashApp(dashboard, querystrings=True, bootstrap=True)
print(app.to_yaml())
dash_app:
class_name: DashApp
module: dash_oop_components.core
params:
dashboard_component:
dash_component:
class_name: DuoPlots
module: __main__
params:
plot_factory:
dash_figure_factory:
class_name: CovidPlots
module: __main__
params:
datafile: covid.csv
name: FBhmLSses6
port: 8050
mode: dash
querystrings: true
kwargs:
external_stylesheets:
- https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css
suppress_callback_exceptions: true
if run_app:
app.run()
app.to_yaml("covid_dashboard.yaml")
launch from the commandline with dashapp
CLI
Now we could launch the dashboard from the command line with the dashapp
CLI tool:
$ dashapp covid_dashboard.yaml
reload dashboard from config:
app2 = DashApp.from_yaml("covid_dashboard.yaml")
We can check that the configuration of this new app2
is indeed the same as app
:
print(app2.to_yaml())
dash_app:
class_name: DashApp
module: dash_oop_components.core
params:
dashboard_component:
dash_component:
class_name: DuoPlots
module: __main__
params:
plot_factory:
dash_figure_factory:
class_name: CovidPlots
module: __main__
params:
datafile: covid.csv
name: Jq9frNnUiA
port: 8050
mode: dash
querystrings: true
kwargs:
external_stylesheets:
- https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css
suppress_callback_exceptions: true
And if we run it it still works!
if run_app:
app2.run()
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for dash_oop_components-0.0.7.tar.gz
Algorithm | Hash digest | |
---|---|---|
SHA256 | a50eab8d2b695cfe769b82cac8e32cbea1e73dac832c5f821f9feeb5ed7092da |
|
MD5 | 8029417b6794d93c8192c0dd03902181 |
|
BLAKE2b-256 | 596818d6e8de453cc9648c9e034bcf24523a80bd0619985d2f4cbc97438b0491 |
Hashes for dash_oop_components-0.0.7-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 41432150a2fff6f99d8d56acc45374687a1d9f7c2a4e80da1e8c775d4510c1b7 |
|
MD5 | 9eaf01d130b244047cf242192da68965 |
|
BLAKE2b-256 | f53f96efc581fb885a285974d1b566609136e22cead3b10071ce040386c8432c |