Chapter 15: Key Takeaways - Interactive Dashboards

Quick Reference Card

Framework Selection Guide

Framework Best For Learning Curve Deployment
Plotly Interactive charts in notebooks/web Low HTML export
Dash Full web apps with callbacks Medium Heroku, AWS, Docker
Streamlit Rapid prototyping, data apps Low Streamlit Cloud
Bokeh Custom interactivity, large data High Bokeh Server

Interaction Types

Type Description Example
Filter Narrow data to subsets Conference dropdown
Select Highlight specific elements Click team point
Drill Navigate detail levels Season → Game → Play
Link Connect views together Cross-filtering
Hover Show tooltips Play description
Zoom Focus on region Time range selection

Dash Callback Patterns

Basic Callback

@callback(
    Output('chart', 'figure'),
    Input('dropdown', 'value')
)
def update_chart(selected_value):
    filtered = df[df['column'] == selected_value]
    return px.scatter(filtered, x='x', y='y')

Multiple Outputs

@callback(
    Output('chart1', 'figure'),
    Output('chart2', 'figure'),
    Output('summary', 'children'),
    Input('filter', 'value')
)
def update_all(filter_value):
    filtered = apply_filter(df, filter_value)
    return create_chart1(filtered), create_chart2(filtered), create_summary(filtered)

Cross-Filtering

@callback(
    Output('chart', 'figure'),
    Input('chart', 'selectedData'),
    Input('dropdown', 'value')
)
def cross_filter(selection, dropdown_value):
    filtered = df.copy()
    if selection:
        selected_ids = [p['customdata'] for p in selection['points']]
        filtered = filtered[filtered['id'].isin(selected_ids)]
    return create_figure(filtered)

Using State

@callback(
    Output('result', 'children'),
    Input('submit-button', 'n_clicks'),
    State('input-field', 'value'),
    prevent_initial_call=True
)
def submit_form(n_clicks, input_value):
    return f"Submitted: {input_value}"

Streamlit Patterns

Basic Layout

st.title("Dashboard Title")

# Sidebar
with st.sidebar:
    filter_value = st.selectbox("Filter", options)

# Columns
col1, col2 = st.columns(2)
with col1:
    st.metric("Metric", value)
with col2:
    st.plotly_chart(fig)

Caching

@st.cache_data(ttl=3600)  # Cache for 1 hour
def load_data():
    return pd.read_csv("large_file.csv")

@st.cache_resource  # For models, connections
def load_model():
    return joblib.load("model.pkl")

Session State

if 'counter' not in st.session_state:
    st.session_state.counter = 0

if st.button("Increment"):
    st.session_state.counter += 1

st.write(f"Count: {st.session_state.counter}")

Plotly Templates

Interactive Scatter

fig = px.scatter(df, x='off_epa', y='def_epa',
                 size='wins', color='conference',
                 hover_name='team',
                 title='Team Efficiency')
fig.update_layout(template='plotly_white',
                 hovermode='closest')

Hover Template

fig.update_traces(
    hovertemplate='<b>%{hovertext}</b><br>' +
                  'Off EPA: %{x:.3f}<br>' +
                  'Def EPA: %{y:.3f}<br>' +
                  '<extra></extra>'
)

Annotations

fig.add_annotation(
    x=0.25, y=-0.20,
    text="Key moment",
    showarrow=True,
    arrowhead=2
)

Performance Optimization

Caching Strategies

# Streamlit
@st.cache_data(ttl=3600)
def load_data(): ...

# Dash
from flask_caching import Cache
cache = Cache(app.server, config={'CACHE_TYPE': 'simple'})

@cache.memoize(timeout=3600)
def expensive_computation(): ...

Lazy Loading

# Load on demand, not on page load
@callback(
    Output('data-table', 'data'),
    Input('load-button', 'n_clicks'),
    prevent_initial_call=True
)
def load_on_click(n):
    return load_large_dataset().to_dict('records')

Data Aggregation

def reduce_for_display(df, max_points=1000):
    if len(df) > max_points:
        return df.sample(max_points)
    return df

Design Principles Checklist

  • [ ] Progressive disclosure: Start simple, reveal complexity on demand
  • [ ] Responsive feedback: Show loading states, confirm actions
  • [ ] Consistent interactions: Same gesture = same result everywhere
  • [ ] Information hierarchy: Key metrics visible first
  • [ ] Empty states: Meaningful message when no data matches
  • [ ] Error handling: Graceful failure with helpful messages

Common Callbacks

Filter Update

@callback(Output('chart', 'figure'), Input('filter', 'value'))
def update(val):
    return create_fig(df[df['col'] == val] if val else df)

Click Selection

@callback(Output('details', 'children'), Input('chart', 'clickData'))
def show_details(click):
    if not click:
        return "Click a point"
    return click['points'][0]['hovertext']

Range Selection

@callback(Output('summary', 'children'), Input('chart', 'relayoutData'))
def on_zoom(relayout):
    if relayout and 'xaxis.range[0]' in relayout:
        start = relayout['xaxis.range[0]']
        end = relayout['xaxis.range[1]']
        return f"Selected: {start} to {end}"

Deployment Commands

Streamlit

# Local
streamlit run app.py

# Cloud
# Push to GitHub, connect Streamlit Cloud

Dash (Gunicorn)

gunicorn app:server -b 0.0.0.0:8050

Docker

FROM python:3.10
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
EXPOSE 8050
CMD ["gunicorn", "app:server", "-b", "0.0.0.0:8050"]

Key Terminology

Term Definition
Callback Function triggered by user interaction
Cross-filtering Selection in one chart filters all charts
Drill-down Navigate from summary to detail
Lazy loading Load data only when requested
Progressive disclosure Reveal complexity gradually
Session state Data persisted across user interactions
Throttling Limit update frequency for performance

Quick Decision Tree

What do you need?

├── Quick exploration in notebook → Plotly
│
├── Simple data app → Streamlit
│
├── Full web application
│   ├── Need callbacks/state → Dash
│   └── Need custom interactivity → Bokeh
│
└── Embeddable chart → Plotly (export HTML)

Common Mistakes to Avoid

  1. Loading all data on startup → Use lazy loading
  2. Rebuilding entire figure on small changes → Update only what changed
  3. No loading indicators → Users think app is broken
  4. Filters at bottom of page → Put filters where users look first
  5. No empty state handling → Show message when filters return no data
  6. Ignoring mobile users → Test on tablets and phones