import dash from dash import dcc, html from dash.dependencies import Input, Output from utils.figures import serve_prediction_plot app = dash.Dash( __name__, meta_tags=[{"name": "viewport", "content": "width=device-width, initial-scale=1"}] ) app.title = "code213 Supervised Learning Analytics Explorer" server = app.server app.layout = html.Div( # --- Full Screen Viewport with Gradient Pink Background --- style={ 'fontFamily': 'system-ui, sans-serif', 'background': 'linear-gradient(135deg, #fff5f5 0%, #ffe4e6 50%, #fbcfe8 100%)', 'minHeight': '100vh', 'padding': '40px 20px' }, children=[ # Max-width Inner Content Wrapper html.Div( style={'maxWidth': '1400px', 'margin': '0 auto'}, children=[ # Header Section html.H1("Supervised Learning Module: KNN Explorer", style={'textAlign': 'center', 'color': '#1e1b4b', 'fontWeight': '800'}), html.P("code213 Data Science Bootcamp • Instructor: Latreche Sara", style={'textAlign': 'center', 'color': '#be123c', 'marginTop': '-10px', 'fontWeight': 'bold', 'letterSpacing': '0.5px'}), html.Div( style={'display': 'flex', 'flexWrap': 'wrap', 'gap': '30px', 'marginTop': '35px'}, children=[ # Sidebar Panel (Solid, opaque glass panel for readability) html.Div( style={ 'flex': '1', 'minWidth': '320px', 'backgroundColor': 'rgba(255, 255, 255, 0.9)', 'padding': '25px', 'borderRadius': '16px', 'border': '1px solid rgba(255, 255, 255, 0.7)', 'boxShadow': '0 10px 15px -3px rgba(0, 0, 0, 0.05)' }, children=[ html.H4("Hyperparameters", style={'marginTop': '0', 'color': '#1e1b4b', 'borderBottom': '3px solid #e11d48', 'paddingBottom': '8px', 'fontWeight': '700'}), html.Label("Dataset Selection", style={'fontWeight': 'bold', 'display': 'block', 'marginTop': '15px', 'color': '#334155'}), dcc.Dropdown( id='dataset-selector', options=[ {'label': 'Moons Topology', 'value': 'moons'}, {'label': 'Circles Topology', 'value': 'circles'}, {'label': 'Linear Separable', 'value': 'linear'} ], value='moons', clearable=False, style={'marginBottom': '15px'} ), html.Label("Dataset Noise Level", style={'fontWeight': 'bold', 'color': '#334155', 'display': 'block', 'marginBottom': '5px'}), dcc.Slider(id='noise-slider', min=0.0, max=0.6, step=0.05, value=0.15, marks={i/10: str(i/10) for i in range(7)}), html.Div(style={'height': '20px'}), html.Label("Number of Neighbors (k)", style={'fontWeight': 'bold', 'color': '#334155', 'display': 'block', 'marginBottom': '5px'}), dcc.Slider(id='k-slider', min=1, max=50, step=1, value=5, marks={1: '1', 5: '5', 15: '15', 30: '30', 50: '50'}), html.Div(style={'height': '20px'}), html.Label("Voting Weights", style={'fontWeight': 'bold', 'color': '#334155', 'display': 'block', 'marginBottom': '8px'}), dcc.RadioItems( id='weights-radio', options=[ {'label': ' Uniform (Equal Vote)', 'value': 'uniform'}, {'label': ' Distance (Inverse Proximity)', 'value': 'distance'} ], value='uniform', labelStyle={'display': 'block', 'marginBottom': '8px', 'color': '#475569'} ), html.Div(style={'height': '20px'}), html.Label("Distance Metric (Minkowski p-norm)", style={'fontWeight': 'bold', 'color': '#334155', 'display': 'block', 'marginBottom': '8px'}), dcc.RadioItems( id='p-norm-radio', options=[ {'label': ' p = 1 (Manhattan Distance)', 'value': 1}, {'label': ' p = 2 (Euclidean Distance)', 'value': 2} ], value=2, labelStyle={'display': 'block', 'marginBottom': '8px', 'color': '#475569'} ) ] ), # Main Dashboard Content Panel html.Div( style={'flex': '2.5', 'minWidth': '500px', 'display': 'flex', 'flexDirection': 'column', 'gap': '20px'}, children=[ # Performance Summary Metrics Row html.Div( style={'display': 'flex', 'gap': '20px'}, children=[ html.Div( style={ 'flex': '1', 'textAlign': 'center', 'padding': '20px', 'backgroundColor': '#ffffff', 'borderRadius': '12px', 'boxShadow': '0 4px 6px -1px rgba(0, 0, 0, 0.05)', 'borderLeft': '5px solid #e11d48' }, children=[ html.Div("Train Accuracy Score", style={'fontSize': '11pt', 'color': '#9f1239', 'fontWeight': 'bold'}), html.H2(id='train-acc-output', style={'margin': '5px 0 0 0', 'color': '#e11d48', 'border': 'none', 'padding': '0', 'fontWeight': '800'}) ] ), html.Div( style={ 'flex': '1', 'textAlign': 'center', 'padding': '20px', 'backgroundColor': '#ffffff', 'borderRadius': '12px', 'boxShadow': '0 4px 6px -1px rgba(0, 0, 0, 0.05)', 'borderLeft': '5px solid #16a34a' }, children=[ html.Div("Test Accuracy Score", style={'fontSize': '11pt', 'color': '#166534', 'fontWeight': 'bold'}), html.H2(id='test-acc-output', style={'margin': '5px 0 0 0', 'color': '#16a34a', 'border': 'none', 'padding': '0', 'fontWeight': '800'}) ] ) ] ), # Graphs Loading Block dcc.Loading( id="loading-plots", type="circle", children=[ # Main decision map display panel html.Div( style={ 'backgroundColor': '#ffffff', 'padding': '20px', 'borderRadius': '16px', 'boxShadow': '0 10px 15px -3px rgba(0, 0, 0, 0.05)' }, children=[ dcc.Graph(id='knn-boundary-plot', style={'height': '500px'}), ] ), html.Div(style={'height': '20px'}), # Evaluation Subplots: Confusion Matrix & ROC Curve Side-by-Side html.Div( style={'display': 'flex', 'flexWrap': 'wrap', 'gap': '20px'}, children=[ html.Div( style={ 'flex': '1', 'minWidth': '300px', 'backgroundColor': '#ffffff', 'padding': '15px', 'borderRadius': '16px', 'boxShadow': '0 10px 15px -3px rgba(0, 0, 0, 0.05)' }, children=[dcc.Graph(id='knn-confusion-matrix')] ), html.Div( style={ 'flex': '1', 'minWidth': '300px', 'backgroundColor': '#ffffff', 'padding': '15px', 'borderRadius': '16px', 'boxShadow': '0 10px 15px -3px rgba(0, 0, 0, 0.05)' }, children=[dcc.Graph(id='knn-roc-curve')] ) ] ) ] ) ] ) ] ) ] ) ] ) @app.callback( [ Output('knn-boundary-plot', 'figure'), Output('knn-confusion-matrix', 'figure'), Output('knn-roc-curve', 'figure'), Output('train-acc-output', 'children'), Output('test-acc-output', 'children') ], [ Input('dataset-selector', 'value'), Input('noise-slider', 'value'), Input('k-slider', 'value'), Input('weights-radio', 'value'), Input('p-norm-radio', 'value') ] ) def update_dashboard(dataset_name, noise, n_neighbors, weights, p_value): fig_boundary, fig_cm, fig_roc, train_score, test_score = serve_prediction_plot( dataset_name=dataset_name, noise=noise, n_neighbors=n_neighbors, weights=weights, metric='minkowski', p_value=p_value ) train_pct = f"{train_score * 100:.2f}%" test_pct = f"{test_score * 100:.2f}%" return fig_boundary, fig_cm, fig_roc, train_pct, test_pct if __name__ == '__main__': app.run(debug=True)