From 171ee7a37385d8fb08123f06b1ec449c1b015fa8 Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Wed, 7 Feb 2024 13:36:49 +0000 Subject: [PATCH 1/2] Improved plots and logic --- .../finished_apps/daily_validation.py | 145 +++++++++--------- validated/plots.py | 56 +++++++ validated/tables.py | 2 +- 3 files changed, 127 insertions(+), 76 deletions(-) create mode 100644 validated/plots.py diff --git a/validated/dash_apps/finished_apps/daily_validation.py b/validated/dash_apps/finished_apps/daily_validation.py index 45fffa8e..9baa6c6c 100644 --- a/validated/dash_apps/finished_apps/daily_validation.py +++ b/validated/dash_apps/finished_apps/daily_validation.py @@ -17,6 +17,7 @@ save_detail_to_validated, save_to_validated, ) +from validated.plots import create_validation_plot from validated.tables import create_columns_daily, create_columns_detail from variable.models import Variable @@ -33,6 +34,7 @@ MINIMUM: Decimal = Decimal(-5) MAXIMUM: Decimal = Decimal(28) SELECTED_DAY: datetime = datetime.strptime("2023-03-14", "%Y-%m-%d") +PLOT_TYPE = "average" # Daily data DATA_DAILY = daily_validation( @@ -171,56 +173,18 @@ # Plot -def create_validation_plot(data: dict) -> go.Figure: - """Creates plot for Validation app +plot = create_validation_plot(data=DATA_DAILY, plot_type=PLOT_TYPE) - Args: - data (dict): Daily data series - - Returns: - go.Figure: Plot - """ - fig = go.Figure() - - datasets = [ - {"key": "measurement", "name": "Measurement", "color": "black"}, - {"key": "selected", "name": "Selected", "color": "#636EFA"}, - {"key": "validated", "name": "Validated", "color": "#00CC96"}, - ] - - for dataset in datasets: - fig.add_trace( - go.Scatter( - x=data[dataset["key"]]["time"], - y=data[dataset["key"]]["average"], - name=dataset["name"], - line=dict(color=dataset["color"]), - mode="markers", - marker_size=3, - ) - ) - - fig.update_yaxes(title_text=f"{str(VARIABLE)} (Average)") - fig.update_layout( - legend=dict( - x=1, - y=1, - xanchor="auto", - yanchor="auto", - ), - autosize=True, - margin=dict( - l=50, - r=0, - b=0, - t=20, - ), - ) - - return fig - - -plot = create_validation_plot(data=DATA_DAILY["series"]) +# Plot radio +plot_radio = dcc.RadioItems( + id="plot_radio", + options=[ + {"value": c, "label": c.capitalize()} for c in DATA_DAILY["value_columns"] + ], + value=PLOT_TYPE, + inline=True, + style={"font-family": DEFAULT_FONT, "font-size": "14px"}, +) # Layout app.layout = html.Div( @@ -262,9 +226,12 @@ def create_validation_plot(data: dict) -> go.Figure: style={ "font-family": DEFAULT_FONT, "font-size": "14px", - "min-height": "14px", + "min-height": "20px", + "padding-top": "5px", + "padding-bottom": "10px", }, ), + plot_radio, dcc.Graph(id="plot", figure=plot, style={"width": "100%"}), ] ) @@ -291,6 +258,7 @@ def create_validation_plot(data: dict) -> go.Figure: Input("detail-reset-button", "n_clicks"), Input("detail-add-button", "n_clicks"), Input("input-daily-id", "value"), + Input("plot_radio", "value"), ], [ State("table_daily", "selectedRows"), @@ -307,6 +275,7 @@ def callbacks( detail_reset_clicks: int, detail_add_clicks: int, daily_id: int, + plot_radio_value: str, in_daily_selected_rows: list[dict], in_daily_row_data: list[dict], in_detail_selected_rows: list[dict], @@ -333,6 +302,7 @@ def callbacks( detail_reset_clicks (int): Number of times detail-reset-button was clicked detail_add_clicks (int): Number of times detail-add-button was clicked daily_id (int): ID of selected day + plot_radio_value (str): Value of plot radio button in_daily_selected_rows (list[dict]): Selected rows in table_daily in_detail_selected_rows (list[dict]): Selected rows in table_detail in_detail_row_data (list[dict]): Full row data for table_detail @@ -341,7 +311,7 @@ def callbacks( tuple[str, go.Figure, list[dict], list[dict], dict, dict, list[dict], list[dict], bool, str, str]: Callback outputs """ - global DATA_DAILY, DATA_DETAIL, SELECTED_DAY + global DATA_DAILY, DATA_DETAIL, SELECTED_DAY, PLOT_TYPE ctx = dash.callback_context button_id = ctx.triggered[0]["prop_id"].split(".")[0] @@ -358,10 +328,12 @@ def callbacks( out_tab_detail_label = dash.no_update out_tabs_value = dash.no_update - daily_refresh_required = False - detail_refresh_required = False - daily_reset_selection = False - detail_reset_selection = False + daily_data_refresh_required = False + detail_data_refresh_required = False + daily_table_refresh_required = False + detail_table_refresh_required = False + daily_table_reset_selection = False + detail_table_reset_selection = False plot_refresh_required = False # Button: Daily save @@ -380,8 +352,9 @@ def callbacks( maximum=MAXIMUM, ) out_status = f"{len(in_daily_selected_rows)} days saved to Validated" + daily_data_refresh_required = True + daily_table_refresh_required = True plot_refresh_required = True - daily_refresh_required = True # Button: Daily reset elif button_id == "daily-reset-button": @@ -392,9 +365,10 @@ def callbacks( end_date=END_DATE, ) out_status = "Validation reset" + daily_data_refresh_required = True + daily_table_refresh_required = True + daily_table_reset_selection = True plot_refresh_required = True - daily_refresh_required = True - daily_reset_selection = True # Button: Detail save elif button_id == "detail-save-button": @@ -407,8 +381,11 @@ def callbacks( station=STATION, ) out_status = f"{len(in_detail_selected_rows)} entries saved to Validated" + daily_data_refresh_required = True + daily_table_refresh_required = True + detail_data_refresh_required = True + detail_table_refresh_required = True plot_refresh_required = True - detail_refresh_required = True # Button: Detail reset elif button_id == "detail-reset-button": @@ -418,9 +395,12 @@ def callbacks( station=STATION, ) out_status = "Validation reset" + daily_data_refresh_required = True + daily_table_refresh_required = True + detail_data_refresh_required = True + detail_table_refresh_required = True + detail_table_reset_selection = True plot_refresh_required = True - detail_refresh_required = True - detail_reset_selection = True # Button: Detail new row elif button_id == "detail-add-button": @@ -437,14 +417,14 @@ def callbacks( out_detail_row_transaction = {"add": [new_row]} out_detail_scroll = {"data": new_row} - # Input: Daily date + # Input: Daily ID elif button_id == "input-daily-id": SELECTED_DAY = next( (d["date"] for d in DATA_DAILY["data"] if d["id"] == daily_id), dash.no_update, ) if SELECTED_DAY != dash.no_update: - detail_refresh_required = True + detail_table_refresh_required = True out_tab_detail_disabled = False out_tab_detail_label = ( f"Detail of Selected Day ({SELECTED_DAY.strftime('%Y-%m-%d')})" @@ -454,8 +434,13 @@ def callbacks( else: out_status = "Invalid ID" - # Refresh plot - if plot_refresh_required: + # Plot radio + elif button_id == "plot_radio": + PLOT_TYPE = plot_radio_value + plot_refresh_required = True + + # Reload daily data + if daily_data_refresh_required: DATA_DAILY = daily_validation( station=STATION, variable=VARIABLE, @@ -464,16 +449,9 @@ def callbacks( minimum=MINIMUM, maximum=MAXIMUM, ) - out_plot = create_validation_plot(DATA_DAILY["series"]) - # Refresh daily table - if daily_refresh_required: - out_daily_row_data = DATA_DAILY["data"] - if daily_reset_selection: - out_daily_selected_rows = out_daily_row_data - - # Refresh detail table - if detail_refresh_required: + # Reload detail data + if detail_data_refresh_required: DATA_DETAIL = detail_list( station=STATION, variable=VARIABLE, @@ -481,8 +459,25 @@ def callbacks( minimum=MINIMUM, maximum=MAXIMUM, ) + + # Refresh plot + if plot_refresh_required: + out_plot = create_validation_plot(data=DATA_DAILY, plot_type=PLOT_TYPE) + + # Refresh daily table + if daily_table_refresh_required: + out_daily_row_data = DATA_DAILY["data"] + + # Reset daily table selection + if daily_table_reset_selection: + out_daily_selected_rows = out_daily_row_data + + # Refresh detail table + if detail_table_refresh_required: out_detail_row_data = DATA_DETAIL["series"] - if detail_reset_selection: + + # Reset detail table selection + if detail_table_reset_selection: out_detail_selected_rows = out_detail_row_data return ( diff --git a/validated/plots.py b/validated/plots.py new file mode 100644 index 00000000..bc4698ab --- /dev/null +++ b/validated/plots.py @@ -0,0 +1,56 @@ +import plotly.graph_objects as go + + +def create_validation_plot(data: dict, plot_type: str) -> go.Figure: + """Creates plot for Validation app + + Args: + data (dict): Daily data + plot_type (str): Type of plot + + Returns: + go.Figure: Plot + """ + variable: str = data["variable"]["name"] + data_series: dict = data["series"] + is_cumulative: bool = data["variable"]["is_cumulative"] + mode = "lines" if is_cumulative else "markers" + + fig = go.Figure() + + datasets = [ + {"key": "measurement", "name": "Measurement", "color": "black"}, + {"key": "selected", "name": "Selected", "color": "#636EFA"}, + {"key": "validated", "name": "Validated", "color": "#00CC96"}, + ] + + for dataset in datasets: + fig.add_trace( + go.Scatter( + x=data_series[dataset["key"]]["time"], + y=data_series[dataset["key"]][plot_type], + name=dataset["name"], + line=dict(color=dataset["color"]), + mode=mode, + marker_size=3, + ) + ) + + fig.update_yaxes(title_text=f"{variable} ({plot_type.capitalize()})") + fig.update_layout( + legend=dict( + x=1, + y=1, + xanchor="auto", + yanchor="auto", + ), + autosize=True, + margin=dict( + l=50, + r=0, + b=0, + t=20, + ), + ) + + return fig diff --git a/validated/tables.py b/validated/tables.py index 9e464a1c..241eb53e 100644 --- a/validated/tables.py +++ b/validated/tables.py @@ -106,7 +106,7 @@ def create_columns_detail(value_columns: list) -> list: columns += [ { "field": c, - "headerName": c[0].upper() + c[1:], + "headerName": c.capitalize(), "filter": "agNumberColumnFilter", "editable": True, **styles[c], From 3c9350ea4448afdce2c7126cbdf50fe871de4287 Mon Sep 17 00:00:00 2001 From: Tom Bland Date: Wed, 7 Feb 2024 14:55:34 +0000 Subject: [PATCH 2/2] Bug fix --- validated/dash_apps/finished_apps/daily_validation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/validated/dash_apps/finished_apps/daily_validation.py b/validated/dash_apps/finished_apps/daily_validation.py index 9baa6c6c..a28b3a27 100644 --- a/validated/dash_apps/finished_apps/daily_validation.py +++ b/validated/dash_apps/finished_apps/daily_validation.py @@ -424,6 +424,7 @@ def callbacks( dash.no_update, ) if SELECTED_DAY != dash.no_update: + detail_data_refresh_required = True detail_table_refresh_required = True out_tab_detail_disabled = False out_tab_detail_label = (