dash-mcp / simple_app.py
mic3333's picture
update new version of app
20706fe
import os
import base64
import io
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from dash import Dash, html, dcc, Input, Output, State, callback_context
import dash_bootstrap_components as dbc
# Initialize Dash app
app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
server = app.server
# App layout
app.layout = dbc.Container([
dbc.Row([
dbc.Col([
html.H1("πŸ“Š Interactive Data Dashboard", className="text-center mb-4"),
html.P("Upload data and create interactive visualizations with different chart types!",
className="text-center text-muted"),
html.Hr(),
], width=12)
]),
dbc.Row([
dbc.Col([
dbc.Card([
dbc.CardBody([
html.H4("πŸ“ Data Upload", className="card-title"),
dcc.Upload(
id='upload-data',
children=html.Div([
'Drag and Drop or ',
html.A('Select Files')
]),
style={
'width': '100%',
'height': '60px',
'lineHeight': '60px',
'borderWidth': '1px',
'borderStyle': 'dashed',
'borderRadius': '5px',
'textAlign': 'center',
'margin': '10px'
},
multiple=False,
accept='.csv,.xlsx,.txt'
),
html.Div(id='upload-status', className="mt-2"),
html.Hr(),
html.H4("πŸ“Š Quick Analytics", className="card-title"),
dbc.ButtonGroup([
dbc.Button("Summary Stats", id="stats-btn", size="sm"),
dbc.Button("Correlations", id="corr-btn", size="sm"),
dbc.Button("Missing Data", id="missing-btn", size="sm"),
], className="w-100"),
html.Div(id="quick-analytics", className="mt-3")
])
])
], width=4),
dbc.Col([
dbc.Card([
dbc.CardBody([
html.H4("πŸ“ˆ Visualizations", className="card-title"),
# Chart controls
dbc.Row([
dbc.Col([
html.Label("Chart Type:", className="form-label"),
dcc.Dropdown(
id='chart-type',
options=[
{'label': 'Scatter Plot', 'value': 'scatter'},
{'label': 'Line Chart', 'value': 'line'},
{'label': 'Bar Chart', 'value': 'bar'},
{'label': 'Histogram', 'value': 'histogram'},
{'label': 'Box Plot', 'value': 'box'},
{'label': 'Heatmap', 'value': 'heatmap'},
{'label': 'Pie Chart', 'value': 'pie'}
],
value='scatter',
className="mb-2"
)
], width=6),
dbc.Col([
html.Label("Color By:", className="form-label"),
dcc.Dropdown(
id='color-column',
placeholder="Select column (optional)",
className="mb-2"
)
], width=6)
]),
dbc.Row([
dbc.Col([
html.Label("X-Axis:", className="form-label"),
dcc.Dropdown(
id='x-column',
placeholder="Select X column"
)
], width=6),
dbc.Col([
html.Label("Y-Axis:", className="form-label"),
dcc.Dropdown(
id='y-column',
placeholder="Select Y column"
)
], width=6)
], className="mb-3"),
dcc.Graph(id='main-graph', style={'height': '500px'}),
])
]),
dbc.Card([
dbc.CardBody([
html.H4("πŸ” Data Explorer", className="card-title"),
html.Div(id='data-table')
])
], className="mt-3")
], width=8)
], className="mt-4"),
# Store components
dcc.Store(id='stored-data'),
], fluid=True)
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, "Unsupported file type"
return df, None
except Exception as e:
return None, f"Error processing file: {str(e)}"
@app.callback(
[Output('stored-data', 'data'),
Output('upload-status', 'children'),
Output('data-table', 'children'),
Output('x-column', 'options'),
Output('y-column', 'options'),
Output('color-column', 'options'),
Output('x-column', 'value'),
Output('y-column', 'value')],
[Input('upload-data', 'contents')],
[State('upload-data', 'filename')]
)
def update_data(contents, filename):
"""Update data when file is uploaded"""
if contents is None:
return None, "", "", [], [], [], None, None
df, error = parse_contents(contents, filename)
if error:
return None, dbc.Alert(error, color="danger"), "", [], [], [], None, None
# Create data table preview
table = dbc.Table.from_dataframe(
df.head(10),
striped=True,
bordered=True,
hover=True,
size='sm'
)
success_msg = dbc.Alert([
html.H6(f"βœ… File uploaded successfully!"),
html.P(f"Shape: {df.shape[0]} rows Γ— {df.shape[1]} columns"),
html.P(f"Columns: {', '.join(df.columns.tolist())}")
], color="success")
# Create column options for dropdowns
all_columns = [{'label': col, 'value': col} for col in df.columns]
numeric_columns = [{'label': col, 'value': col} for col in df.select_dtypes(include=['number']).columns]
# Set default values - prefer numeric columns for x and y
default_x = numeric_columns[0]['value'] if numeric_columns else all_columns[0]['value'] if all_columns else None
default_y = numeric_columns[1]['value'] if len(numeric_columns) > 1 else (numeric_columns[0]['value'] if numeric_columns else (all_columns[1]['value'] if len(all_columns) > 1 else None))
return df.to_dict('records'), success_msg, table, all_columns, all_columns, all_columns, default_x, default_y
@app.callback(
Output('quick-analytics', 'children'),
[Input('stats-btn', 'n_clicks'),
Input('corr-btn', 'n_clicks'),
Input('missing-btn', 'n_clicks')],
[State('stored-data', 'data')]
)
def quick_analytics(stats_clicks, corr_clicks, missing_clicks, data):
"""Handle quick analytics buttons"""
if not data:
return ""
df = pd.DataFrame(data)
ctx = callback_context
if not ctx.triggered:
return ""
button_id = ctx.triggered[0]['prop_id'].split('.')[0]
if button_id == 'stats-btn':
stats = df.describe()
return dbc.Alert([
html.H6("πŸ“Š Summary Statistics"),
dbc.Table.from_dataframe(stats.reset_index(), size='sm')
], color="light")
elif button_id == 'corr-btn':
numeric_df = df.select_dtypes(include=['number'])
if len(numeric_df.columns) > 1:
corr = numeric_df.corr()
fig = px.imshow(corr, text_auto=True, aspect="auto",
title="Correlation Matrix")
return dcc.Graph(figure=fig, style={'height': '300px'})
return dbc.Alert("No numeric columns for correlation analysis", color="warning")
elif button_id == 'missing-btn':
missing = df.isnull().sum()
missing = missing[missing > 0]
if missing.empty:
return dbc.Alert("βœ… No missing values!", color="success")
return dbc.Alert([
html.H6("⚠️ Missing Values"),
html.Pre(missing.to_string())
], color="warning")
return ""
@app.callback(
Output('main-graph', 'figure'),
[Input('stored-data', 'data'),
Input('chart-type', 'value'),
Input('x-column', 'value'),
Input('y-column', 'value'),
Input('color-column', 'value')]
)
def update_main_graph(data, chart_type, x_col, y_col, color_col):
"""Update main visualization based on user selections"""
if not data:
fig = go.Figure()
fig.add_annotation(text="Upload data to see visualizations",
x=0.5, y=0.5, showarrow=False,
font=dict(size=16, color="gray"))
fig.update_layout(template="plotly_white")
return fig
df = pd.DataFrame(data)
# Handle cases where columns aren't selected yet
if not x_col and not y_col:
fig = go.Figure()
fig.add_annotation(text="Select columns to create visualization",
x=0.5, y=0.5, showarrow=False,
font=dict(size=16, color="gray"))
fig.update_layout(template="plotly_white")
return fig
try:
# Create visualization based on chart type
if chart_type == 'scatter':
if x_col and y_col:
fig = px.scatter(df, x=x_col, y=y_col, color=color_col,
title=f"Scatter Plot: {y_col} vs {x_col}")
else:
fig = go.Figure()
fig.add_annotation(text="Select both X and Y columns for scatter plot",
x=0.5, y=0.5, showarrow=False)
elif chart_type == 'line':
if x_col and y_col:
fig = px.line(df, x=x_col, y=y_col, color=color_col,
title=f"Line Chart: {y_col} vs {x_col}")
else:
fig = go.Figure()
fig.add_annotation(text="Select both X and Y columns for line chart",
x=0.5, y=0.5, showarrow=False)
elif chart_type == 'bar':
if x_col and y_col:
fig = px.bar(df, x=x_col, y=y_col, color=color_col,
title=f"Bar Chart: {y_col} by {x_col}")
elif x_col:
fig = px.bar(df[x_col].value_counts().reset_index(),
x='index', y=x_col,
title=f"Value Counts: {x_col}")
else:
fig = go.Figure()
fig.add_annotation(text="Select at least X column for bar chart",
x=0.5, y=0.5, showarrow=False)
elif chart_type == 'histogram':
if x_col:
fig = px.histogram(df, x=x_col, color=color_col,
title=f"Histogram: {x_col}")
else:
fig = go.Figure()
fig.add_annotation(text="Select X column for histogram",
x=0.5, y=0.5, showarrow=False)
elif chart_type == 'box':
if y_col:
fig = px.box(df, x=color_col, y=y_col,
title=f"Box Plot: {y_col}" + (f" by {color_col}" if color_col else ""))
elif x_col:
fig = px.box(df, y=x_col,
title=f"Box Plot: {x_col}")
else:
fig = go.Figure()
fig.add_annotation(text="Select a column for box plot",
x=0.5, y=0.5, showarrow=False)
elif chart_type == 'heatmap':
numeric_cols = df.select_dtypes(include=['number']).columns
if len(numeric_cols) > 1:
corr_matrix = df[numeric_cols].corr()
fig = px.imshow(corr_matrix,
text_auto=True,
aspect="auto",
title="Correlation Heatmap",
color_continuous_scale='RdBu_r')
else:
fig = go.Figure()
fig.add_annotation(text="Need at least 2 numeric columns for heatmap",
x=0.5, y=0.5, showarrow=False)
elif chart_type == 'pie':
if x_col:
value_counts = df[x_col].value_counts()
fig = px.pie(values=value_counts.values,
names=value_counts.index,
title=f"Pie Chart: {x_col}")
else:
fig = go.Figure()
fig.add_annotation(text="Select X column for pie chart",
x=0.5, y=0.5, showarrow=False)
else:
fig = go.Figure()
fig.add_annotation(text="Select a chart type",
x=0.5, y=0.5, showarrow=False)
fig.update_layout(template="plotly_white", height=500)
return fig
except Exception as e:
fig = go.Figure()
fig.add_annotation(text=f"Error creating chart: {str(e)}",
x=0.5, y=0.5, showarrow=False,
font=dict(color="red"))
fig.update_layout(template="plotly_white")
return fig
if __name__ == '__main__':
app.run_server(host='0.0.0.0', port=8050, debug=True)