DEV Community

Interactive Data Visualization: Streamlit, Dash, and Bokeh

Introduction

Modern data analysis requires interactive visualizations that allow users to explore data dynamically. Today we'll explore three powerful Python libraries: Streamlit, Dash, and Bokeh - each offering unique advantages for different use cases.

πŸš€ Streamlit: Simple and Fast

Streamlit is perfect for rapid prototyping with minimal code. It's the go-to choice for data scientists who want to create web apps without web development knowledge.

Key Features

βœ… Zero HTML/CSS knowledge required
βœ… Real-time updates
βœ… Easy deployment
βœ… Rich widget ecosystem

Example: Sales Dashboard

import streamlit as st
import pandas as pd
import plotly.express as px
import numpy as np


# Page configuration
st.set_page_config(page_title="Sales Dashboard", layout="wide")
st.title("πŸ“Š Sales Dashboard")

# Generate sample data
@st.cache_data
def load_data():
    dates = pd.date_range('2024-01-01', '2024-12-31', freq='D')
    data = {
        'date': dates,
        'sales': np.random.normal(1000, 200, len(dates)),
        'region': np.random.choice(['North', 'South', 'East', 'West'], len(dates)),
        'product': np.random.choice(['Product A', 'Product B', 'Product C'], len(dates))
    }
    df = pd.DataFrame(data)
    df['sales'] = df['sales'].clip(lower=0)  # No negative sales
    return df

df = load_data()

# Sidebar filters
st.sidebar.header("Filters")
region = st.sidebar.selectbox("Select Region", df['region'].unique())
product = st.sidebar.selectbox("Select Product", df['product'].unique())

# Filter data
filtered_df = df[(df['region'] == region) & (df['product'] == product)]

# Metrics row
col1, col2, col3 = st.columns(3)
with col1:
    st.metric("Total Sales", f"${filtered_df['sales'].sum():,.2f}")
with col2:
    st.metric("Average Daily Sales", f"${filtered_df['sales'].mean():.2f}")
with col3:
    st.metric("Number of Days", len(filtered_df))

# Charts
col1, col2 = st.columns(2)

with col1:
    # Time series chart
    fig_line = px.line(
        filtered_df, 
        x='date', 
        y='sales', 
        title=f"Sales Trend - {region} ({product})"
    )
    st.plotly_chart(fig_line, use_container_width=True)

with col2:
    # Distribution chart
    fig_hist = px.histogram(
        filtered_df, 
        x='sales', 
        title="Sales Distribution",
        nbins=30
    )
    st.plotly_chart(fig_hist, use_container_width=True)

# Data table
if st.checkbox("Show Raw Data"):
    st.subheader("Raw Data")
    st.dataframe(filtered_df.head(100), use_container_width=True)

Enter fullscreen mode Exit fullscreen mode

🏒 Dash: Enterprise-Ready

Dash offers more control and customization for production applications. It's built on Flask and React, making it perfect for enterprise-grade dashboards.

Key Features

βœ… Production-ready
βœ… Highly customizable
βœ… Advanced callback system
βœ… Enterprise authentication support

Example: Stock Portfolio Tracker

import dash
from dash import dcc, html, Input, Output, callback
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
import yfinance as yf

# Initialize the Dash app
app = dash.Dash(__name__)

# Portfolio data
STOCKS = ['AAPL', 'GOOGL', 'TSLA', 'MSFT', 'AMZN']

# Layout
app.layout = html.Div([
    html.Div([
        html.H1("πŸ’Ό Stock Portfolio Tracker", 
                style={'textAlign': 'center', 'color': '#2E86AB', 'marginBottom': 30}),
    ]),

    html.Div([
        html.Div([
            html.Label("Select Stock:", style={'fontWeight': 'bold'}),
            dcc.Dropdown(
                id='stock-dropdown',
                options=[{'label': stock, 'value': stock} for stock in STOCKS],
                value='AAPL',
                style={'marginBottom': 20}
            ),

            html.Label("Time Period:", style={'fontWeight': 'bold'}),
            dcc.Dropdown(
                id='period-dropdown',
                options=[
                    {'label': '1 Month', 'value': '1mo'},
                    {'label': '3 Months', 'value': '3mo'},
                    {'label': '6 Months', 'value': '6mo'},
                    {'label': '1 Year', 'value': '1y'}
                ],
                value='3mo'
            )
        ], style={'width': '30%', 'display': 'inline-block', 'verticalAlign': 'top'}),

        html.Div([
            html.Div(id='stock-info')
        ], style={'width': '70%', 'display': 'inline-block', 'paddingLeft': 20})
    ]),

    html.Div([
        dcc.Graph(id='stock-chart')
    ]),

    html.Div([
        dcc.Graph(id='volume-chart')
    ])
])

@app.callback(
    [Output('stock-chart', 'figure'),
     Output('volume-chart', 'figure'),
     Output('stock-info', 'children')],
    [Input('stock-dropdown', 'value'),
     Input('period-dropdown', 'value')]
)
def update_dashboard(selected_stock, period):
    # Fetch stock data
    try:
        stock_data = yf.download(selected_stock, period=period)

        # Stock price chart
        fig_price = go.Figure()
        fig_price.add_trace(go.Candlestick(
            x=stock_data.index,
            open=stock_data['Open'],
            high=stock_data['High'],
            low=stock_data['Low'],
            close=stock_data['Close'],
            name=selected_stock
        ))
        fig_price.update_layout(
            title=f"{selected_stock} Stock Price",
            yaxis_title="Price ($)",
            xaxis_title="Date"
        )

        # Volume chart
        fig_volume = px.bar(
            x=stock_data.index,
            y=stock_data['Volume'],
            title=f"{selected_stock} Trading Volume"
        )

        # Stock info
        current_price = stock_data['Close'].iloc[-1]
        price_change = current_price - stock_data['Close'].iloc[-2]
        price_change_pct = (price_change / stock_data['Close'].iloc[-2]) * 100

        info = html.Div([
            html.H3(f"{selected_stock} Information"),
            html.P(f"Current Price: ${current_price:.2f}"),
            html.P(f"Change: ${price_change:.2f} ({price_change_pct:.2f}%)",
                   style={'color': 'green' if price_change >= 0 else 'red'}),
            html.P(f"Period High: ${stock_data['High'].max():.2f}"),
            html.P(f"Period Low: ${stock_data['Low'].min():.2f}")
        ])

        return fig_price, fig_volume, info

    except Exception as e:
        # Error handling
        empty_fig = go.Figure()
        error_info = html.P(f"Error loading data: {str(e)}")
        return empty_fig, empty_fig, error_info

if __name__ == '__main__':
    app.run_server(debug=True)
Enter fullscreen mode Exit fullscreen mode

⚑ Bokeh: Advanced Visualizations

Bokeh handles large datasets and complex interactions efficiently. It's perfect for creating sophisticated, publication-ready visualizations.

Key Features

βœ… High performance with large datasets
βœ… Advanced interactions
βœ… Real-time capabilities
βœ… Server and standalone applications

Example: Real-time Data Monitor

from bokeh.plotting import figure, curdoc
from bokeh.layouts import column, row
from bokeh.models import ColumnDataSource, Select, Button
from bokeh.models.widgets import DataTable, TableColumn
import numpy as np
import pandas as pd
from datetime import datetime
from functools import partial

# Global data source
source = ColumnDataSource(data=dict(
    x=[], y=[], timestamp=[], sensor_id=[], color=[]
))

# Create main plot
def create_time_plot():
    p = figure(
        title="Real-time Sensor Data Monitor",
        x_axis_label="Time",
        y_axis_label="Value",
        width=800,
        height=400,
        x_axis_type='datetime'
    )

    p.line('timestamp', 'y', source=source, line_width=2, color='blue', alpha=0.8)
    p.circle('timestamp', 'y', source=source, size=8, color='color', alpha=0.6)

    return p

# Create histogram
def create_histogram():
    p = figure(
        title="Value Distribution",
        x_axis_label="Value",
        y_axis_label="Frequency",
        width=400,
        height=300
    )

    return p

# Controls
sensor_select = Select(
    title="Sensor ID:", 
    value="All", 
    options=["All", "Sensor_1", "Sensor_2", "Sensor_3"]
)

start_button = Button(label="Start Monitoring", button_type="success")
stop_button = Button(label="Stop Monitoring", button_type="danger")

# Data table
columns = [
    TableColumn(field="timestamp", title="Timestamp"),
    TableColumn(field="y", title="Value"),
    TableColumn(field="sensor_id", title="Sensor ID")
]
data_table = DataTable(source=source, columns=columns, width=400, height=200)

# Callback for data generation
def update_data():
    """Generate new data point"""
    sensors = ["Sensor_1", "Sensor_2", "Sensor_3"]
    colors = ["red", "blue", "green"]

    new_data = {
        'timestamp': [datetime.now()],
        'y': [np.random.normal(50, 10)],
        'sensor_id': [np.random.choice(sensors)],
        'color': [np.random.choice(colors)]
    }

    # Update data source
    current_data = source.data.copy()
    for key in new_data:
        if key in current_data:
            current_data[key].extend(new_data[key])
            # Keep only last 100 points
            if len(current_data[key]) > 100:
                current_data[key] = current_data[key][-100:]
        else:
            current_data[key] = new_data[key]

    source.data = current_data

# Button callbacks
def start_monitoring():
    curdoc().add_periodic_callback(update_data, 1000)

def stop_monitoring():
    curdoc().remove_periodic_callback(update_data)

start_button.on_click(start_monitoring)
stop_button.on_click(stop_monitoring)

# Create plots
time_plot = create_time_plot()
histogram = create_histogram()

# Layout
controls = column(sensor_select, row(start_button, stop_button))
plots = row(time_plot, column(histogram, data_table))
layout = column(controls, plots)

# Add to document
curdoc().add_root(layout)
curdoc().title = "Real-time Data Monitor"
Enter fullscreen mode Exit fullscreen mode

πŸ“¦ Deployment Guide

Streamlit Cloud (Recommended)

Create requirements.txt:

txtstreamlit==1.28.0
pandas==2.0.3
plotly==5.15.0
numpy==1.24.3
Enter fullscreen mode Exit fullscreen mode

Deploy Steps:

# Push to GitHub
git add .
git commit -m "Initial commit"
git push origin main

# Visit share.streamlit.io
# Connect your GitHub repository
# Auto-deploy!
Enter fullscreen mode Exit fullscreen mode

Dash on Heroku

Prepare files:

# Add to your Dash app
server = app.server

if __name__ == '__main__':
    app.run_server(debug=False)
Enter fullscreen mode Exit fullscreen mode

Create Procfile:

web: gunicorn app:server
Enter fullscreen mode Exit fullscreen mode

Deploy:

heroku create your-app-name
git push heroku main
Enter fullscreen mode Exit fullscreen mode

***Bokeh Server*

Start Bokeh server:**

bashbokeh serve --show --allow-websocket-origin=yourdomain.com app.py
Enter fullscreen mode Exit fullscreen mode

For cloud deployment:

bashbokeh serve --port=$PORT --allow-websocket-origin=* app.py
Enter fullscreen mode Exit fullscreen mode

πŸ“Š Comparison Matrix

Conclusion

Interactive data visualization has never been more accessible. Whether you choose Streamlit for rapid prototyping, Dash for enterprise applications, or Bokeh for advanced visualizations, you now have the power to create compelling, interactive dashboards that transform how users interact with data.
Start with Streamlit to get your feet wet, graduate to Dash when you need production features, and leverage Bokeh when performance and advanced interactions are critical.
The future of data visualization is interactive - and these tools are your gateway to that future.

Top comments (1)

Collapse
 
andy_michaelcalizayalad profile image
ANDY MICHAEL CALIZAYA LADERA

nice