From 4bf8f4003e60953734d527602c1e2ef61fb96fc7 Mon Sep 17 00:00:00 2001 From: Bertrand Benjamin Date: Sun, 17 Jan 2021 22:04:52 +0100 Subject: [PATCH 1/3] Feat: remove bootstrap and replace it with css --- recopytex/dashboard/assets/style.css | 41 +++++ recopytex/dashboard/exam.py | 215 ++++++++++++++------------- recopytex/scripts/recopytex.py | 5 +- 3 files changed, 159 insertions(+), 102 deletions(-) create mode 100644 recopytex/dashboard/assets/style.css diff --git a/recopytex/dashboard/assets/style.css b/recopytex/dashboard/assets/style.css new file mode 100644 index 0000000..acb9d3f --- /dev/null +++ b/recopytex/dashboard/assets/style.css @@ -0,0 +1,41 @@ +body { + margin: 0px; + font-family: 'Source Sans Pro','Roboto','Open Sans','Liberation Sans','DejaVu Sans','Verdana','Helvetica','Arial',sans-serif; +} + +header { + margin: 0px 0px 20px 0px; + background-color: #333333; + color: #ffffff; + padding: 20px; +} + +header > h1 { + margin: 0px; +} + +main { + width: 95vw; + margin: auto; +} + +#select { + margin-bottom: 20px; +} + +#select > div { + width: 40vw; + margin: auto; +} + +#analysis { + display: flex; + flex-flow: row wrap; +} + +#analysis > * { + display: flex; + flex-flow: column; + width: 45vw; + margin: auto; +} diff --git a/recopytex/dashboard/exam.py b/recopytex/dashboard/exam.py index 5c3026c..30863e5 100644 --- a/recopytex/dashboard/exam.py +++ b/recopytex/dashboard/exam.py @@ -26,116 +26,133 @@ COLORS = { 3: "#68D42F", } -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 = dash.Dash(__name__) app.layout = html.Div( children=[ - dbc.NavbarSimple( + html.Header( children=[ - dbc.Alert("Dernière sauvegarde", id="lastsave", color="success"), + html.H1("Analyse des notes"), + html.P("Dernière sauvegarde", id="lastsave"), ], - brand="Analyse des notes", - brand_href="#", - color="success", - dark=True, ), - html.Br(), - dbc.Row( + html.Main( [ - dbc.Col( + html.Section( [ - "Classe: ", - dbc.Select( - id="tribe", - options=[ - {"label": t["name"], "value": t["name"]} - for t in config["tribes"] + html.Div( + [ + "Classe: ", + dcc.Dropdown( + id="tribe", + options=[ + {"label": t["name"], "value": t["name"]} + for t in config["tribes"] + ], + value=config["tribes"][0]["name"], + ), ], - value=config["tribes"][0]["name"], + style={ + "display": "flex", + "flex-flow": "column", + }, ), - ] + html.Div( + [ + "Evaluation: ", + dcc.Dropdown(id="csv"), + ], + style={ + "display": "flex", + "flex-flow": "column", + }, + ), + ], + id="select", + style={ + "display": "flex", + "flex-flow": "row wrap", + }, ), - dbc.Col( + html.Div( [ - "Evaluation: ", - dbc.Select(id="csv"), - ] + html.Div( + dash_table.DataTable( + id="final_score_table", + columns=[ + {"id": "Élève", "name": "Élève"}, + {"id": "Note", "name": "Note"}, + {"id": "Barème", "name": "Barème"}, + ], + data=[], + style_data_conditional=[ + { + "if": {"row_index": "odd"}, + "backgroundColor": "rgb(248, 248, 248)", + } + ], + style_data={ + "width": "100px", + "maxWidth": "100px", + "minWidth": "100px", + }, + ), + id="final_score_table_container", + ), + html.Div( + [ + dash_table.DataTable( + id="final_score_describe", + columns=[ + {"id": "count", "name": "count"}, + {"id": "mean", "name": "mean"}, + {"id": "std", "name": "std"}, + {"id": "min", "name": "min"}, + {"id": "25%", "name": "25%"}, + {"id": "50%", "name": "50%"}, + {"id": "75%", "name": "75%"}, + {"id": "max", "name": "max"}, + ], + ), + dcc.Graph( + id="fig_assessment_hist", + ), + dcc.Graph(id="fig_competences"), + ], + id="desc_plots", + ), + ], + id="analysis", ), - ], - ), - 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": "Barème"}, - ], - 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( + html.Div( [ dash_table.DataTable( - id="final_score_describe", - columns=[{"id": "count", "name": "count"}, - {"id": "mean", "name": "mean"}, - {"id": "std", "name": "std"}, - {"id": "min", "name": "min"}, - {"id": "25%", "name": "25%"}, - {"id": "50%", "name": "50%"}, - {"id": "75%", "name": "75%"}, - {"id": "max", "name": "max"}, - ] - + id="scores_table", + columns=[ + {"id": c, "name": c} for c in NO_ST_COLUMNS.values() + ], + style_cell={ + "whiteSpace": "normal", + "height": "auto", + }, + style_data_conditional=[], + editable=True, ), - dcc.Graph( - id="fig_assessment_hist", - ), - dcc.Graph(id="fig_competences"), - ] + html.Button("Ajouter un élément", id="btn_add_element"), + ], + id="big_table", ), + dcc.Store(id="final_score"), ], + className="content", + style={ + "width": "95vw", + "margin": "auto", + }, ), - html.Br(), - html.Div( - [ - dash_table.DataTable( - id="scores_table", - columns=[{"id": c, "name": c} for c in NO_ST_COLUMNS.values()], - style_cell={ - "whiteSpace": "normal", - "height": "auto", - }, - style_data_conditional=[], - editable=True, - ), - dbc.Button("Ajouter un élément", id="btn_add_element"), - ] - ), - dcc.Store(id="final_score"), - ] + ], ) @@ -178,9 +195,7 @@ def update_final_scores(data): return [{}] scores = pp_q_scores(scores) - assessment_scores = scores.groupby(["Eleve"]).agg( - {"Note": "sum", "Bareme": "sum"} - ) + assessment_scores = scores.groupby(["Eleve"]).agg({"Note": "sum", "Bareme": "sum"}) return [assessment_scores.reset_index().to_dict("records")] @@ -202,7 +217,7 @@ def update_final_scores_table(data): [dash.dependencies.Input("final_score", "data")], ) def update_final_scores_descr(data): - scores = pd.DataFrame.from_records(data) + scores = pd.DataFrame.from_records(data) if scores.empty: return [[{}]] desc = scores["Note"].describe().T @@ -311,7 +326,6 @@ def update_competence_fig(data): @app.callback( [ dash.dependencies.Output("lastsave", "children"), - dash.dependencies.Output("lastsave", "color"), ], [ dash.dependencies.Input("scores_table", "data"), @@ -323,9 +337,9 @@ def save_scores(data, csv): scores = pd.DataFrame.from_records(data) scores.to_csv(csv, index=False) except: - return [f"Soucis pour sauvegarder à {datetime.today()} dans {csv}"], "warning" + return [f"Soucis pour sauvegarder à {datetime.today()} dans {csv}"] else: - return [f"Dernière sauvegarde {datetime.today()} dans {csv}"], "success" + return [f"Dernière sauvegarde {datetime.today()} dans {csv}"] def highlight_value(df): @@ -358,15 +372,16 @@ def highlight_value(df): ) def update_scores_table(csv, add_element, data): ctx = dash.callback_context - if ctx.triggered[0]['prop_id'] == "csv.value": + 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": + 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()}]) + 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), ) - diff --git a/recopytex/scripts/recopytex.py b/recopytex/scripts/recopytex.py index 63f41e6..165f7dc 100644 --- a/recopytex/scripts/recopytex.py +++ b/recopytex/scripts/recopytex.py @@ -87,8 +87,9 @@ def new_exam(): @cli.command() -def exam_analysis(): - exam_app.run_server(debug=True) +@click.option("--debug", default=0, help="Debug mode for dash") +def exam_analysis(debug): + exam_app.run_server(debug=bool(debug)) @cli.command() From 4f14e3518ce8b3c146fa9b6e51defe9eea5611a5 Mon Sep 17 00:00:00 2001 From: Bertrand Benjamin Date: Sun, 17 Jan 2021 22:21:58 +0100 Subject: [PATCH 2/3] Fix: concatenate index for competence plot --- recopytex/dashboard/exam.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/recopytex/dashboard/exam.py b/recopytex/dashboard/exam.py index 30863e5..ff001e2 100644 --- a/recopytex/dashboard/exam.py +++ b/recopytex/dashboard/exam.py @@ -293,11 +293,11 @@ def update_competence_fig(data): pt.loc[(str(i), "", ""), :] = "" pt.sort_index(inplace=True) index = ( - pt.index.get_level_values(0) + pt.index.get_level_values(0).map(str) + ":" - + pt.index.get_level_values(1) + + pt.index.get_level_values(1).map(str) + " " - + pt.index.get_level_values(2) + + pt.index.get_level_values(2).map(str) ) fig = go.Figure() From 7955b989b4e62f29d0cd8eaec66e72114bce30ff Mon Sep 17 00:00:00 2001 From: Bertrand Benjamin Date: Sun, 17 Jan 2021 22:26:16 +0100 Subject: [PATCH 3/3] Fix: missing category (0) in final_score plot --- recopytex/dashboard/exam.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/recopytex/dashboard/exam.py b/recopytex/dashboard/exam.py index ff001e2..3f69152 100644 --- a/recopytex/dashboard/exam.py +++ b/recopytex/dashboard/exam.py @@ -237,7 +237,9 @@ def update_final_scores_hist(data): return [{}] ranges = np.linspace( - 0, assessment_scores.Bareme.max(), int(assessment_scores.Bareme.max() * 2 + 1) + -0.5, + assessment_scores.Bareme.max(), + int(assessment_scores.Bareme.max() * 2 + 2), ) bins = pd.cut(assessment_scores["Note"], ranges) assessment_scores["Bin"] = bins