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)