From 26bdcd0772511237b04ff9c99a0a822c4036e70b Mon Sep 17 00:00:00 2001 From: bvandekerkhof Date: Fri, 31 May 2024 10:07:02 +0200 Subject: [PATCH 1/4] Cycle through colormap using color_idx Signed-off-by: bvandekerkhof --- src/pyelq/meteorology.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pyelq/meteorology.py b/src/pyelq/meteorology.py index f1cf976..0305011 100644 --- a/src/pyelq/meteorology.py +++ b/src/pyelq/meteorology.py @@ -218,6 +218,7 @@ def plot_polar_scatter(self, fig, sensor_object: SensorGroup, template: object = ) else: theta = self.wind_direction + color_idx = i % len(sensor_object.color_map) fig.add_trace( go.Scatterpolar( @@ -225,7 +226,7 @@ def plot_polar_scatter(self, fig, sensor_object: SensorGroup, template: object = theta=theta, mode="markers", name=sensor_key, - marker={"color": sensor_object.color_map[i]}, + marker={"color": sensor_object.color_map[color_idx]}, ) ) From a57f282624ef8a92b2f6af22c0fd4671f3eea501 Mon Sep 17 00:00:00 2001 From: bvandekerkhof Date: Fri, 31 May 2024 14:12:40 +0200 Subject: [PATCH 2/4] bugfix plot polar scatter method Signed-off-by: bvandekerkhof --- src/pyelq/meteorology.py | 157 +++++++++++++++++++++++++++++---------- 1 file changed, 118 insertions(+), 39 deletions(-) diff --git a/src/pyelq/meteorology.py b/src/pyelq/meteorology.py index 0305011..8c692fd 100644 --- a/src/pyelq/meteorology.py +++ b/src/pyelq/meteorology.py @@ -124,7 +124,7 @@ def plot_polar_hist(self, nof_sectors: int = 16, nof_divisions: int = 5, templat Args: nof_sectors (int, optional): The number of wind direction sectors into which the data is binned. nof_divisions (int, optional): The number of wind speed divisions into which the data is binned. - template (go.update_layout): A layout template which can be applied to the plot. Defaults to None. + template (object): A layout template which can be applied to the plot. Defaults to None. Returns: fig (go.Figure): A plotly go figure containing the trace of the rose plot. @@ -195,13 +195,19 @@ def plot_polar_hist(self, nof_sectors: int = 16, nof_divisions: int = 5, templat return fig - def plot_polar_scatter(self, fig, sensor_object: SensorGroup, template: object = None) -> go.Figure(): + def plot_polar_scatter(self, fig: go.Figure, sensor_object: SensorGroup, template: object = None) -> go.Figure(): """Plots a scatter plot of concentration with respect to wind direction in polar Coordinates. + This function implements the polar scatter functionality for a (single) Meteorology object. Assuming the all + Sensors in the SensorGroup are consistent with the Meteorology object. + + Note we do plot the sensors which do not contain any values when present in the SensorGroup to keep consistency + in plot colors. + Args: fig (go.Figure): A plotly figure onto which traces can be drawn. sensor_object (SensorGroup): SensorGroup object which contains the concentration information - template (go.update_layout): A layout template which can be applied to the plot. Defaults to None. + template (object): A layout template which can be applied to the plot. Defaults to None. Returns: fig (go.Figure): A plotly go figure containing the trace of the rose plot. @@ -229,43 +235,10 @@ def plot_polar_scatter(self, fig, sensor_object: SensorGroup, template: object = marker={"color": sensor_object.color_map[color_idx]}, ) ) + if sensor.concentration.size > 0: + max_concentration = np.maximum(np.nanmax(sensor.concentration), max_concentration) - max_concentration = np.maximum(np.nanmax(sensor.concentration), max_concentration) - - ticktext = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"] - polar_dict = { - "radialaxis": {"tickangle": 0, "range": [0.0, 1.01 * max_concentration]}, - "radialaxis_angle": 0, - "angularaxis": { - "tickmode": "array", - "ticktext": ticktext, - "direction": "clockwise", - "rotation": 90, - "tickvals": list(np.linspace(0, 360 - (360 / 8), 8)), - }, - } - - fig.add_annotation( - x=1, - y=1, - yref="paper", - xref="paper", - xanchor="right", - yanchor="top", - align="left", - font={"size": 18, "color": "#000000"}, - showarrow=False, - borderwidth=2, - borderpad=10, - bgcolor="#ffffff", - bordercolor="#000000", - opacity=0.8, - text="Radial Axis: Wind
speed in m/s.", - ) - - fig.update_layout(polar=polar_dict) - fig.update_layout(template=template) - fig.update_layout(title="Measured Concentration against Wind Direction.") + fig = set_plot_polar_scatter_layout(max_concentration=max_concentration, fig=fig, template=template) return fig @@ -302,3 +275,109 @@ def calculate_wind_speed_from_uv(self): """Calculate wind speed from the u and v components for each member of the group.""" for met in self.values(): met.calculate_wind_speed_from_uv() + + def plot_polar_scatter(self, fig: go.Figure, sensor_object: SensorGroup, template: object = None) -> go.Figure(): + """Plots a scatter plot of concentration with respect to wind direction in polar coordinates. + + This function implements the polar scatter functionality for a MeteorologyGroup object. It assumes each object + in the SensorGroup has an associated Meteorology object in the MeteorologyGroup. + + Note we do plot the sensors which do not contain any values when present in the SensorGroup to keep consistency + in plot colors. + + Args: + fig (go.Figure): A plotly figure onto which traces can be drawn. + sensor_object (SensorGroup): SensorGroup object which contains the concentration information + template (object): A layout template which can be applied to the plot. Defaults to None. + + Returns: + fig (go.Figure): A plotly go figure containing the trace of the rose plot. + + Raises + ValueError: When there is a sensor key which is not present in the MeteorologyGroup. + + """ + max_concentration = 0 + + for i, (sensor_key, sensor) in enumerate(sensor_object.items()): + if sensor_key not in self.keys(): + raise ValueError(f"Key {sensor_key} not found in MeteorologyGroup.") + temp_met_object = self[sensor_key] + if sensor.concentration.shape != temp_met_object.wind_direction.shape: + warnings.warn( + f"Concentration values for sensor {sensor_key} are of shape " + + f"{sensor.concentration.shape}, but wind_direction values for meteorology object {sensor_key} " + f"has shape {temp_met_object.wind_direction.shape}. It will not be plotted on the polar scatter " + f"plot." + ) + else: + theta = temp_met_object.wind_direction + color_idx = i % len(sensor_object.color_map) + + fig.add_trace( + go.Scatterpolar( + r=sensor.concentration, + theta=theta, + mode="markers", + name=sensor_key, + marker={"color": sensor_object.color_map[color_idx]}, + ) + ) + + if sensor.concentration.size > 0: + max_concentration = np.maximum(np.nanmax(sensor.concentration), max_concentration) + + fig = set_plot_polar_scatter_layout(max_concentration=max_concentration, fig=fig, template=template) + + return fig + + +def set_plot_polar_scatter_layout(max_concentration: float, fig: go.Figure(), template: object) -> go.Figure: + """Helper function to set the layout of the polar scatter plot. + + Helps avoid code duplication. + + Args: + max_concentration (float): The maximum concentration value used to update radial axis range. + fig (go.Figure): A plotly figure onto which traces can be drawn. + template (object): A layout template which can be applied to the plot. + + Returns: + fig (go.Figure): A plotly go figure containing the trace of the rose plot. + + """ + ticktext = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"] + polar_dict = { + "radialaxis": {"tickangle": 0, "range": [0.0, 1.01 * max_concentration]}, + "radialaxis_angle": 0, + "angularaxis": { + "tickmode": "array", + "ticktext": ticktext, + "direction": "clockwise", + "rotation": 90, + "tickvals": list(np.linspace(0, 360 - (360 / 8), 8)), + }, + } + + fig.add_annotation( + x=1, + y=1, + yref="paper", + xref="paper", + xanchor="right", + yanchor="top", + align="left", + font={"size": 18, "color": "#000000"}, + showarrow=False, + borderwidth=2, + borderpad=10, + bgcolor="#ffffff", + bordercolor="#000000", + opacity=0.8, + text="Radial Axis: Wind
speed in m/s.", + ) + + fig.update_layout(polar=polar_dict) + fig.update_layout(template=template) + fig.update_layout(title="Measured Concentration against Wind Direction.") + return fig From b9b67d776de84445f00e9af7fab95c28a332d542 Mon Sep 17 00:00:00 2001 From: bvandekerkhof Date: Fri, 31 May 2024 14:12:50 +0200 Subject: [PATCH 3/4] Updating version Signed-off-by: bvandekerkhof --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1f62973..32ec6f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ build-backend = "poetry.core.masonry.api" [tool.poetry] name = "pyelq-sdk" -version = "1.0.5" +version = "1.0.6" description = "Package for detection, localization and quantification code." authors = ["Bas van de Kerkhof", "Matthew Jones", "David Randell"] homepage = "https://sede-open.github.io/pyELQ/" From 388fd6d302f9df2531a081420a93ee07d4205cda Mon Sep 17 00:00:00 2001 From: bvandekerkhof Date: Fri, 31 May 2024 14:17:12 +0200 Subject: [PATCH 4/4] black edits Signed-off-by: bvandekerkhof --- src/pyelq/meteorology.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pyelq/meteorology.py b/src/pyelq/meteorology.py index 8c692fd..87cc358 100644 --- a/src/pyelq/meteorology.py +++ b/src/pyelq/meteorology.py @@ -307,8 +307,8 @@ def plot_polar_scatter(self, fig: go.Figure, sensor_object: SensorGroup, templat warnings.warn( f"Concentration values for sensor {sensor_key} are of shape " + f"{sensor.concentration.shape}, but wind_direction values for meteorology object {sensor_key} " - f"has shape {temp_met_object.wind_direction.shape}. It will not be plotted on the polar scatter " - f"plot." + f"has shape {temp_met_object.wind_direction.shape}. It will not be plotted on the polar scatter " + f"plot." ) else: theta = temp_met_object.wind_direction