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

  1. Decorate your function with @app.callback and define 1 Output, 0+ Input, 0+ State, 0+ Event
  2. Pass to your function N variables for the inputs, M variables for the states, no variables for the events
  3. 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

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

@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)) + ')')
    '''
Made with Slides.com