How to Design an Interactive Dash and Plotly Dashboard with Callback M …

In this tutorial, we set out to build an advanced interactive dashboard using Dash, Plotly, and Bootstrap. We highlight not only how these tools enable us to design layouts and visualizations, but also how Dash’s callback mechanism links controls to outputs, allowing for real-time responsiveness. By combining local execution with the ability to run in cloud platforms like Google Colab, we explore a workflow that is both flexible and practical. Check out the FULL CODES here.

Copy CodeCopiedUse a different Browser!pip install dash plotly pandas numpy dash-bootstrap-components

import dash
from dash import dcc, html, Input, Output, callback, dash_table
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
import dash_bootstrap_components as dbc

print(“Generating sample data…”)
np.random.seed(42)

We begin by installing and importing the necessary components, including Dash, Plotly, Pandas, NumPy, and Bootstrap, to set up our dashboard environment. We also initialize random seeds and generate sample data so that we can consistently test the interactive features as we build them. Check out the FULL CODES here.

Copy CodeCopiedUse a different Browserstart_date = datetime(2023, 1, 1)
end_date = datetime(2024, 12, 31)
dates = pd.date_range(start=start_date, end=end_date, freq=’D’)
stock_names = [‘AAPL’, ‘GOOGL’, ‘MSFT’, ‘AMZN’, ‘TSLA’]

all_data = []
base_prices = {‘AAPL’: 150, ‘GOOGL’: 120, ‘MSFT’: 250, ‘AMZN’: 100, ‘TSLA’: 200}

for stock in stock_names:
print(f”Creating data for {stock}…”)
base_price = base_prices[stock]

n_days = len(dates)
returns = np.random.normal(0.0005, 0.025, n_days)
prices = np.zeros(n_days)
prices[0] = base_price

for i in range(1, n_days):
prices[i] = prices[i-1] * (1 + returns[i])

volumes = np.random.lognormal(15, 0.5, n_days).astype(int)

stock_df = pd.DataFrame({
‘Date’: dates,
‘Stock’: stock,
‘Price’: prices,
‘Volume’: volumes,
‘Returns’: np.concatenate([[0], np.diff(prices) / prices[:-1]]),
‘Sector’: np.random.choice([‘Technology’, ‘Consumer’, ‘Automotive’], 1)[0]
})

all_data.append(stock_df)

df = pd.concat(all_data, ignore_index=True)

df[‘Date’] = pd.to_datetime(df[‘Date’])
df_sorted = df.sort_values([‘Stock’, ‘Date’]).reset_index(drop=True)

print(“Calculating technical indicators…”)
df_sorted[‘MA_20’] = df_sorted.groupby(‘Stock’)[‘Price’].transform(lambda x: x.rolling(20, min_periods=1).mean())
df_sorted[‘Volatility’] = df_sorted.groupby(‘Stock’)[‘Returns’].transform(lambda x: x.rolling(30, min_periods=1).std())

df = df_sorted.copy()

print(f”Data generated successfully! Shape: {df.shape}”)
print(f”Date range: {df[‘Date’].min()} to {df[‘Date’].max()}”)
print(f”Stocks: {df[‘Stock’].unique().tolist()}”)

We generate synthetic stock data, including prices, volumes, and returns, for multiple tickers across a specified date range. We calculate moving averages and volatility to enrich the dataset with useful technical indicators, providing a strong foundation for building interactive visualizations. Check out the FULL CODES here.

Copy CodeCopiedUse a different Browserapp = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])

app.layout = dbc.Container([
dbc.Row([
dbc.Col([
html.H1(” Advanced Financial Dashboard”, className=”text-center mb-4″),
html.P(f”Interactive dashboard with {len(df)} data points across {len(stock_names)} stocks”,
className=”text-center text-muted”),
html.Hr()
])
]),

dbc.Row([
dbc.Col([
dbc.Card([
dbc.CardBody([
html.H5(” Dashboard Controls”, className=”card-title”),

html.Label(“Select Stocks:”, className=”fw-bold mt-3″),
dcc.Dropdown(
id=’stock-dropdown’,
options=[{‘label’: f'{stock} ({base_prices[stock]})’, ‘value’: stock}
for stock in stock_names],
value=[‘AAPL’, ‘GOOGL’],
multi=True,
placeholder=”Choose stocks to analyze…”
),

html.Label(“Date Range:”, className=”fw-bold mt-3″),
dcc.DatePickerRange(
id=’date-picker-range’,
start_date=’2023-06-01′,
end_date=’2024-06-01′,
display_format=’YYYY-MM-DD’,
style={‘width’: ‘100%’}
),

html.Label(“Chart Style:”, className=”fw-bold mt-3″),
dcc.RadioItems(
id=’chart-type’,
options=[
{‘label’: ‘ Line Chart’, ‘value’: ‘line’},
{‘label’: ‘ Area Chart’, ‘value’: ‘area’},
{‘label’: ‘ Scatter Plot’, ‘value’: ‘scatter’}
],
value=’line’,
labelStyle={‘display’: ‘block’, ‘margin’: ‘5px’}
),

dbc.Checklist(
id=’show-ma’,
options=[{‘label’: ‘ Show Moving Average’, ‘value’: ‘show’}],
value=[],
style={‘margin’: ’10px 0′}
),
])
], className=”h-100″)
], width=3),

dbc.Col([
dbc.Card([
dbc.CardHeader(” Stock Price Analysis”),
dbc.CardBody([
dcc.Graph(id=’main-chart’, style={‘height’: ‘450px’})
])
])
], width=9)
], className=”mb-4″),

dbc.Row([
dbc.Col([
dbc.Card([
dbc.CardBody([
html.H4(id=”avg-price”, className=”text-primary mb-0″),
html.Small(“Average Price”, className=”text-muted”)
])
])
], width=3),
dbc.Col([
dbc.Card([
dbc.CardBody([
html.H4(id=”total-volume”, className=”text-success mb-0″),
html.Small(“Total Volume”, className=”text-muted”)
])
])
], width=3),
dbc.Col([
dbc.Card([
dbc.CardBody([
html.H4(id=”price-range”, className=”text-info mb-0″),
html.Small(“Price Range”, className=”text-muted”)
])
])
], width=3),
dbc.Col([
dbc.Card([
dbc.CardBody([
html.H4(id=”data-points”, className=”text-warning mb-0″),
html.Small(“Data Points”, className=”text-muted”)
])
])
], width=3)
], className=”mb-4″),

dbc.Row([
dbc.Col([
dbc.Card([
dbc.CardHeader(” Trading Volume”),
dbc.CardBody([
dcc.Graph(id=’volume-chart’, style={‘height’: ‘300px’})
])
])
], width=6),
dbc.Col([
dbc.Card([
dbc.CardHeader(” Returns Distribution”),
dbc.CardBody([
dcc.Graph(id=’returns-chart’, style={‘height’: ‘300px’})
])
])
], width=6)
], className=”mb-4″),

dbc.Row([
dbc.Col([
dbc.Card([
dbc.CardHeader(” Latest Stock Data”),
dbc.CardBody([
dash_table.DataTable(
id=’data-table’,
columns=[
{‘name’: ‘Stock’, ‘id’: ‘Stock’},
{‘name’: ‘Date’, ‘id’: ‘Date’},
{‘name’: ‘Price ($)’, ‘id’: ‘Price’, ‘type’: ‘numeric’,
‘format’: {‘specifier’: ‘.2f’}},
{‘name’: ‘Volume’, ‘id’: ‘Volume’, ‘type’: ‘numeric’,
‘format’: {‘specifier’: ‘,.0f’}},
{‘name’: ‘Daily Return (%)’, ‘id’: ‘Returns’, ‘type’: ‘numeric’,
‘format’: {‘specifier’: ‘.2%’}}
],
style_cell={‘textAlign’: ‘center’, ‘fontSize’: ’14px’, ‘padding’: ’10px’},
style_header={‘backgroundColor’: ‘rgb(230, 230, 230)’, ‘fontWeight’: ‘bold’},
style_data_conditional=[
{
‘if’: {‘filter_query’: ‘{Returns} > 0’},
‘backgroundColor’: ‘#d4edda’
},
{
‘if’: {‘filter_query’: ‘{Returns} < 0’},
‘backgroundColor’: ‘#f8d7da’
}
],
page_size=15,
sort_action=”native”,
filter_action=”native”
)
])
])
])
])
], fluid=True)

We define the app layout with Bootstrap rows and cards, where we place controls (dropdown, date range, chart style, MA toggle) alongside the main graph. We add metric cards, two secondary graphs, and a sortable/filterable data table, so we organize everything into a responsive, clean interface that we can wire up to callbacks next. Check out the FULL CODES here.

Copy CodeCopiedUse a different Browser@callback(
[Output(‘main-chart’, ‘figure’),
Output(‘volume-chart’, ‘figure’),
Output(‘returns-chart’, ‘figure’),
Output(‘data-table’, ‘data’),
Output(‘avg-price’, ‘children’),
Output(‘total-volume’, ‘children’),
Output(‘price-range’, ‘children’),
Output(‘data-points’, ‘children’)],
[Input(‘stock-dropdown’, ‘value’),
Input(‘date-picker-range’, ‘start_date’),
Input(‘date-picker-range’, ‘end_date’),
Input(‘chart-type’, ‘value’),
Input(‘show-ma’, ‘value’)]
)
def update_all_charts(selected_stocks, start_date, end_date, chart_type, show_ma):
print(f”Callback triggered with stocks: {selected_stocks}”)

if not selected_stocks:
selected_stocks = [‘AAPL’]

filtered_df = df[
(df[‘Stock’].isin(selected_stocks)) &
(df[‘Date’] >= start_date) &
(df[‘Date’] <= end_date)
].copy()

print(f”Filtered data shape: {filtered_df.shape}”)

if filtered_df.empty:
filtered_df = df[df[‘Stock’].isin(selected_stocks)].copy()
print(f”Using all available data. Shape: {filtered_df.shape}”)

if chart_type == ‘line’:
main_fig = px.line(filtered_df, x=’Date’, y=’Price’, color=’Stock’,
title=f’Stock Prices – {chart_type.title()} View’,
labels={‘Price’: ‘Price ($)’, ‘Date’: ‘Date’})
elif chart_type == ‘area’:
main_fig = px.area(filtered_df, x=’Date’, y=’Price’, color=’Stock’,
title=f’Stock Prices – {chart_type.title()} View’,
labels={‘Price’: ‘Price ($)’, ‘Date’: ‘Date’})
else:
main_fig = px.scatter(filtered_df, x=’Date’, y=’Price’, color=’Stock’,
title=f’Stock Prices – {chart_type.title()} View’,
labels={‘Price’: ‘Price ($)’, ‘Date’: ‘Date’})

if ‘show’ in show_ma:
for stock in selected_stocks:
stock_data = filtered_df[filtered_df[‘Stock’] == stock]
if not stock_data.empty:
main_fig.add_scatter(
x=stock_data[‘Date’],
y=stock_data[‘MA_20′],
mode=’lines’,
name=f'{stock} MA-20′,
line=dict(dash=’dash’, width=2)
)

main_fig.update_layout(height=450, showlegend=True, hovermode=’x unified’)

volume_fig = px.bar(filtered_df, x=’Date’, y=’Volume’, color=’Stock’,
title=’Daily Trading Volume’,
labels={‘Volume’: ‘Volume (shares)’, ‘Date’: ‘Date’})
volume_fig.update_layout(height=300, showlegend=True)

returns_fig = px.histogram(filtered_df.dropna(subset=[‘Returns’]),
x=’Returns’, color=’Stock’,
title=’Daily Returns Distribution’,
labels={‘Returns’: ‘Daily Returns’, ‘count’: ‘Frequency’},
nbins=50)
returns_fig.update_layout(height=300, showlegend=True)

if not filtered_df.empty:
avg_price = f”${filtered_df[‘Price’].mean():.2f}”
total_volume = f”{filtered_df[‘Volume’].sum():,.0f}”
price_range = f”${filtered_df[‘Price’].min():.0f} – ${filtered_df[‘Price’].max():.0f}”
data_points = f”{len(filtered_df):,}”

table_data = filtered_df.nlargest(100, ‘Date’)[
[‘Stock’, ‘Date’, ‘Price’, ‘Volume’, ‘Returns’]
].round(4).to_dict(‘records’)

for row in table_data:
row[‘Date’] = row[‘Date’].strftime(‘%Y-%m-%d’) if pd.notnull(row[‘Date’]) else ”
else:
avg_price = “No data”
total_volume = “No data”
price_range = “No data”
data_points = “0”
table_data = []

return (main_fig, volume_fig, returns_fig, table_data,
avg_price, total_volume, price_range, data_points)

We wire up Dash’s callback to connect our controls to every output, so changing any input instantly updates charts, stats, and the table. We filter the dataframe by selections and dates, build figures (plus optional MA overlays), and compute summary metrics. Finally, we format recent rows for the table so we can inspect the latest results at a glance. Check out the FULL CODES here.

Copy CodeCopiedUse a different Browserif __name__ == ‘__main__’:
print(“Starting Dash app…”)
print(“Available data preview:”)
print(df.head())
print(f”Total rows: {len(df)}”)

app.run(mode=’inline’, port=8050, debug=True, height=1000)

# app.run(debug=True)

We set up the entry point for running the app. We print a quick preview of the dataset to determine what’s available, and then launch the Dash server. In Colab, we can run it inline. For local development, we can simply switch to the regular app.run(debug=True) for desktop development.

In conclusion, we integrate interactive charts, responsive layouts, and Dash’s callback mechanism into a cohesive application. We see how the callbacks orchestrate communication between user input and dynamic updates, turning static visuals into powerful interactive tools. With the ability to operate smoothly both locally and online, this approach provides a versatile foundation that we can extend for broader applications.

Check out the FULL CODES here. Feel free to check out our GitHub Page for Tutorials, Codes and Notebooks. Also, feel free to follow us on Twitter and don’t forget to join our 100k+ ML SubReddit and Subscribe to our Newsletter.

The post How to Design an Interactive Dash and Plotly Dashboard with Callback Mechanisms for Local and Online Deployment? appeared first on MarkTechPost.

<