|
|
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 |
|
|
|
|
|
|
|
|
app = Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP]) |
|
|
server = app.server |
|
|
|
|
|
|
|
|
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"), |
|
|
|
|
|
|
|
|
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"), |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
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] |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
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) |