Advanced Example
This example demonstrates a more complex Dash application with interactive callbacks, multiple components, and file uploads running on nanoHUB.
Interactive Data Explorer
This example creates an interactive data exploration dashboard with:
File upload capability
Dynamic graph selection
Interactive filtering
Data table display
Cell 1: Load the extension and configure environment
%load_ext nanohubdash
%set_dash_env --port 8001
Cell 2: Create the advanced Dash app
import os
import base64
import io
from dash import Dash, html, dcc, dash_table, callback, Input, Output, State
import plotly.express as px
import pandas as pd
import numpy as np
# Create the Dash app
app = Dash(__name__,
routes_pathname_prefix=os.getenv("DASH_ROUTES_PATHNAME_PREFIX"),
requests_pathname_prefix=os.getenv("DASH_REQUESTS_PATHNAME_PREFIX"),
suppress_callback_exceptions=True
)
# Sample dataset
np.random.seed(42)
sample_df = pd.DataFrame({
'x': np.random.randn(100),
'y': np.random.randn(100),
'category': np.random.choice(['A', 'B', 'C'], 100),
'size': np.random.randint(10, 100, 100)
})
app.layout = html.Div([
html.H1("Interactive Data Explorer", style={'textAlign': 'center'}),
html.Hr(),
# Tabs for different sections
dcc.Tabs([
# Tab 1: Upload and visualize data
dcc.Tab(label='Data Upload', children=[
html.Div([
html.H3("Upload Your Data"),
dcc.Upload(
id='upload-data',
children=html.Div([
'Drag and Drop or ',
html.A('Select a CSV File')
]),
style={
'width': '100%',
'height': '60px',
'lineHeight': '60px',
'borderWidth': '1px',
'borderStyle': 'dashed',
'borderRadius': '5px',
'textAlign': 'center',
'margin': '10px'
},
multiple=False
),
html.Div(id='upload-status'),
html.Hr(),
html.H3("Or Use Sample Data"),
html.Button("Load Sample Data", id='load-sample', n_clicks=0),
], style={'padding': '20px'})
]),
# Tab 2: Visualization
dcc.Tab(label='Visualization', children=[
html.Div([
html.Div([
html.Label("Select Chart Type:"),
dcc.Dropdown(
id='chart-type',
options=[
{'label': 'Scatter Plot', 'value': 'scatter'},
{'label': 'Histogram', 'value': 'histogram'},
{'label': 'Box Plot', 'value': 'box'},
{'label': 'Violin Plot', 'value': 'violin'}
],
value='scatter'
),
], style={'width': '30%', 'display': 'inline-block', 'padding': '10px'}),
html.Div([
html.Label("X-Axis Column:"),
dcc.Dropdown(id='x-column'),
], style={'width': '30%', 'display': 'inline-block', 'padding': '10px'}),
html.Div([
html.Label("Y-Axis Column:"),
dcc.Dropdown(id='y-column'),
], style={'width': '30%', 'display': 'inline-block', 'padding': '10px'}),
]),
dcc.Graph(id='main-graph'),
]),
# Tab 3: Data Table
dcc.Tab(label='Data Table', children=[
html.Div([
html.H3("Data Preview"),
html.Div(id='data-table-container')
], style={'padding': '20px'})
]),
# Tab 4: Statistics
dcc.Tab(label='Statistics', children=[
html.Div([
html.H3("Descriptive Statistics"),
html.Div(id='stats-container')
], style={'padding': '20px'})
])
]),
# Store for the dataframe
dcc.Store(id='stored-data')
])
def parse_contents(contents, filename):
"""Parse uploaded file contents."""
content_type, content_string = contents.split(',')
decoded = base64.b64decode(content_string)
try:
if 'csv' in filename:
df = pd.read_csv(io.StringIO(decoded.decode('utf-8')))
elif 'xls' in filename:
df = pd.read_excel(io.BytesIO(decoded))
else:
return None
return df
except Exception as e:
print(e)
return None
@callback(
Output('stored-data', 'data'),
Output('upload-status', 'children'),
Input('upload-data', 'contents'),
Input('load-sample', 'n_clicks'),
State('upload-data', 'filename'),
prevent_initial_call=True
)
def update_data(contents, n_clicks, filename):
from dash import ctx
triggered = ctx.triggered_id
if triggered == 'load-sample':
return sample_df.to_json(date_format='iso', orient='split'), \
html.Div("Sample data loaded!", style={'color': 'green'})
if contents is not None:
df = parse_contents(contents, filename)
if df is not None:
return df.to_json(date_format='iso', orient='split'), \
html.Div(f"File '{filename}' uploaded successfully!", style={'color': 'green'})
return None, html.Div("Error parsing file.", style={'color': 'red'})
return None, ""
@callback(
Output('x-column', 'options'),
Output('y-column', 'options'),
Output('x-column', 'value'),
Output('y-column', 'value'),
Input('stored-data', 'data')
)
def update_column_options(data):
if data is None:
return [], [], None, None
df = pd.read_json(io.StringIO(data), orient='split')
numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
options = [{'label': col, 'value': col} for col in numeric_cols]
x_val = numeric_cols[0] if numeric_cols else None
y_val = numeric_cols[1] if len(numeric_cols) > 1 else numeric_cols[0] if numeric_cols else None
return options, options, x_val, y_val
@callback(
Output('main-graph', 'figure'),
Input('chart-type', 'value'),
Input('x-column', 'value'),
Input('y-column', 'value'),
Input('stored-data', 'data')
)
def update_graph(chart_type, x_col, y_col, data):
if data is None or x_col is None:
return px.scatter(title="Load data to visualize")
df = pd.read_json(io.StringIO(data), orient='split')
if chart_type == 'scatter':
fig = px.scatter(df, x=x_col, y=y_col, title=f'{x_col} vs {y_col}')
elif chart_type == 'histogram':
fig = px.histogram(df, x=x_col, title=f'Distribution of {x_col}')
elif chart_type == 'box':
fig = px.box(df, y=y_col, title=f'Box Plot of {y_col}')
elif chart_type == 'violin':
fig = px.violin(df, y=y_col, title=f'Violin Plot of {y_col}')
else:
fig = px.scatter(df, x=x_col, y=y_col)
return fig
@callback(
Output('data-table-container', 'children'),
Input('stored-data', 'data')
)
def update_table(data):
if data is None:
return html.P("No data loaded. Please upload a file or load sample data.")
df = pd.read_json(io.StringIO(data), orient='split')
return dash_table.DataTable(
data=df.head(100).to_dict('records'),
columns=[{'name': i, 'id': i} for i in df.columns],
page_size=20,
style_table={'overflowX': 'auto'},
style_cell={'textAlign': 'left'},
style_header={'backgroundColor': 'paleturquoise', 'fontWeight': 'bold'}
)
@callback(
Output('stats-container', 'children'),
Input('stored-data', 'data')
)
def update_stats(data):
if data is None:
return html.P("No data loaded. Please upload a file or load sample data.")
df = pd.read_json(io.StringIO(data), orient='split')
stats = df.describe().round(3)
return dash_table.DataTable(
data=stats.reset_index().to_dict('records'),
columns=[{'name': 'Statistic', 'id': 'index'}] + \
[{'name': i, 'id': i} for i in stats.columns],
style_table={'overflowX': 'auto'},
style_cell={'textAlign': 'right'},
style_header={'backgroundColor': 'lightblue', 'fontWeight': 'bold'}
)
if __name__ == "__main__":
app.run(
jupyter_server_url=os.environ.get("DASH_BASE_PROXY"),
host=os.environ.get("DASH_HOST", "0.0.0.0"),
port=os.environ.get("DASH_PORT", "8001"),
debug=False,
)
Key Features Demonstrated
Multiple Tabs: Organized interface with tabs for different functionality
File Upload: Support for uploading CSV files
Dynamic Dropdowns: Column selectors that update based on loaded data
Multiple Chart Types: Switch between scatter, histogram, box, and violin plots
Data Tables: Interactive data table with pagination
Statistics View: Automatic descriptive statistics calculation
State Management: Using
dcc.Storeto share data between callbacks
Using Environment Variables
You can access the configured environment variables in your advanced app:
import os
# Get the base proxy URL for constructing external links
base_proxy = os.environ.get("DASH_BASE_PROXY", "")
# Get the configured port
port = os.environ.get("DASH_PORT", "8001")
# Example: Create a shareable link
requests_prefix = os.environ.get("DASH_REQUESTS_PATHNAME_PREFIX", "/")
full_url = f"{base_proxy}{requests_prefix}"
print(f"Your app is available at: {full_url}")
Running with start_dash
Save the advanced app to a file called advanced_app.py and run:
start_dash --app advanced_app.py --debug True
The --debug True flag enables:
Hot reloading when you modify the code
Detailed error messages in the browser
Stream logging from wrwroxy
Tips for Advanced Applications
Use suppress_callback_exceptions: Set
suppress_callback_exceptions=Truewhen using dynamic layoutsHandle missing data gracefully: Always check if data exists before processing
Use dcc.Store for shared state: Store data that needs to be accessed by multiple callbacks
Optimize large datasets: Use pagination in data tables and limit displayed data
Add loading states: Use
dcc.Loadingcomponents for better user experience