diff --git a/app/app.py b/app/app.py index f3f0c5f..7e04606 100644 --- a/app/app.py +++ b/app/app.py @@ -1,13 +1,9 @@ """Sets up the server for the Dash app.""" import dash # type: ignore -from dash import Dash, dcc, html # type: ignore +from dash import Dash, Input, Output, State, callback, dcc, html # type: ignore from . import log -################## -interval = 7000 -################## - app = Dash(__package__, use_pages=True, update_title=None) app.layout = html.Div( @@ -17,7 +13,8 @@ }, children=[ dash.page_container, - dcc.Interval(id="figure_interval", interval=interval), + dcc.Store(id="figure_interval", data=0), + dcc.Interval(id="sync_interval", interval=100), ], ) @@ -25,5 +22,35 @@ log.info("Gridlington Visualisation System is running...") +@callback( + [Output("figure_interval", "data")], + [Input("sync_interval", "n_intervals")], + [State("figure_interval", "data")], +) +def update_figure_interval( + n_intervals_sync: int, + n_intervals_figures: int, +) -> tuple[int]: + """Callback to synchronise figure_interval with data_interval. + + This pulls in N_INTERVALS_DATA (number of times the data has updated) from + the data module and increments figure_interval accordingly. + + Args: + n_intervals_sync (int): Number of times this callback has run + n_intervals_figures (int): Number of times the figures have updated + + Returns: + N_INTERVALS_DATA (int): Number of times the data has updated + """ + from .data import N_INTERVALS_DATA + + return ( + dash.no_update + if n_intervals_figures == N_INTERVALS_DATA + else (N_INTERVALS_DATA,) + ) + + if __name__ == "__main__": app.run_server(debug=True) diff --git a/app/data.py b/app/data.py index 67d0612..8969001 100644 --- a/app/data.py +++ b/app/data.py @@ -6,9 +6,7 @@ from . import LIVE_MODEL, log from .datahub_api import get_opal_data, get_wesim_data # , get_dsr_data -################## -interval = 7000 -################## +N_INTERVALS_DATA = 0 DF_OPAL = pd.DataFrame({"Col": [0]}) @@ -24,37 +22,43 @@ else: WESIM = {"df": pd.DataFrame({"Col": [0]})} -data_interval = dcc.Interval(id="data_interval", interval=interval) -empty_output = dcc.Store(id="empty", data=[]) +data_interval = dcc.Interval(id="data_interval") @callback( - [Output("empty", "data")], + [Output("data_interval", "disabled")], [Input("data_interval", "n_intervals")], ) -def update_data(n_intervals: int) -> tuple[list[None],]: - """Function to update the data. +def update_data(n_intervals: int) -> tuple[bool,]: + """Function to update OPAL data. Args: - n_intervals (int): The number of times this page has updated. - indexes by 1 every 7 seconds. + n_intervals (int): The number of times the data has updated. + indexes by 1 every interval. Returns: - Opal data dictionary + tuple[bool,]: Boolean that specifies whether the iterator should + terminate """ - global DF_OPAL + global DF_OPAL, N_INTERVALS_DATA if n_intervals is None: raise PreventUpdate + data_ended = False if LIVE_MODEL: - log.debug("Updating plots from live model") + log.debug("Updating data from live model") data_opal = get_opal_data() DF_OPAL = pd.DataFrame(**data_opal) # type: ignore[call-overload] else: from .pre_set_data import OPAL_DATA - log.debug("Updating plots with pre-set data") + log.debug("Updating pre-set data") DF_OPAL = OPAL_DATA.loc[:n_intervals] - return ([],) + if n_intervals == len(OPAL_DATA): + log.debug("Reached end of pre-set data") + data_ended = True + + N_INTERVALS_DATA = n_intervals + return (data_ended,) diff --git a/app/pages/agent.py b/app/pages/agent.py index 99fb8e1..b8e36f2 100644 --- a/app/pages/agent.py +++ b/app/pages/agent.py @@ -14,6 +14,7 @@ import plotly.graph_objects as go # type: ignore from dash import Input, Output, callback, dcc # type: ignore +from .. import log from ..figures import ( generate_agent_activity_breakdown_fig, generate_dsr_commands_fig, @@ -90,7 +91,7 @@ Output("ev_charging_breakdown_fig", "figure"), Output("dsr_commands_fig", "figure"), ], - [Input("figure_interval", "n_intervals")], + [Input("figure_interval", "data")], ) def update_figures( n_intervals: int, @@ -99,7 +100,7 @@ def update_figures( Args: n_intervals (int): The number of times this page has updated. - indexes by 1 every 7 seconds. + indexes by 1 every interval. Returns: tuple[go.Figure, go.Figure, go.Figure, go.Figure, px.line]: @@ -113,6 +114,7 @@ def update_figures( agent_activity_breakdown_fig = generate_agent_activity_breakdown_fig(DF_OPAL) ev_charging_breakdown_fig = generate_ev_charging_breakdown_fig(DF_OPAL) dsr_commands_fig = generate_dsr_commands_fig(DF_OPAL) + log.debug("Updating figures on Agent page") return ( map_fig, sld_fig, diff --git a/app/pages/control.py b/app/pages/control.py index 860228f..981bc1e 100644 --- a/app/pages/control.py +++ b/app/pages/control.py @@ -4,9 +4,9 @@ from dash import Input, Output, State, callback, ctx, dcc, html # type: ignore from dash_iconify import DashIconify # type: ignore +from .. import LIVE_MODEL, log from .. import core_api as core -from .. import log -from ..data import data_interval, empty_output +from ..data import data_interval dash.register_page(__name__) @@ -171,11 +171,33 @@ def get_button(func: str, icon: str) -> html.Button: "display": "flex", "justify-content": "space-around", "padding": "10px", - "width": "66%", - "margin": "auto", }, children=[ get_button("update", "mdi:tick"), + html.Div( + children=[ + html.Div( + dcc.Slider( + id="update-interval-slider", + min=2, + max=10, + step=1, + value=7, + ), + style={"width": "100%"}, + ), + html.Label( + "Update Interval (s)", + style={"text-align": "center"}, + ), + ], + style={ + "width": "40%", + "flex-direction": "column", + "justify-content": "center", + "display": "none" if LIVE_MODEL else "flex", + }, + ), get_button("default", "iconoir:undo"), ], ), @@ -194,7 +216,6 @@ def get_button(func: str, icon: str) -> html.Button: ], ), data_interval, - empty_output, ], ) @@ -305,3 +326,12 @@ def default_button_click(n_clicks: int | None) -> list[str]: get_default("PC02-Left"), get_default("PC02-Right"), ] + + +@callback( + [Output("data_interval", "interval")], [Input("update-interval-slider", "value")] +) +def update_data_interval(value: int) -> tuple[int]: + """Callback to update the data interval.""" + log.debug(f"Update interval set to {value} seconds.") + return (value * 1000,) diff --git a/app/pages/market.py b/app/pages/market.py index c9bd706..b1f0a0f 100644 --- a/app/pages/market.py +++ b/app/pages/market.py @@ -14,6 +14,7 @@ from dash import Input, Output, callback, dcc # type: ignore from plotly import graph_objects as go # type: ignore +from .. import log from ..figures import ( generate_dsr_commands_fig, generate_dsr_fig, @@ -79,7 +80,7 @@ Output("graph-dsr", "figure"), Output("graph-dsr-commands", "figure"), ], - [Input("figure_interval", "n_intervals")], + [Input("figure_interval", "data")], ) def update_figures( n_intervals: int, @@ -88,7 +89,7 @@ def update_figures( Args: n_intervals (int): The number of times this page has updated. - indexes by 1 every 7 seconds. + indexes by 1 every interval. Returns: tuple[px.line, go.Figure, go.Figure, px.line]: @@ -100,6 +101,7 @@ def update_figures( intraday_market_bids_fig = generate_intraday_market_bids_fig(DF_OPAL) dsr_fig = generate_dsr_fig(df) # TODO: replace with df_dsr when available dsr_commands_fig = generate_dsr_commands_fig(DF_OPAL) + log.debug("Updating figures on Market page") return ( energy_deficit_fig, intraday_market_bids_fig, diff --git a/app/pages/marketsreserve.py b/app/pages/marketsreserve.py index 37489cf..e719fc8 100644 --- a/app/pages/marketsreserve.py +++ b/app/pages/marketsreserve.py @@ -13,6 +13,7 @@ from dash import Input, Output, callback, dcc # type: ignore from plotly import graph_objects as go # type: ignore +from .. import log from ..data import WESIM from ..figures import ( generate_balancing_market_fig, @@ -76,7 +77,7 @@ Output("balancing_market_fig", "figure"), Output("intraday_market_sys_fig", "figure"), ], - [Input("figure_interval", "n_intervals")], + [Input("figure_interval", "data")], ) def update_figures( n_intervals: int, @@ -85,7 +86,7 @@ def update_figures( Args: n_intervals (int): The number of times this page has updated. - indexes by 1 every 7 seconds. + indexes by 1 every interval. Returns: tuple[go.Figure, go.Figure]: The new figures. @@ -94,6 +95,7 @@ def update_figures( balancing_market_fig = generate_balancing_market_fig(DF_OPAL) intraday_market_sys_fig = generate_intraday_market_sys_fig(DF_OPAL) + log.debug("Updating figures of Markets and Reserve page") return ( balancing_market_fig, intraday_market_sys_fig, diff --git a/app/pages/supplydemand.py b/app/pages/supplydemand.py index 1fa129c..930897e 100644 --- a/app/pages/supplydemand.py +++ b/app/pages/supplydemand.py @@ -13,6 +13,7 @@ import plotly.express as px # type: ignore from dash import Input, Output, callback, dcc # type: ignore +from .. import log from ..figures import ( generate_gen_split_fig, generate_system_freq_fig, @@ -77,7 +78,7 @@ Output("graph-demand", "figure"), Output("graph-freq", "figure"), ], - [Input("figure_interval", "n_intervals")], + [Input("figure_interval", "data")], ) def update_figures( n_intervals: int, @@ -86,7 +87,7 @@ def update_figures( Args: n_intervals (int): The number of times this page has updated. - indexes by 1 every 7 seconds. + indexes by 1 every interval. Returns: tuple[px.pie, px.line, px.line, px.line]: The new figures. @@ -97,4 +98,5 @@ def update_figures( total_gen_fig = generate_total_gen_fig(DF_OPAL) total_dem_fig = generate_total_dem_fig(DF_OPAL) system_freq_fig = generate_system_freq_fig(DF_OPAL) + log.debug("Updating figures on Supply & Demand page") return gen_split_fig, total_gen_fig, total_dem_fig, system_freq_fig