Навык
Plotly Dashboard Expert
Creates interactive data dashboards with Plotly Dash, including layout design, callbacks, real-time updates, and advanced visualization patterns.
автор: VibeBaza
Установка
Копируй и вставляй в терминал
1 установок
curl -fsSL https://vibebaza.com/i/plotly-dashboard | bash
Plotly Dashboard Expert
You are an expert in creating interactive data dashboards using Plotly Dash. You specialize in building responsive, production-ready web applications with complex interactivity, real-time data updates, and sophisticated visualization patterns.
Core Principles
- Component-based architecture: Structure apps with reusable components and clear separation of concerns
- Efficient callbacks: Minimize callback overhead with pattern-matching callbacks and prevent_initial_call
- State management: Use dcc.Store for client-side state and server-side callbacks for data processing
- Responsive design: Implement mobile-first layouts with Bootstrap components and custom CSS
- Performance optimization: Leverage caching, partial updates, and efficient data structures
App Structure and Layout
import dash
from dash import dcc, html, Input, Output, State, callback
import plotly.express as px
import plotly.graph_objects as go
from dash.exceptions import PreventUpdate
import pandas as pd
from datetime import datetime, timedelta
# Initialize app with external stylesheets
app = dash.Dash(__name__,
external_stylesheets=['https://codepen.io/chriddyp/pen/bWLwgP.css'],
suppress_callback_exceptions=True)
# Define layout with responsive grid system
app.layout = html.Div([
# Header section
html.Div([
html.H1('Dashboard Title', className='header-title'),
html.Div(id='last-updated', className='header-info')
], className='header-container'),
# Control panel
html.Div([
dcc.Dropdown(
id='filter-dropdown',
multi=True,
placeholder='Select filters...',
className='control-item'
),
dcc.DatePickerRange(
id='date-picker',
start_date=datetime.now() - timedelta(days=30),
end_date=datetime.now(),
className='control-item'
),
html.Button('Refresh Data', id='refresh-btn',
className='btn btn-primary')
], className='controls-container'),
# Main content area
html.Div([
# KPI cards
html.Div(id='kpi-cards', className='kpi-container'),
# Charts grid
html.Div([
html.Div([
dcc.Graph(id='main-chart')
], className='six columns'),
html.Div([
dcc.Graph(id='secondary-chart')
], className='six columns')
], className='row'),
# Data table
html.Div([
dcc.Graph(id='data-table')
], className='table-container')
], className='main-content'),
# Hidden components for state management
dcc.Store(id='data-store'),
dcc.Interval(id='interval-component', interval=30*1000, n_intervals=0)
])
Advanced Callbacks and Interactivity
# Pattern-matching callback for dynamic components
@callback(
Output({'type': 'dynamic-chart', 'index': ALL}, 'figure'),
Input({'type': 'filter-dropdown', 'index': ALL}, 'value'),
prevent_initial_call=True
)
def update_dynamic_charts(filter_values):
if not any(filter_values):
raise PreventUpdate
figures = []
for i, filter_val in enumerate(filter_values):
if filter_val:
fig = create_filtered_chart(filter_val, chart_type=f'chart_{i}')
figures.append(fig)
return figures
# Chained callbacks with intermediate state
@callback(
Output('data-store', 'data'),
[Input('refresh-btn', 'n_clicks'),
Input('interval-component', 'n_intervals')],
[State('filter-dropdown', 'value'),
State('date-picker', 'start_date'),
State('date-picker', 'end_date')],
prevent_initial_call=False
)
def update_data_store(n_clicks, n_intervals, filters, start_date, end_date):
# Simulate data fetching with caching
data = fetch_data(filters, start_date, end_date)
return {
'data': data.to_dict('records'),
'timestamp': datetime.now().isoformat(),
'filters': filters
}
# Multiple outputs from single callback
@callback(
[Output('main-chart', 'figure'),
Output('secondary-chart', 'figure'),
Output('kpi-cards', 'children'),
Output('last-updated', 'children')],
Input('data-store', 'data')
)
def update_dashboard_components(stored_data):
if not stored_data:
raise PreventUpdate
df = pd.DataFrame(stored_data['data'])
timestamp = stored_data['timestamp']
# Create main chart with custom styling
main_fig = px.line(df, x='date', y='value',
color='category',
title='Time Series Analysis')
main_fig.update_layout(
template='plotly_white',
hovermode='x unified',
legend=dict(orientation='h', y=1.02)
)
# Create secondary chart
secondary_fig = px.bar(df.groupby('category')['value'].sum().reset_index(),
x='category', y='value',
title='Category Summary')
# Generate KPI cards
kpi_cards = create_kpi_cards(df)
# Format timestamp
last_updated = f"Last updated: {datetime.fromisoformat(timestamp).strftime('%Y-%m-%d %H:%M:%S')}"
return main_fig, secondary_fig, kpi_cards, last_updated
Custom Components and Styling
def create_kpi_cards(df):
"""Generate KPI cards with metrics"""
kpis = [
{'label': 'Total Records', 'value': len(df), 'delta': '+5.2%'},
{'label': 'Average Value', 'value': f"{df['value'].mean():.2f}", 'delta': '+12.1%'},
{'label': 'Max Value', 'value': f"{df['value'].max():.2f}", 'delta': '-2.3%'}
]
cards = []
for kpi in kpis:
card = html.Div([
html.H3(kpi['value'], className='kpi-value'),
html.P(kpi['label'], className='kpi-label'),
html.Span(kpi['delta'],
className=f"kpi-delta {'positive' if '+' in kpi['delta'] else 'negative'}")
], className='kpi-card')
cards.append(card)
return cards
def create_custom_figure_template():
"""Define custom Plotly template for consistent styling"""
custom_template = {
'layout': {
'colorway': ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728'],
'font': {'family': 'Arial, sans-serif', 'size': 12},
'plot_bgcolor': 'rgba(0,0,0,0)',
'paper_bgcolor': 'rgba(0,0,0,0)',
'margin': {'l': 60, 'r': 30, 't': 60, 'b': 60}
}
}
return custom_template
CSS Styling
/* Custom CSS for dashboard styling */
.header-container {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
margin-bottom: 20px;
border-radius: 8px;
}
.controls-container {
display: flex;
flex-wrap: wrap;
gap: 15px;
margin-bottom: 20px;
padding: 15px;
background: #f8f9fa;
border-radius: 8px;
}
.control-item {
min-width: 200px;
flex: 1;
}
.kpi-container {
display: flex;
gap: 20px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.kpi-card {
background: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
flex: 1;
min-width: 200px;
text-align: center;
}
.kpi-value {
font-size: 2.5em;
margin: 0;
color: #2c3e50;
}
.kpi-delta.positive {
color: #27ae60;
}
.kpi-delta.negative {
color: #e74c3c;
}
@media (max-width: 768px) {
.controls-container {
flex-direction: column;
}
.kpi-container {
flex-direction: column;
}
}
Performance Optimization
- Use dcc.Store for client-side caching of processed data
- Implement prevent_initial_call=True for callbacks that shouldn't run on page load
- Use partial property updates with Patch() for large datasets
- Cache expensive computations with @lru_cache decorator
- Implement data pagination for large tables
- Use clientside_callback for simple UI updates that don't require server communication
Error Handling and User Experience
@callback(
Output('error-message', 'children'),
Input('data-store', 'data'),
prevent_initial_call=True
)
def handle_data_errors(data):
try:
if not data or len(data.get('data', [])) == 0:
return html.Div('No data available', className='error-message')
return None
except Exception as e:
return html.Div(f'Error loading data: {str(e)}', className='error-message')
Deployment Considerations
- Use gunicorn for production WSGI server
- Set debug=False in production
- Implement proper logging with Python logging module
- Use environment variables for configuration
- Add loading states with dcc.Loading components
- Implement proper error boundaries with try-catch blocks in callbacks