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

  1. Multiple Tabs: Organized interface with tabs for different functionality

  2. File Upload: Support for uploading CSV files

  3. Dynamic Dropdowns: Column selectors that update based on loaded data

  4. Multiple Chart Types: Switch between scatter, histogram, box, and violin plots

  5. Data Tables: Interactive data table with pagination

  6. Statistics View: Automatic descriptive statistics calculation

  7. State Management: Using dcc.Store to 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

  1. Use suppress_callback_exceptions: Set suppress_callback_exceptions=True when using dynamic layouts

  2. Handle missing data gracefully: Always check if data exists before processing

  3. Use dcc.Store for shared state: Store data that needs to be accessed by multiple callbacks

  4. Optimize large datasets: Use pagination in data tables and limit displayed data

  5. Add loading states: Use dcc.Loading components for better user experience