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