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)