diff --git a/recopytex/dashboard/exam.py b/recopytex/dashboard/exam.py index e0660ec..866eeee 100644 --- a/recopytex/dashboard/exam.py +++ b/recopytex/dashboard/exam.py @@ -11,6 +11,7 @@ from pathlib import Path from datetime import datetime import pandas as pd import numpy as np +import dash_bootstrap_components as dbc from .. import flat_df_students, pp_q_scores @@ -25,66 +26,87 @@ COLORS = { 3: "#68D42F", } -external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"] -app = dash.Dash(__name__, external_stylesheets=external_stylesheets) +app = dash.Dash(external_stylesheets=[dbc.themes.SIMPLEX]) +# external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"] +# app = dash.Dash(__name__, external_stylesheets=external_stylesheets) # app = dash.Dash(__name__) app.layout = html.Div( children=[ - html.H1("Analyse des notes"), - html.Div( - [ - "Classe: ", - dcc.Dropdown( - id="tribe", - options=[ - {"label": t["name"], "value": t["name"]} - for t in config["tribes"] - ], - value=config["tribes"][0]["name"], - ), - "Evaluation: ", - dcc.Dropdown(id="csv"), + dbc.NavbarSimple( + children=[ + dbc.Alert("Dernière sauvegarde", id="lastsave", color="success"), ], - style={"columnCount": 2}, + brand="Analyse des notes", + brand_href="#", + color="success", + dark=True, ), - html.Div( + html.Br(), + dbc.Row( [ - dash_table.DataTable( - id="final_score_table", - columns=[ - {"id": "Élève", "name": "Élève"}, - {"id": "Note", "name": "Note"}, - {"id": "Barème", "name": "Bareme"}, - ], - data=[], - style_data_conditional=[ - { - "if": {"row_index": "odd"}, - "backgroundColor": "rgb(248, 248, 248)", - } - ], - style_header={ - "backgroundColor": "rgb(230, 230, 230)", - "fontWeight": "bold", - }, - style_data={ - "width": "100px", - "maxWidth": "100px", - "minWidth": "100px", - }, + dbc.Col( + [ + "Classe: ", + dbc.Select( + id="tribe", + options=[ + {"label": t["name"], "value": t["name"]} + for t in config["tribes"] + ], + value=config["tribes"][0]["name"], + ), + ] ), - html.Div( + dbc.Col( + [ + "Evaluation: ", + dbc.Select(id="csv"), + ] + ), + ], + ), + html.Br(), + dbc.Row( + [ + dbc.Col( + dash_table.DataTable( + id="final_score_table", + columns=[ + {"id": "Élève", "name": "Élève"}, + {"id": "Note", "name": "Note"}, + {"id": "Barème", "name": "Bareme"}, + ], + data=[], + style_data_conditional=[ + { + "if": {"row_index": "odd"}, + "backgroundColor": "rgb(248, 248, 248)", + } + ], + style_header={ + "backgroundColor": "rgb(230, 230, 230)", + "fontWeight": "bold", + }, + style_data={ + "width": "100px", + "maxWidth": "100px", + "minWidth": "100px", + }, + ) + ), + dbc.Col( [ dash_table.DataTable( id="final_score_describe", ), - dcc.Graph(id="fig_assessment_hist"), - dcc.Graph(id="fig_competences"), + dcc.Graph( + id="fig_assessment_hist", + ), + # dcc.Graph(id="fig_competences"), ] ), ], - style={"columnCount": 2}, ), html.Br(), html.Div( @@ -98,10 +120,10 @@ app.layout = html.Div( }, style_data_conditional=[], editable=True, - ) + ), + dbc.Button("Ajouter un élément", id="btn_add_element"), ] ), - html.P(id="lastsave"), dcc.Store(id="final_score"), ] ) @@ -201,74 +223,78 @@ def update_final_scores_hist(data): hovertemplate="", marker_color="#4E89DE", ) - # fig = go.Figure( - # data=go.Histogram( - # x=assessment_scores["Note"], - # xbins={"start": 0, "end": assessment_scores["Bareme"].max(), "size": 0.25}, - # ), - # ) + fig.update_layout( + height=300, + margin=dict(l=5, r=5, b=5, t=5), + ) return [fig] +# @app.callback( +# [ +# dash.dependencies.Output("fig_competences", "figure"), +# ], +# [dash.dependencies.Input("scores_table", "data")], +# ) +# def update_competence_fig(data): +# scores = pd.DataFrame.from_records(data) +# scores = flat_df_students(scores).dropna(subset=["Score"]) +# scores = pp_q_scores(scores) +# pt = pd.pivot_table( +# scores, +# index=["Exercice", "Question", "Commentaire"], +# columns="Score", +# aggfunc="size", +# fill_value=0, +# ) +# for i in {i for i in pt.index.get_level_values(0)}: +# pt.loc[(str(i), "", ""), :] = "" +# pt.sort_index(inplace=True) +# index = ( +# pt.index.get_level_values(0) +# + ":" +# + pt.index.get_level_values(1) +# + " " +# + pt.index.get_level_values(2) +# ) +# +# fig = go.Figure() +# bars = [ +# {"score": -1, "name": "Pas de réponse", "color": COLORS["."]}, +# {"score": 0, "name": "Faut", "color": COLORS[0]}, +# {"score": 1, "name": "Peu juste", "color": COLORS[1]}, +# {"score": 2, "name": "Presque juste", "color": COLORS[2]}, +# {"score": 3, "name": "Juste", "color": COLORS[3]}, +# ] +# for b in bars: +# try: +# fig.add_bar( +# x=index, y=pt[b["score"]], name=b["name"], marker_color=b["color"] +# ) +# except KeyError: +# pass +# fig.update_layout(barmode="relative") +# return [fig] + + @app.callback( [ - dash.dependencies.Output("fig_competences", "figure"), + dash.dependencies.Output("lastsave", "children"), + dash.dependencies.Output("lastsave", "color"), ], - [dash.dependencies.Input("scores_table", "data")], -) -def update_competence_fig(data): - scores = pd.DataFrame.from_records(data) - scores = flat_df_students(scores).dropna(subset=["Score"]) - scores = pp_q_scores(scores) - pt = pd.pivot_table( - scores, - index=["Exercice", "Question", "Commentaire"], - columns="Score", - aggfunc="size", - fill_value=0, - ) - for i in {i for i in pt.index.get_level_values(0)}: - pt.loc[(str(i), "", ""), :] = "" - pt.sort_index(inplace=True) - index = ( - pt.index.get_level_values(0) - + ":" - + pt.index.get_level_values(1) - + " " - + pt.index.get_level_values(2) - ) - - fig = go.Figure() - bars = [ - {"score": -1, "name": "Pas de réponse", "color": COLORS["."]}, - {"score": 0, "name": "Faut", "color": COLORS[0]}, - {"score": 1, "name": "Peu juste", "color": COLORS[1]}, - {"score": 2, "name": "Presque juste", "color": COLORS[2]}, - {"score": 3, "name": "Juste", "color": COLORS[3]}, - ] - for b in bars: - try: - fig.add_bar( - x=index, y=pt[b["score"]], name=b["name"], marker_color=b["color"] - ) - except KeyError: - pass - fig.update_layout(barmode="relative") - return [fig] - - -@app.callback( - [dash.dependencies.Output("lastsave", "children")], [ dash.dependencies.Input("scores_table", "data"), dash.dependencies.State("csv", "value"), ], ) def save_scores(data, csv): - scores = pd.DataFrame.from_records(data) - print(f"save at {csv} ({datetime.today()})") - scores.to_csv(csv, index=False) - return [datetime.today()] + try: + scores = pd.DataFrame.from_records(data) + scores.to_csv(csv, index=False) + except: + return [f"Soucis pour sauvegarder à {datetime.today()} dans {csv}"], "warning" + else: + return [f"Dernière sauvegarde {datetime.today()} dans {csv}"], "success" def highlight_value(df): @@ -293,18 +319,23 @@ def highlight_value(df): dash.dependencies.Output("scores_table", "data"), dash.dependencies.Output("scores_table", "style_data_conditional"), ], - [dash.dependencies.Input("csv", "value")], + [ + dash.dependencies.Input("csv", "value"), + dash.dependencies.Input("btn_add_element", "n_clicks"), + dash.dependencies.State("scores_table", "data"), + ], ) -def update_scores_table(value): - if not value: - raise PreventUpdate - stack = pd.read_csv(value, encoding="UTF8") - # try: - # stack = stack.drop(columns=["Nom", "Trimestre", "Date", "Competence", "Domaine", "Est_nivele", "Bareme"]) - # except KeyError: - # stack = stack +def update_scores_table(csv, add_element, data): + ctx = dash.callback_context + if ctx.triggered[0]['prop_id'] == "csv.value": + stack = pd.read_csv(csv, encoding="UTF8") + elif ctx.triggered[0]['prop_id'] == "btn_add_element.n_clicks": + stack = pd.DataFrame.from_records(data) + infos = pd.DataFrame.from_records([{k: stack.iloc[-1][k] for k in NO_ST_COLUMNS.values()}]) + stack = stack.append(infos) return ( [{"id": c, "name": c} for c in stack.columns], stack.to_dict("records"), highlight_value(stack), ) +