| 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) |