Dash

Interactive Data Visualization Web Apps with no Javascript

Dom Weldon

Senior Software Engineer, decisionLab

@DomWeldon

Slides

http://bit.ly/ep2019dash

Code Examples

http://bit.ly/ep2019dashgithub

What you can, can't, should and probably shouldn't do with plotly/Dash.

This talk:

  • Basic introduction to Dash, how it works and what you can do with it.
  • Discuss what we've learned at decisionLab from using Dash across teams
  • Some good practice we've learned.
  • Looking for feedback and questions at the end!
  • Slides Link: http://bit.ly/ep2019dash
  • Github Link: http://bit.ly/ep2019dashgithub

If you're viewing these slides on the web, press the down key whenever you see a ⬇️

👍

I'm not a Dash expert or involved with/an author on the project

This is a talk about using Dash at work, I apologise to plotly/Dash if anything is out of date or incorrect - get in touch!

Why am I here?

I'm a full stack python and javascript developer, why am I using python to write javascript?

decisionLab Ltd. London

Mathematical modelling consultancy based in central London.

Sorry

Sorry 🇪🇺

Invite

PyCon 🇬🇧 2019

Friday 13 - Tuesday 17 September

in Cardiff 🏴󠁧󠁢󠁷󠁬󠁳󠁿

https://2019.pyconuk.org/

Two cultures? 👩‍🔬👨‍🔬 & 👷‍♀️👷‍♂️

Python Data and Python Software: different tools and approaches.

Collaboration is key, but can be very time consuming on small projects.

Want to minimise the new technologies required to get a project off the ground.

If I'm building a very basic proof-of-concept (PoC) app for a client, I want my data scientist 👩‍ to take the lead and rapidly prototype a web application.

Their background probably isn't in web development.

I 👷‍♂️ want to facilitate their work and deploy the tools we build.

In a tweet...

...and so we found Dash.

Open source, with paid consultancy options.

Project describes itself as experimental, although it recently (June) hit version 1.0

No Javascript! ...and that's a good thing?

Rest of this talk:

  • How does Dash work? Including a Hello World example.
  • Some examples of what you can do with Dash very quickly.
  • Tips/good practice on building larger Dash projects (and how to support your colleagues doing this)
  • An opinion: when to use Dash and when to stop using Dash.
  • Github examples: bit.ly/ep2019dashgithub

So how does Dash work?

A Hello World example.


$ pip install dash

"""Hello world example."""
import dash
import dash_html_components as html

app = dash.Dash(__name__)

app.layout = html.Div(
    children=[html.H1(children="Hello EuroPython!")]
)

if __name__ == "__main__":
    app.run_server(debug=True)

$ virtualenv -p python3 venv  # or whatever floats your boat
$ pip install -r requirements.txt
$ python app00.py

See it live app00.py

Dash is composed of several modules. Two at work here:

  • Dash HTML Components is a module which wraps the core React HTML components (here Div and H1, which come from the HTML specification). We use python to write an HTML style layout.
  • Dash on its own which manages the relationships between the components and serves the layout using Flask.

But... what if I want an interactive webpage?

First: how does this work in JS?

⬇️



Hello there!

3 DOM (Document Object Model) Nodes:

  • P

    • Span#helloName
  • Input#helloInput

We need a script that sets the innerHTML of Span#helloName to value of Input#helloInput, and monitors it for changes.

Javacript allows us to manipulate the DOM in the browser to do this.

React lets us do this declaratively. Rather than write our own script, we declare the value of something and how it changes.

Precise syntax isn't important, but the value of Input#helloInput would be a prop to the Span#helloName component.

A javascript function ultimately defines how the relationship works.


	let spanHelloName = (helloInputValue) => (
	  {helloInputValue}
	);

Meanwhile... in Dash...

⬇️

Interaction and changes to the layout/DOM are managed by callbacks. They define relationships between components.

Think of these a bit like excel.

They are functions that are called whenever any of their inputs change.

Updated Hello World! ⬇️

Give the heading an ID, my-h1


	app.layout = html.Div(
	    children=[
	        html.H1(id="my-h1"),
	        dcc.Input(
	            id="my-input",
	            value="Basel",
	            type="text",
	        ),
	    ]
	)
	

Set its value with a Callback: taking the value of my-input


	@app.callback(
	    Output(
	        component_id="my-h1",
	        component_property="children",
	    ),
	    [
	        Input(
	            component_id="my-input",
	            component_property="value",
	        )
	    ],
	)
	def update_output_div(input_value: str) -> str:
	    return f"Hello {input_value}!"  # python 3.6
	

	$ python app01.py
		

See it live app01.py

Important ❗⬇️

The code defining the relationship between our components now lives in python. The 🐍 lives on the server.

Whereas in a React app, this would all be managed inside the browser, in Dash it now involves a call to the server (to get the new value for the DIV)

🏎️ vs 🛴

Huge performance loss compared with pure React.

But that's okay...

I want to build an app really quickly. Using familiar technologies for data scientists. In order to build a proof-of-concept (PoC), or a very low-use alpha.

So what can you do with Dash?

As the name implies, it's mostly geared towards building dashboards to display data. But you can also add more transactional and interactive functionality quite easily.

1. Display Data Quickly and Easily

app03.py: listing the 🛳️ dataset ⬇️

Full Code Example


app.layout = html.Div(
    children=[
        html.H1("Titanic Dataset"),
        html.H5("Filter by sex:"),
        dcc.Dropdown(
            id="my-dropdown",
            options=[{"label": "All", "value": "both"}]
            + [
                {"label": sex, "value": sex}
                for sex in df.Sex.unique()
            ],
            value="both",
        ),
        html.Div(id="my-div"),
        dash_table.DataTable(
            id="my-table",
            columns=[
                {"name": i, "id": i} for i in df.columns
            ],
            data=[],
        ),
    ]
)

@app.callback(
    Output(
        component_id="my-table", component_property="data"
    ),
    [
        Input(
            component_id="my-dropdown",
            component_property="value",
        )
    ],
)
def provide_passengers(sex: str) -> Iterable[Mapping]:
    if sex == "both":
        return df.to_dict("rows")

    return df[df.Sex == sex].to_dict("rows")

See it live app03.py

Displaying Graphs

Full support for a rich suite of graphing functions.

Full code example


app.layout = html.Div(
	  children=[
	      html.H1("Titanic Names"),
	      dcc.Graph(
	          id="example-graph",
	          figure={
	              "data": [
	                  {
	                      "x": letters,
	                      "y": freq,
	                      "type": "bar",
	                      "name": "Titanic Passengers By First Name",
	                  }
	              ],
	              "layout": {
	                  "title": (
	                      "Number of Passengers on the Titanic By First "
	                      "Letter of First Name"
	                  )
	              },
	          },
	      ),
	  ]
)

See it live app03a.py

Making Interactive Tools

Example: basic To-Do list (app04.py) ⬇️


app.layout = html.Div(
  children=[
      html.H1("To Do List"),
      html.Ul(
          id="my-ul",
          children=list_tasks(data_store["tasks"]),
      ),
      dcc.Input(id="my-input"),
      html.Button(
          id="my-button", n_clicks=0, children="Add"
      ),
  ]
)

# replace this with a real data store!
data_store = {"tasks": []}

def list_tasks(tasks: Iterable[str]) -> Iterable[html.Li]:
  """Return list items of tasks."""

  return [html.Li(children=task) for task in tasks]

@app.callback(
	Output("my-ul", "children"),
	[Input("my-button", "n_clicks")],
	[State("my-input", "value")],
)
def add_task(
	n_clicks: int = 0,
	value: str = None,
	data_store: Mapping[str, Iterable] = data_store,
) -> Iterable[html.Li]:
	"""Called when the button is clicked."""
	if value is not None and len(value.strip()):
			data_store["tasks"].append(value.strip())

	return list_tasks(data_store["tasks"])

Key to transactional/interactive interface:

  • Input causes the callback to be run whenever this value changes (i.e, button is pushed)
  • State provides the value of another DOM element, but does not cause the callback to be fired whenever that value changes.

See it live app04.py

You can build your own Dash components.

But the Dash API limits what you can do.

Dash prevents you from properly accessing Redux. Apps tend not to be "smooth" as a result.

You can still go very far with Dash!

A user interface for a machine learning tool used to detect criminal activity in the Amazon rainforest.

How to get the most out of Dash?

Some tips and lessons learned.

  1. Organize your app
  2. Build your application using factory functions
  3. Implement routing and navigation
  4. Tooling Up Dash - dependency injection?

1. Organize your application! ⬇️

Dash is novel and experimental, so people are always referring to the docs.

All the docs display apps in a single file.

Result: 2,000 line single-file dash apps.

This may seem like very basic coding advice but it's important!

File Structures

  • Split callbacks and layouts into different files
  • Run the app as a module with __main__.py
  • Give sets of fetures their own directories
  • Abstract logical code from callbacks

File Structures: app05.py

Separation of concerns

  • __init__.py
  • __main__.py - runs the app (as module)
  • app.py - calls dash.Dash() to construct the app
  • callbacks.py - handles interactions
  • layouts.py - defines the look of the app

$ python -m app05

2. Build your app with factory functions

...because...

3. This allows easier routing and navigation! ⬇️

  • At decisionLab, we've abstracted the Dash interface from our callbacks and layouts.
  • Our App class has a callback decorator which stores callbacks as they are declared.
  • And a layout decorator which stores layout (i.e., dash components) alongside a route (a string for the URL path)..
  • Our default template and factory functions then build a dash app based on the modules declared in our application's entry point.

"""Run the app."""
from .factory import create_app

from .home import *  # at dL we parameterize this instead
from .todo import *
from .shopping import *

app = create_app()

if __name__ == "__main__":
  app.run_server(debug=True)

@App.callback(
  Output("my-shopping-ul", "children"),
  [Input("my-shopping-button", "n_clicks")],
  [State("my-shopping-input", "value")],
)
def add_item(
  n_clicks: int = 0,
  value: str = None,
  data_store: Mapping[str, Iterable] = data_store,
) -> Iterable[html.Li]:
  """Called when the button is clicked."""
  if value is not None and len(value.strip()):
      data_store["items"].append(value.strip())

  return list_items(data_store["items"])

@App.layout(label="Shopping List", path="/shopping")
def shopping_layout() -> html.Div:
  return html.Div(
      children=[
          html.H1("Shopping List"),
          html.Ul(
              id="my-shopping-ul",
              children=list_items(data_store["items"]),
          ),
          dcc.Input(id="my-shopping-input"),
          html.Button(
              id="my-shopping-button", n_clicks=0, children="Add Item"
          ),
      ]
  )

Register a single callback/base layout


def register_template(app: dash.dash.Dash) -> None:
  """Register the main template of the application, including css."""
  app.layout = base_layout()
  app.callback(
      Output("page_content", "children"), [Input("url", "pathname")]
  )(App.route)

Layout...


def base_layout() -> html.Div:
  """Return a Dash Component which will serve as the top-level layout."""
  # register the layout
  return html.Div(
      [dcc.Location(id='url', refresh=False),] + [html.Div(

          html.Ul([
              html.Li(
                  dcc.Link(
                      route["label"],
                      href=path
                  ),
              )
              for path, route in App._ROUTES.items()
          ])
      ),
      html.Div(id="page_content", children="Click link to continue"),]
  )

Methods on our App class.


@classmethod
def route(cls, path) -> dash.development.base_component.Component:
  """Return the desired layout."""
  try:
      return cls._ROUTES[path]["callable"]()
  except KeyError:
      return cls._not_found()

@staticmethod
def _not_found() -> html.Div:
  return html.Div("Not Found")

$ python -m app06

See it live app06

4. Tooling up Dash: Dependency Injection ⬇️

Once we have a class to declare and manage the app, and factory functions to build it, we can start to integrate other tools to make life for our users easier.

The first such tool we've begun to use is dependency injection, using google/pinject.

If you're interested in implementing this, please contact me to talk further!

Which brings me to the final point...

When to stop using Dash and start building a "proper" web application?

Dash is great for...

  • Rapid UI development by non specialists.
  • Informed UI development: data scientists don't have to hand over work to software engineers and can focus on building an app that fits their analysis.

But...

  • Rapid UI development: are you creating technical debt, if your app will be rebuilt from scratch in React should you not invest time in JS early on?.
  • Informed UI development: informed by whom? Dash tools are great, but obviously are limited. Do you need a user researcher / UX consultant?

Some things Dash isn't great at...

  • Authentication (without using plotly accounts)
  • Testing...
  • Complex interactions where you may wish to incorporate components using complex third party Javascript libraries and use redux.
  • Heavy loads. Every keystroke causses a request to the server...

Summary...

  • Dash is a great tool for facilitating rapid development of data-driven interfaces and dashboards.
  • Investing a bit of time into building tools into it and organizing your code can allow you to go very, very far inside a dash app.
  • Front end developers will still have a job

Some areas we're working on for the future:

  • Testing: how to automate this in Dash?
  • Dash Tooling and Dependency Injection: to be open sourced!
  • Open sourcing some of our custom Dash components: inclluding a port for Leaflet maps.

Thanks for listening!

Please contact me for more:

dom.weldon@decisionlab.co.uk

@DomWeldon