From 56195084138accd5201ed3b555556f0666dfef7b Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Thu, 15 Aug 2024 13:31:15 +0200 Subject: [PATCH 1/3] Add multiple_chart method to GenericAsset class Signed-off-by: Mohamed Belhsan Hmida --- flexmeasures/data/models/generic_assets.py | 83 ++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/flexmeasures/data/models/generic_assets.py b/flexmeasures/data/models/generic_assets.py index 3ba9330d4b..09bd956840 100644 --- a/flexmeasures/data/models/generic_assets.py +++ b/flexmeasures/data/models/generic_assets.py @@ -498,6 +498,89 @@ def chart( return chart_specs + def multiple_chart( + self, + chart_type: str = "chart_for_multiple_sensors", + event_starts_after: datetime | None = None, + event_ends_before: datetime | None = None, + beliefs_after: datetime | None = None, + beliefs_before: datetime | None = None, + source: DataSource + | list[DataSource] + | int + | list[int] + | str + | list[str] + | None = None, + include_data: bool = False, + dataset_name: str | None = None, + resolution: str | timedelta | None = None, + **kwargs, + ) -> list[dict]: + """Create a vega-lite chart showing sensor data. + + :param chart_type: currently only "bar_chart" # todo: where can we properly list the available chart types? + :param event_starts_after: only return beliefs about events that start after this datetime (inclusive) + :param event_ends_before: only return beliefs about events that end before this datetime (inclusive) + :param beliefs_after: only return beliefs formed after this datetime (inclusive) + :param beliefs_before: only return beliefs formed before this datetime (inclusive) + :param source: search only beliefs by this source (pass the DataSource, or its name or id) or list of sources + :param include_data: if True, include data in the chart, or if False, exclude data + :param dataset_name: optionally name the dataset used in the chart (the default name is sensor_) + :param resolution: optionally set the resolution of data being displayed + :returns: JSON string defining vega-lite chart specs + """ + sensors = flatten_unique(self.sensors_to_show) + for sensor in sensors: + sensor.sensor_type = sensor.get_attribute("sensor_type", sensor.name) + + # Ensure all elements are lists as search_beliefs requires a list of sensors + nested_sensors = [ + [e] if not isinstance(e, list) else e for e in self.sensors_to_show + ] + + # Initialize an empty list to store chart_specs for each group of sensors + return_description = [] + + # Loop through each set of sensors to generate individual chart_specs + for sensors in nested_sensors: + # Set up chart specification + if dataset_name is None: + dataset_name = "asset_" + str(self.id) + if event_starts_after: + kwargs["event_starts_after"] = event_starts_after + if event_ends_before: + kwargs["event_ends_before"] = event_ends_before + chart_specs = chart_type_to_chart_specs( + chart_type, + sensors_to_show=[ + sensors + ], # Wrap sensors in a list to allow multiple sensors to be displayed in the same graph + dataset_name=dataset_name, + **kwargs, + ) + + if include_data: + # Get data + data = self.search_beliefs( + sensors=sensors, + as_json=True, + event_starts_after=event_starts_after, + event_ends_before=event_ends_before, + beliefs_after=beliefs_after, + beliefs_before=beliefs_before, + source=source, + resolution=resolution, + ) + + # Combine chart specs and data + chart_specs["datasets"] = { + dataset_name: json.loads(data), + } + return_description.append(chart_specs) + + return return_description + def search_beliefs( self, sensors: list["Sensor"] | None = None, # noqa F821 From 708f86c5650a74b1b76dfeb15a2ca396b61afaad Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Tue, 20 Aug 2024 22:47:18 +0200 Subject: [PATCH 2/3] Add internal utility method_gather_specs_and_data_for_chart(), rename multiple_chart to charts, and refactor chart and charts methods to use the new function Signed-off-by: Mohamed Belhsan Hmida --- flexmeasures/data/models/generic_assets.py | 169 ++++++++++++--------- 1 file changed, 99 insertions(+), 70 deletions(-) diff --git a/flexmeasures/data/models/generic_assets.py b/flexmeasures/data/models/generic_assets.py index 09bd956840..76b9bb1c77 100644 --- a/flexmeasures/data/models/generic_assets.py +++ b/flexmeasures/data/models/generic_assets.py @@ -464,41 +464,21 @@ def chart( for sensor in sensors: sensor.sensor_type = sensor.get_attribute("sensor_type", sensor.name) - # Set up chart specification - if dataset_name is None: - dataset_name = "asset_" + str(self.id) - if event_starts_after: - kwargs["event_starts_after"] = event_starts_after - if event_ends_before: - kwargs["event_ends_before"] = event_ends_before - chart_specs = chart_type_to_chart_specs( + return self._gather_specs_and_data_for_chart( + sensors, chart_type, - sensors_to_show=self.sensors_to_show, - dataset_name=dataset_name, + event_starts_after, + event_ends_before, + beliefs_after, + beliefs_before, + source, + include_data, + dataset_name, + resolution, **kwargs, ) - if include_data: - # Get data - data = self.search_beliefs( - sensors=sensors, - as_json=True, - event_starts_after=event_starts_after, - event_ends_before=event_ends_before, - beliefs_after=beliefs_after, - beliefs_before=beliefs_before, - source=source, - resolution=resolution, - ) - - # Combine chart specs and data - chart_specs["datasets"] = { - dataset_name: json.loads(data), - } - - return chart_specs - - def multiple_chart( + def charts( self, chart_type: str = "chart_for_multiple_sensors", event_starts_after: datetime | None = None, @@ -517,7 +497,7 @@ def multiple_chart( resolution: str | timedelta | None = None, **kwargs, ) -> list[dict]: - """Create a vega-lite chart showing sensor data. + """Create multiple vega-lite charts showing sensor data for nested sensor groups. :param chart_type: currently only "bar_chart" # todo: where can we properly list the available chart types? :param event_starts_after: only return beliefs about events that start after this datetime (inclusive) @@ -539,47 +519,23 @@ def multiple_chart( [e] if not isinstance(e, list) else e for e in self.sensors_to_show ] - # Initialize an empty list to store chart_specs for each group of sensors - return_description = [] - - # Loop through each set of sensors to generate individual chart_specs - for sensors in nested_sensors: - # Set up chart specification - if dataset_name is None: - dataset_name = "asset_" + str(self.id) - if event_starts_after: - kwargs["event_starts_after"] = event_starts_after - if event_ends_before: - kwargs["event_ends_before"] = event_ends_before - chart_specs = chart_type_to_chart_specs( + return [ + self._gather_specs_and_data_for_chart( + sensors, chart_type, - sensors_to_show=[ - sensors - ], # Wrap sensors in a list to allow multiple sensors to be displayed in the same graph - dataset_name=dataset_name, + event_starts_after, + event_ends_before, + beliefs_after, + beliefs_before, + source, + include_data, + dataset_name, + resolution, + wrap_sensors_in_list=True, **kwargs, ) - - if include_data: - # Get data - data = self.search_beliefs( - sensors=sensors, - as_json=True, - event_starts_after=event_starts_after, - event_ends_before=event_ends_before, - beliefs_after=beliefs_after, - beliefs_before=beliefs_before, - source=source, - resolution=resolution, - ) - - # Combine chart specs and data - chart_specs["datasets"] = { - dataset_name: json.loads(data), - } - return_description.append(chart_specs) - - return return_description + for sensors in nested_sensors + ] def search_beliefs( self, @@ -837,6 +793,79 @@ def set_inflexible_sensors(self, inflexible_sensor_ids: list[int]) -> None: ).all() db.session.add(self) + def _gather_specs_and_data_for_chart( + self, + sensors: list["Sensor"], # noqa F821 + chart_type: str, + event_starts_after: datetime | None, + event_ends_before: datetime | None, + beliefs_after: datetime | None, + beliefs_before: datetime | None, + source: DataSource + | list[DataSource] + | int + | list[int] + | str + | list[str] + | None, + include_data: bool, + dataset_name: str | None, + resolution: str | timedelta | None, + wrap_sensors_in_list: bool = False, + **kwargs, + ) -> dict: + """ + Centralizes the logic for generating Vega-Lite chart specifications. + + This utility function is designed to be used by both the `chart` and `charts` methods. + + :param sensors: A list of sensors to be displayed in the chart. + :param chart_type: currently only "bar_chart" # todo: where can we properly list the available chart types? + :param event_starts_after: only return beliefs about events that start after this datetime (inclusive) + :param event_ends_before: only return beliefs about events that end before this datetime (inclusive) + :param beliefs_after: only return beliefs formed after this datetime (inclusive) + :param beliefs_before: only return beliefs formed before this datetime (inclusive) + :param source: search only beliefs by this source (pass the DataSource, or its name or id) or list of sources + :param include_data: if True, include data in the chart, or if False, exclude data + :param dataset_name: optionally name the dataset used in the chart (the default name is sensor_) + :param resolution: optionally set the resolution of data being displayed + :param wrap_sensors_in_list: If True, wraps the sensors in a list, which is necessary when displaying multiple sensors in a single chart. + Defaults to False. + :param kwargs: Additional keyword arguments that can be passed to further customize the chart specifications. + :return: A dictionary containing the Vega-Lite chart specification. + """ + + if dataset_name is None: + dataset_name = "asset_" + str(self.id) + if event_starts_after: + kwargs["event_starts_after"] = event_starts_after + if event_ends_before: + kwargs["event_ends_before"] = event_ends_before + + sensors_to_show = [sensors] if wrap_sensors_in_list else self.sensors_to_show + + chart_specs = chart_type_to_chart_specs( + chart_type, + sensors_to_show=sensors_to_show, + dataset_name=dataset_name, + **kwargs, + ) + + if include_data: + data = self.search_beliefs( + sensors=sensors, + as_json=True, + event_starts_after=event_starts_after, + event_ends_before=event_ends_before, + beliefs_after=beliefs_after, + beliefs_before=beliefs_before, + source=source, + resolution=resolution, + ) + chart_specs["datasets"] = {dataset_name: json.loads(data)} + + return chart_specs + def create_generic_asset(generic_asset_type: str, **kwargs) -> GenericAsset: """Create a GenericAsset and assigns it an id. From 670eaa829801dfecbbc45590148de7ecc0a08e59 Mon Sep 17 00:00:00 2001 From: Mohamed Belhsan Hmida Date: Tue, 20 Aug 2024 22:53:08 +0200 Subject: [PATCH 3/3] Update charts method docstring to address it showing one chart per entry in . Signed-off-by: Mohamed Belhsan Hmida --- flexmeasures/data/models/generic_assets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flexmeasures/data/models/generic_assets.py b/flexmeasures/data/models/generic_assets.py index 76b9bb1c77..52ddfffacd 100644 --- a/flexmeasures/data/models/generic_assets.py +++ b/flexmeasures/data/models/generic_assets.py @@ -497,7 +497,7 @@ def charts( resolution: str | timedelta | None = None, **kwargs, ) -> list[dict]: - """Create multiple vega-lite charts showing sensor data for nested sensor groups. + """Create multiple vega-lite charts, one chart per entry in `sensors_to_show`. :param chart_type: currently only "bar_chart" # todo: where can we properly list the available chart types? :param event_starts_after: only return beliefs about events that start after this datetime (inclusive)