Reactive dashboards with Plotly Dash
15/11/2017 @TrustYou
Giacomo Debidda
A Big Problem
So many libraries...
Through a couple of simple patterns, Dash abstracts away all of the technologies and protocols that are required to build an interactive web-based application.
Let's see some examples...
is the
for
Dropdown
# ui.R (or app.R)
library(shiny)
selectInput(
inputId = 'my-shiny-dropdown',
label = strong('My Label'),
choices = c('Foo', 'Bar', 'Baz'),
selected = 'Foo',
)
# app.py
import dash_core_components as dcc
dcc.Dropdown(
id='my-dash-dropdown',
options=[
{'label': 'Foo', 'value': 'FOO'},
{'label': 'Bar', 'value': 'BAR'},
{'label': 'Baz', 'value': 'BAZ'},
],
value='FOO',
)
Slider
# ui.R (or app.R)
library(shiny)
sliderInput(
inputId = 'my-shiny-slider',
label = 'Number of observations:',
min = 0,
max = 1000,
value = 500,
step = 5,
),
# app.py
import dash_core_components as dcc
dcc.Slider(
id='my-dash-slider',
min=0,
max=1000,
step=5,
value=500,
)
pip install...
pip install dash==0.19.0 # The core dash backend
pip install dash-renderer==0.11.1 # The dash front-end
pip install dash-html-components==0.8.0 # HTML components
pip install dash-core-components==0.14.0 # Supercharged components
pip install plotly --upgrade # Latest Plotly graphing library
Layout
app.layout = html.Div(children=[
html.H1(children='Hello Dash'),
html.Div(children='''
Dash: A web application framework for Python.
'''),
dcc.Graph(
id='example-graph',
figure={
'data': [
{'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'bar', 'name': 'SF'},
{'x': [1, 2, 3], 'y': [2, 4, 5], 'type': 'bar', 'name': u'Montréal'},
],
'layout': {
'title': 'Dash Data Visualization'
}
}
)
])
A hierarchical tree of components. Dash's layout is serialized as JSON and served to Dash's front-end.
Interaction
# dash/dependencies.py
class Output:
def __init__(self, component_id, component_property):
self.component_id = component_id
self.component_property = component_property
class Input:
def __init__(self, component_id, component_property):
self.component_id = component_id
self.component_property = component_property
class State:
def __init__(self, component_id, component_property):
self.component_id = component_id
self.component_property = component_property
class Event:
def __init__(self, component_id, component_event):
self.component_id = component_id
self.component_event = component_event
What a component is, in relationship with another component
Reactive callbacks
- Decorate your function with @app.callback and define 1 Output, 0+ Input, 0+ State, 0+ Event
- Pass to your function N variables for the inputs, M variables for the states, no variables for the events
- The Dash callback is automatically called whenever the specified property of the Input component changes
app = Dash(name=app_name, server=Flask(app_name))
@app.callback(
output=Output('my-table', 'rows'),
inputs=[Input('button', 'n_clicks')],
state=[State('my-slider', 'value'), State('my-dropdown', 'value')],
events=[Event('interval-component', 'interval')])
)
def update_table(n_clicks, slider_value, dropdown_value):
pass
Callbacks caching
Dash callbacks are functional (they don't contain any state) so you can easily add memoization caching.
from flask_caching import Cache
cache = Cache(app.server, config={
'CACHE_TYPE': 'filesystem', 'CACHE_DIR': 'cache',
'CACHE_THRESHOLD': 10, 'CACHE_DEFAULT_TIMEOUT': 30})})
@app.callback(
output=Output('memoized-children', 'children'),
inputs=[Input('memoized-dropdown', 'value')])
@cache.memoize()
def render(value):
children = 'Selected {}'.format(value)
return children
Callbacks run in parallel
If 10 Output components (e.g. several graphs, a table, labels) depend on the same Input component (e.g. a Dropdown), then 10 requests are made at the same time in parallel.
None of these requests block each other: each component will update when its request finishes.
Share state
Dash callbacks should never mutate variables outside of their scope!
Perform expensive operations (e.g. downloading, querying data) in the global scope of the app instead of within the callback functions.
Single Page Apps
import dash
import dash_core_components as dcc
import dash_html_components as html
app = dash.Dash()
app.layout = html.Div([
# represents the URL bar, doesn't render anything
dcc.Location(id='url', refresh=False),
dcc.Link('Navigate to "/"', href='/'),
dcc.Link('Navigate to "/page-2"', href='/page-2'),
# content will be rendered in this element
html.Div(id='page-content')
])
@app.callback(
output=dash.dependencies.Output('page-content', 'children'),
inputs=[dash.dependencies.Input('url', 'pathname')],
)
def display_page(pathname):
children = html.Div([html.H3('You are on page {}'.format(pathname))])
return children
External CSS/JS
external_css = [
# dash stylesheet
'https://codepen.io/chriddyp/pen/bWLwgP.css',
'https://fonts.googleapis.com/css?family=Lobster|Raleway',
]
external_js = [
# google analytics with the tracking ID for this app
'https://codepen.io/jackdbd/pen/rYmdLN.js'
]
for js in external_js:
app.scripts.append_script({'external_url': js})
for css in external_css:
app.css.append_css({'external_url': css})
# in ipython, for example
>>> import dash_core_components as dcc
>>> help(dcc.Graph)
class Graph(dash.development.base_component.Component)
| A Graph component.
|
|
| Keyword arguments:
| - id (string; required)
| - clickData (dict; optional): Data from latest click event
| - hoverData (dict; optional): Data from latest hover event
| - clear_on_unhover (boolean; optional): If True, `clear_on_unhover` will clear the `hoverData` property
| when the user "unhovers" from a point.
| If False, then the `hoverData` property will be equal to the
| data from the last point that was hovered over.
...
Getting help
Internals 1/4
# dash_core_components/__init__.py
_current_path = _os.path.dirname(_os.path.abspath(__file__))
_components = _dash.development.component_loader.load_components(
_os.path.join(_current_path, 'metadata.json'),
'dash_core_components'
)
_this_module = _sys.modules[__name__]
_js_dist = [
# some js files here
]
_css_dist = [
# some css files here
]
for component in _components:
setattr(_this_module, component.__name__, component)
setattr(component, '_js_dist', _js_dist)
setattr(component, '_css_dist', _css_dist)
Components are created when you instantiate the app.
Internals 2/4
# dash/development/component_loader.py
from .base_component import generate_class
def load_components(metadata_path, namespace='default_namespace'):
"""Load React component metadata into a format Dash can parse.
Usage: load_components('../../component-suites/lib/metadata.json')
Keyword arguments:
metadata_path -- a path to a JSON file created by
[`react-docgen`](https://github.com/reactjs/react-docgen).
Returns:
components -- a list of component objects with keys
`type`, `valid_kwargs`, and `setup`.
"""
...
The jsonified React.js components are loaded.
Internals 3/4
Internals 4/4
# dash/development/base_component.py
def generate_class(typename, props, description, namespace):
# Dynamically generate classes to have nicely formatted docstrings,
# keyword arguments, and repr
# Insired by http://jameso.be/2013/08/06/namedtuple.html
...
React.js components are translated into Python classes that include: argument validation, Python docstring, type, basic sets of methods.
def js_to_py_type(type_object):
js_type_name = type_object['name']
# wrapping everything in lambda to prevent immediate execution
js_to_py_types = {
'array': lambda: 'list',
'bool': lambda: 'boolean',
...
}
...
Pros
- better documentation
- awesome gallery
- more mature
- more known/used in the industry (e.g. FDA)
- less code to write (probably)
- deploy on shinyapps.io
- better logo
Cons
- less flexible (I guess...)
- if you need 3d graphics, you probably need the Plotly API anyway
Pros
- it's Python!
- more flexible (can use ANY React.js component that can be serialized)
- easier to deploy (it's WSGI)
- SPA (Single Page Apps)
Cons
- less mature
- some components are not "production-ready" (table, tabs, date picker)
- less examples
- less known/used
- ugly logo
Reference
- Introducing Dash (Medium)
- Why Chris Parmer chose Flask for Dash (Reddit)
- Plotly VS Bokeh (Reddit)
- Overview of Python visualization tools (Practical Business Python)
- Dash gallery
- Plotly Python API
- Plotly Forum
- Deploy Dash apps on Heroku
- Hosting multiple Dash apps
- Visualize earthquakes with Plotly Dash
@jackdbd
giacomodebidda.com
c = '''class {typename}(Component):
"""{docstring}
"""
def __init__(self, {default_argtext}):
self._prop_names = {list_of_valid_keys}
self._type = '{typename}'
self._namespace = '{namespace}'
self.available_events = {events}
self.available_properties = {list_of_valid_keys}
for k in {required_args}:
if k not in kwargs:
raise Exception(
'Required argument `' + k + '` was not specified.'
)
super({typename}, self).__init__({argtext})
def __repr__(self):
if(any(getattr(self, c, None) is not None for c in self._prop_names
if c is not self._prop_names[0])):
return (
'{typename}(' +
', '.join([c+'='+repr(getattr(self, c, None))
for c in self._prop_names
if getattr(self, c, None) is not None])+')')
else:
return (
'{typename}(' +
repr(getattr(self, self._prop_names[0], None)) + ')')
'''