Exercises: Dash Dashboards

Install: pip install dash dash-bootstrap-components plotly pandas. Run apps with python app.py and open http://127.0.0.1:8050.


Part A: Conceptual (6 problems)

A.1 ★☆☆ | Recall

Name the two main parts of a Dash application.

Guidance**Layout** (a tree of html and dcc components) and **callbacks** (reactive functions decorated with `@app.callback`).

A.2 ★☆☆ | Recall

What is the difference between Input and State in a callback?

Guidance`Input`: a component property whose change triggers the callback. `State`: a component property whose value is read but does not trigger the callback. State is used for form patterns where you want to read a value only when a button is clicked.

A.3 ★★☆ | Understand

Describe the chapter's threshold concept ("callbacks are reactive declarations").

GuidanceIn Dash, you do not write "when the slider moves, do X" (imperative). You write "output Y depends on inputs A, B, C" (declarative). The framework handles when and how to call the function. You are declaring dependencies, not describing actions.

A.4 ★★☆ | Understand

How does Dash's clickData enable cross-filtering?

GuidanceWhen a user clicks a point in a `dcc.Graph`, Plotly updates the `clickData` property with information about the clicked point. A callback with `Input("source-chart", "clickData")` can read this data and update another chart based on it. This is how brushing-and-linking between charts works in Dash.

A.5 ★★★ | Analyze

When should you prefer Dash over Streamlit?

GuidanceWhen you need: cross-filtering between multiple charts, auto-refresh for live data, complex callback dependencies, custom CSS styling, enterprise authentication/scaling, or explicit control over what runs when. Streamlit is better for simple dashboards, prototypes, and ML demos.

A.6 ★★★ | Evaluate

A colleague has a Streamlit dashboard that needs cross-filtering between three charts. Should they rewrite in Dash?

GuidanceProbably yes. Cross-filtering is awkward in Streamlit (requires session_state workarounds) and native in Dash. The rewrite is 1-3 days for a moderate dashboard, and the result will be cleaner and more maintainable. If the cross-filtering is only "nice to have," staying in Streamlit is reasonable. If it is critical to the workflow, migrate.

Part B: Applied (10 problems)

B.1 ★☆☆ | Apply

Create a minimal Dash app with a title, a dropdown, and a graph.

Guidance
from dash import Dash, html, dcc, Input, Output
import plotly.express as px

app = Dash(__name__)
df = px.data.iris()

app.layout = html.Div([
    html.H1("Iris"),
    dcc.Dropdown(id="x-col", options=[{"label": c, "value": c} for c in df.columns[:4]], value="sepal_length"),
    dcc.Graph(id="chart"),
])

@app.callback(Output("chart", "figure"), Input("x-col", "value"))
def update(col):
    return px.histogram(df, x=col)

app.run(debug=True)

B.2 ★☆☆ | Apply

Add a second dropdown for y-axis and update the chart to a scatter plot.

Guidance
dcc.Dropdown(id="y-col", options=[...], value="sepal_width"),

@app.callback(Output("chart", "figure"), Input("x-col", "value"), Input("y-col", "value"))
def update(x, y):
    return px.scatter(df, x=x, y=y, color="species")

B.3 ★★☆ | Apply

Use Dash Bootstrap Components to create a two-column layout.

Guidance
import dash_bootstrap_components as dbc
app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
app.layout = dbc.Container([
    dbc.Row([
        dbc.Col([dcc.Dropdown(id="filter", options=[...])], width=4),
        dbc.Col([dcc.Graph(id="chart")], width=8),
    ]),
])

B.4 ★★☆ | Apply

Implement a callback where clicking a bar chart filters a scatter plot.

Guidance
@app.callback(
    Output("scatter", "figure"),
    Input("bar", "clickData"),
)
def filter_scatter(click_data):
    if click_data is None:
        return px.scatter(df, x="x", y="y")
    category = click_data["points"][0]["x"]
    return px.scatter(df[df["category"] == category], x="x", y="y")

B.5 ★★☆ | Apply

Use State to read a text input only when a button is clicked.

Guidance
@app.callback(
    Output("output", "children"),
    Input("submit", "n_clicks"),
    State("text-input", "value"),
)
def handle(n_clicks, text):
    if n_clicks is None:
        return ""
    return f"You said: {text}"

B.6 ★★☆ | Apply

Add auto-refresh every 10 seconds using dcc.Interval.

Guidance
dcc.Interval(id="interval", interval=10_000, n_intervals=0)

@app.callback(Output("chart", "figure"), Input("interval", "n_intervals"))
def refresh(n):
    df = fetch_fresh_data()
    return px.line(df, x="time", y="value")

B.7 ★★★ | Apply

Create a multi-page Dash app with two pages: Home and Data.

Guidance
# app.py
from dash import Dash, html, dcc, page_container, page_registry
app = Dash(__name__, use_pages=True, pages_folder="pages")
app.layout = html.Div([
    dcc.Link("Home", href="/"),
    dcc.Link("Data", href="/data"),
    page_container,
])

# pages/home.py
from dash import register_page, html
register_page(__name__, path="/")
layout = html.Div([html.H1("Home")])

# pages/data.py
from dash import register_page, html
register_page(__name__, path="/data")
layout = html.Div([html.H1("Data")])

B.8 ★★★ | Apply

Build a pattern-matching callback that sums the values of dynamically-created sliders.

Guidance
from dash import ALL

app.layout = html.Div([
    html.Div([
        dcc.Slider(id={"type": "slider", "index": i}, min=0, max=100, value=50)
        for i in range(5)
    ]),
    html.Div(id="sum"),
])

@app.callback(
    Output("sum", "children"),
    Input({"type": "slider", "index": ALL}, "value"),
)
def sum_sliders(values):
    return f"Sum: {sum(values)}"

B.9 ★★☆ | Apply

Implement a clientside callback that updates a label with the value of an input.

Guidance
app.clientside_callback(
    "function(value) { return 'Value: ' + value; }",
    Output("label", "children"),
    Input("input", "value"),
)

B.10 ★★★ | Create

Build a complete sales dashboard with KPI cards, filters, and three cross-filtered charts.

GuidanceSee Section 30.12 of the chapter for the full example. About 75 lines of Python with Dash Bootstrap Components.

Part C: Synthesis (4 problems)

C.1 ★★★ | Analyze

Port a Streamlit dashboard to Dash and compare the line counts and complexity.

GuidanceDash versions are typically 30-50% longer than equivalent Streamlit versions because of the explicit layout + callback separation. The complexity feels higher but scales better. For simple dashboards, Streamlit wins on brevity; for complex ones, Dash wins on maintainability.

C.2 ★★★ | Evaluate

A Dash app has 20 callbacks that all depend on each other. What do you suggest?

GuidanceUse the callback graph visualizer to see the dependency structure. Consider consolidating related callbacks into single multi-output callbacks. Check for circular dependencies that Dash might be silently refusing to run. Consider whether a multi-page structure would isolate concerns. If the complexity is inherent, accept it and document the structure clearly.

C.3 ★★★ | Create

Deploy a Dash app using Docker, Gunicorn, and Nginx.

GuidanceSee Section 30.21 for the stack. Create a Dockerfile, a gunicorn command, and an nginx config. Test locally with `docker build` and `docker run` before deploying to a server. For production, add TLS certificates via Let's Encrypt.

C.4 ★★★ | Evaluate

The chapter argues that "tool choice is a trade-off, not an absolute ranking." Apply this principle to your own workflow. When have you used Streamlit, Dash, or neither, and what drove the choice?

GuidanceReflect on real projects. Common answers: Streamlit for quick demos and ML prototypes; Dash for client dashboards with custom styling; neither for analytical reports that are better as notebooks or PDFs. The exercise is to develop self-awareness about your own decision criteria.

Chapter 31 moves from interactive dashboards to automated report generation.