From 22bcdf6ffa8f088bfef530de2bfdbe3905da045a Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Thu, 2 May 2024 12:43:13 -0600 Subject: [PATCH 01/29] Update x-axis, y-axis, and plot layout. --- dimes/common.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/dimes/common.py b/dimes/common.py index cd764ae..0ce17c1 100644 --- a/dimes/common.py +++ b/dimes/common.py @@ -140,6 +140,11 @@ def __init__(self, x_axis_values: list): self.x_axis_values = x_axis_values self.subplots: List[Union[DimensionalSubplot, None]] = [None] self.is_finalized = False + self.LINE_WIDTH = 1.5 + self.WHITE = "white" + self.BLACK = "black" + self.GREY = "rgba(128,128,128,0.3)" + def add_display_data( self, @@ -167,6 +172,8 @@ def finalize_plot(self): number_of_subplots = len(self.subplots) subplot_domains = get_subplot_domains(number_of_subplots) absolute_axis_index = 0 # Used to track axes data in the plot + self.figure.layout["plot_bgcolor"] = self.WHITE + self.figure.layout["font_color"] = self.BLACK for subplot_index, subplot in enumerate(self.subplots): subplot_number = subplot_index + 1 x_axis_id = subplot_number @@ -224,6 +231,16 @@ def finalize_plot(self): ), "tickmode": "sync" if not is_base_y_axis else None, "autoshift": True if axis_number > 1 else None, + "mirror": True, + "linecolor":self.BLACK, + "linewidth":self.LINE_WIDTH, + "showgrid":True, + "gridcolor":self.GREY, + "gridwidth":self.LINE_WIDTH, + "zeroline":True, + "zerolinecolor":self.GREY, + "zerolinewidth":self.LINE_WIDTH, + } absolute_axis_index += 1 y_axis_side = "right" if y_axis_side == "left" else "left" @@ -237,6 +254,16 @@ def finalize_plot(self): else None ), "showticklabels": None if is_last_subplot else False, + "ticks":"outside", + "tickson":"boundaries", + "tickcolor":self.BLACK, + "tickwidth":self.LINE_WIDTH, + "mirror": True, + "linecolor":self.BLACK, + "linewidth":self.LINE_WIDTH, + "zeroline":True, + "zerolinecolor":self.GREY, + "zerolinewidth":self.LINE_WIDTH, } else: warnings.warn(f"Subplot {subplot_number} is unused.") From 29a683ee0d93f728f2171fefa6bcd886e4e4b599 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Thu, 2 May 2024 16:11:17 -0600 Subject: [PATCH 02/29] Automatically set y-axis range. --- dimes/common.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/dimes/common.py b/dimes/common.py index 0ce17c1..83de5eb 100644 --- a/dimes/common.py +++ b/dimes/common.py @@ -144,6 +144,7 @@ def __init__(self, x_axis_values: list): self.WHITE = "white" self.BLACK = "black" self.GREY = "rgba(128,128,128,0.3)" + self.RANGE_BUFFER = 0.1 def add_display_data( @@ -164,6 +165,17 @@ def add_display_data( if self.subplots[subplot_index] is None: self.subplots[subplot_index] = DimensionalSubplot() self.subplots[subplot_index].add_display_data(display_data, axis_name) # type: ignore[union-attr] + + def append_y_values_range(self, y_values): + self.y_range["min"].append(min(y_values)) + self.y_range["max"].append(max(y_values)) + + def get_axis_range(self, values): + min_value = min(values["min"]) + max_value = max(values["max"]) + range_values = max_value - min_value + return [min_value - range_values*self.RANGE_BUFFER, max_value + range_values*self.RANGE_BUFFER] + def finalize_plot(self): """Once all TimeSeriesData objects have been added, generate plot and subplots.""" @@ -182,16 +194,19 @@ def finalize_plot(self): y_axis_side = "left" for axis_number, axis in enumerate(subplot.axes): y_axis_id = absolute_axis_index + 1 + self.y_range: dict = {"min":[],"max":[]} for display_data in axis.display_data_set: at_least_one_subplot = True - self.figure.add_trace( - Scatter( - x=self.x_axis_values, - y=koozie.convert( + y_values = koozie.convert( display_data.data_values, display_data.native_units, axis.units, - ), + ) + self.append_y_values_range(y_values) + self.figure.add_trace( + Scatter( + x=self.x_axis_values, + y=y_values, name=display_data.name, yaxis=f"y{y_axis_id}", xaxis=f"x{x_axis_id}", @@ -240,6 +255,7 @@ def finalize_plot(self): "zeroline":True, "zerolinecolor":self.GREY, "zerolinewidth":self.LINE_WIDTH, + "range":self.get_axis_range(self.y_range) } absolute_axis_index += 1 From ce477e6cdf48cb36bf071cbd6212e5fd8152f91a Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Fri, 3 May 2024 09:24:25 -0600 Subject: [PATCH 03/29] Add title to plot. --- dimes/timeseries.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dimes/timeseries.py b/dimes/timeseries.py index f00438c..61bbc2d 100644 --- a/dimes/timeseries.py +++ b/dimes/timeseries.py @@ -1,6 +1,7 @@ """Module for plotting time series data.""" from .common import DimensionalPlot, DisplayData +from typing import Union class TimeSeriesData(DisplayData): @@ -12,7 +13,8 @@ class TimeSeriesData(DisplayData): class TimeSeriesPlot(DimensionalPlot): """Time series plot.""" - def __init__(self, time_values: list): + def __init__(self, time_values: list, plot_title: Union[str, None] = None): super().__init__(time_values) self.add_time_series = self.add_display_data self.time_values = self.x_axis_values + self.figure.layout["title"] = plot_title From 36406388aa0311aa3b2dc7299684b78ee1308e96 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Fri, 3 May 2024 09:24:42 -0600 Subject: [PATCH 04/29] Center plot title. --- dimes/common.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dimes/common.py b/dimes/common.py index 83de5eb..b663b49 100644 --- a/dimes/common.py +++ b/dimes/common.py @@ -186,6 +186,7 @@ def finalize_plot(self): absolute_axis_index = 0 # Used to track axes data in the plot self.figure.layout["plot_bgcolor"] = self.WHITE self.figure.layout["font_color"] = self.BLACK + self.figure.layout["title_x"] = 0.5 for subplot_index, subplot in enumerate(self.subplots): subplot_number = subplot_index + 1 x_axis_id = subplot_number From 5f80b8ee4e73066bbf56ed183a3568e75e74af15 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Fri, 3 May 2024 09:25:14 -0600 Subject: [PATCH 05/29] Add title to test_basic_plot unit test. --- test/test_timeseries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_timeseries.py b/test/test_timeseries.py index cafdd26..0744c91 100644 --- a/test/test_timeseries.py +++ b/test/test_timeseries.py @@ -10,7 +10,7 @@ def test_basic_plot(): """Test basic plot""" - plot = TimeSeriesPlot([1, 2, 3, 4, 5]) + plot = TimeSeriesPlot([1, 2, 3, 4, 5], "Title Basic Plot") plot.add_time_series(TimeSeriesData([x**2 for x in plot.time_values])) plot.add_time_series(TimeSeriesData([x**3 for x in plot.time_values])) From b07e0feaa6bed3f27fca86f3ce5b00e069cdee9c Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Fri, 3 May 2024 11:33:42 -0600 Subject: [PATCH 06/29] Add legend_group unit test. --- test/test_timeseries.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/test_timeseries.py b/test/test_timeseries.py index 0744c91..517a6d4 100644 --- a/test/test_timeseries.py +++ b/test/test_timeseries.py @@ -135,3 +135,19 @@ def test_missing_marker_symbol(): ) ) plot.write_html_plot(Path(TESTING_DIRECTORY, "missing_marker_symbol.html")) + +def test_legend_group(): + """Test legend group and legend group title""" + plot = TimeSeriesPlot([1, 2, 3, 4, 5]) + city_data = {"City_A":{2000:[x**2 for x in plot.time_values],2010:[x**3 for x in plot.time_values]}, + "City_B":{2000:[x**2.5 for x in plot.time_values],2010:[x**3.5 for x in plot.time_values]}} + for city, year_data in city_data.items(): + for year, data in year_data.items(): + plot.add_time_series( + TimeSeriesData( + data, + name = city, + legend_group = year + ) + ) + plot.write_html_plot(Path(TESTING_DIRECTORY, "legend_group.html")) \ No newline at end of file From 5c457ab5e7732b75ea0b4bfb6db1c304d03aa2ab Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Fri, 3 May 2024 11:35:04 -0600 Subject: [PATCH 07/29] Add legend_group. --- dimes/common.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/dimes/common.py b/dimes/common.py index b663b49..0b4108f 100644 --- a/dimes/common.py +++ b/dimes/common.py @@ -76,10 +76,12 @@ def __init__( display_units: Union[str, None] = None, line_properties: LineProperties = LineProperties(), is_visible: bool = True, + legend_group: Union[str, int, None] = None, ): super().__init__(data_values, name, native_units, display_units) self.line_properties = line_properties self.is_visible = is_visible + self.legend_group = legend_group class DimensionalAxis: @@ -214,7 +216,7 @@ def finalize_plot(self): mode=display_data.line_properties.get_line_mode(), visible=( "legendonly" - if not display_data.line_properties.is_visible + if not display_data.is_visible else True ), line={ @@ -230,6 +232,8 @@ def finalize_plot(self): "width": 2, }, }, + legendgroup=display_data.legend_group, + legendgrouptitle={"text":display_data.legend_group}, ), ) is_base_y_axis = subplot_base_y_axis_id == y_axis_id From 2a3e1fb84c661711966048a45599e4cf225d855f Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Mon, 6 May 2024 06:27:20 -0600 Subject: [PATCH 08/29] Remove is_visible from LineProperties. --- dimes/common.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dimes/common.py b/dimes/common.py index 0b4108f..3801e70 100644 --- a/dimes/common.py +++ b/dimes/common.py @@ -17,7 +17,6 @@ class LineProperties: marker_size: Union[int, None] = None marker_line_color: Union[str, None] = None marker_fill_color: Union[str, None] = None - is_visible: bool = True def get_line_mode(self): if all( From 09833d1edcb9ba288c0be7fdbd84074b428c5971 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Mon, 6 May 2024 06:52:13 -0600 Subject: [PATCH 09/29] Add whitespace. --- test/test_timeseries.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_timeseries.py b/test/test_timeseries.py index 517a6d4..d54b9ca 100644 --- a/test/test_timeseries.py +++ b/test/test_timeseries.py @@ -136,6 +136,7 @@ def test_missing_marker_symbol(): ) plot.write_html_plot(Path(TESTING_DIRECTORY, "missing_marker_symbol.html")) + def test_legend_group(): """Test legend group and legend group title""" plot = TimeSeriesPlot([1, 2, 3, 4, 5]) From 28bd61f77051e235f978af6e851238d205d1b6e7 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Mon, 6 May 2024 06:54:30 -0600 Subject: [PATCH 10/29] Add periods to docstrings. --- test/test_timeseries.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_timeseries.py b/test/test_timeseries.py index d54b9ca..9ef7a23 100644 --- a/test/test_timeseries.py +++ b/test/test_timeseries.py @@ -121,7 +121,7 @@ def test_basic_marker(): def test_missing_marker_symbol(): - """Test missing marker symbol, default symbol should be 'circle'""" + """Test missing marker symbol, default symbol should be 'circle'.""" plot = TimeSeriesPlot([1, 2, 3, 4, 5]) plot.add_time_series( TimeSeriesData( @@ -138,7 +138,7 @@ def test_missing_marker_symbol(): def test_legend_group(): - """Test legend group and legend group title""" + """Test legend group and legend group title.""" plot = TimeSeriesPlot([1, 2, 3, 4, 5]) city_data = {"City_A":{2000:[x**2 for x in plot.time_values],2010:[x**3 for x in plot.time_values]}, "City_B":{2000:[x**2.5 for x in plot.time_values],2010:[x**3.5 for x in plot.time_values]}} From c388b5e7a1508ab3b51267805983079e1e07da83 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Mon, 6 May 2024 06:55:09 -0600 Subject: [PATCH 11/29] Add test_is_visible. --- test/test_timeseries.py | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/test/test_timeseries.py b/test/test_timeseries.py index 9ef7a23..552c603 100644 --- a/test/test_timeseries.py +++ b/test/test_timeseries.py @@ -151,4 +151,36 @@ def test_legend_group(): legend_group = year ) ) - plot.write_html_plot(Path(TESTING_DIRECTORY, "legend_group.html")) \ No newline at end of file + plot.write_html_plot(Path(TESTING_DIRECTORY, "legend_group.html")) + + +def test_is_visible(): + """Test visibility of lines in plot and legend.""" + plot = TimeSeriesPlot([1, 2, 3, 4, 5]) + plot.add_time_series( + TimeSeriesData( + [x**2 for x in plot.time_values], + line_properties=LineProperties( + color="blue", + marker_size=5, + marker_line_color="black", + marker_fill_color="white", + ), + is_visible=True, + name = "Visible" + ) + ) + plot.add_time_series( + TimeSeriesData( + [x**3 for x in plot.time_values], + line_properties=LineProperties( + color="green", + marker_size=5, + marker_line_color="black", + marker_fill_color="white", + ), + is_visible=False, + name = "Legend Only" + ) + ) + plot.write_html_plot(Path(TESTING_DIRECTORY, "is_visible.html")) \ No newline at end of file From 99bbd2478a317fa5e65176231f9ab3dd6b7843e4 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Mon, 6 May 2024 12:51:43 -0600 Subject: [PATCH 12/29] Move is_visible and legend_group to LegendProperties class. --- dimes/common.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/dimes/common.py b/dimes/common.py index 3801e70..710b34c 100644 --- a/dimes/common.py +++ b/dimes/common.py @@ -33,6 +33,12 @@ def get_line_mode(self): return "lines+markers" +@dataclass +class LegendProperties: + is_visible: bool = True + legend_group: Union[str, int, None] = None + + class DimensionalData: def __init__( self, @@ -74,13 +80,11 @@ def __init__( native_units: str = "", display_units: Union[str, None] = None, line_properties: LineProperties = LineProperties(), - is_visible: bool = True, - legend_group: Union[str, int, None] = None, + legend_properties: LegendProperties = LegendProperties(), ): super().__init__(data_values, name, native_units, display_units) self.line_properties = line_properties - self.is_visible = is_visible - self.legend_group = legend_group + self.legend_properties = legend_properties class DimensionalAxis: @@ -215,7 +219,7 @@ def finalize_plot(self): mode=display_data.line_properties.get_line_mode(), visible=( "legendonly" - if not display_data.is_visible + if not display_data.legend_properties.is_visible else True ), line={ @@ -231,8 +235,8 @@ def finalize_plot(self): "width": 2, }, }, - legendgroup=display_data.legend_group, - legendgrouptitle={"text":display_data.legend_group}, + legendgroup=display_data.legend_properties.legend_group, + legendgrouptitle={"text":display_data.legend_properties.legend_group}, ), ) is_base_y_axis = subplot_base_y_axis_id == y_axis_id From 3da01d86dea4993cb6d8280bf6835ea4ddcf141a Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Mon, 6 May 2024 12:53:47 -0600 Subject: [PATCH 13/29] Import LegendProperties. --- dimes/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dimes/__init__.py b/dimes/__init__.py index 998d138..04f0f38 100644 --- a/dimes/__init__.py +++ b/dimes/__init__.py @@ -1,5 +1,5 @@ """dimes public interface""" -from .common import LineProperties, DisplayData +from .common import LegendProperties, LineProperties, DisplayData from .timeseries import TimeSeriesPlot, TimeSeriesData from .griddeddata import GridAxis, GridPointData, RegularGridData From 82f09d11bbe5860c74cdb4b06cce7e278d6d5922 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Mon, 6 May 2024 13:05:40 -0600 Subject: [PATCH 14/29] Add LegendProperties to applicable unit tests. --- test/test_timeseries.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/test/test_timeseries.py b/test/test_timeseries.py index 552c603..82152bb 100644 --- a/test/test_timeseries.py +++ b/test/test_timeseries.py @@ -2,7 +2,7 @@ from pathlib import Path import pytest -from dimes import TimeSeriesPlot, TimeSeriesData, LineProperties +from dimes import TimeSeriesPlot, TimeSeriesData, LineProperties, LegendProperties TESTING_DIRECTORY = Path("test_outputs") TESTING_DIRECTORY.mkdir(exist_ok=True) @@ -76,7 +76,9 @@ def test_multi_plot(): [x * 10 for x in plot.time_values], name="Capacity", native_units="kBtu/h", - is_visible=False, + legend_properties=LegendProperties( + is_visible=False + ) ) ) # Time series and axis will get name from dimensionality, subplot default to 1, new axis for new dimension on existing subplot @@ -148,7 +150,9 @@ def test_legend_group(): TimeSeriesData( data, name = city, - legend_group = year + legend_properties=LegendProperties( + legend_group = year + ), ) ) plot.write_html_plot(Path(TESTING_DIRECTORY, "legend_group.html")) @@ -166,7 +170,9 @@ def test_is_visible(): marker_line_color="black", marker_fill_color="white", ), - is_visible=True, + legend_properties=LegendProperties( + is_visible=True, + ), name = "Visible" ) ) @@ -179,7 +185,9 @@ def test_is_visible(): marker_line_color="black", marker_fill_color="white", ), - is_visible=False, + legend_properties=LegendProperties( + is_visible=False, + ), name = "Legend Only" ) ) From 8a7a1fb2115d493b8f256d2f5b74b02e6561ff62 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Wed, 8 May 2024 10:44:21 -0600 Subject: [PATCH 15/29] Rename LINE_WIDTH to GRID_LINE_WIDTH. --- dimes/common.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/dimes/common.py b/dimes/common.py index 710b34c..ca49a1c 100644 --- a/dimes/common.py +++ b/dimes/common.py @@ -145,7 +145,7 @@ def __init__(self, x_axis_values: list): self.x_axis_values = x_axis_values self.subplots: List[Union[DimensionalSubplot, None]] = [None] self.is_finalized = False - self.LINE_WIDTH = 1.5 + self.GRID_LINE_WIDTH = 1.5 self.WHITE = "white" self.BLACK = "black" self.GREY = "rgba(128,128,128,0.3)" @@ -259,10 +259,7 @@ def finalize_plot(self): "linewidth":self.LINE_WIDTH, "showgrid":True, "gridcolor":self.GREY, - "gridwidth":self.LINE_WIDTH, - "zeroline":True, - "zerolinecolor":self.GREY, - "zerolinewidth":self.LINE_WIDTH, + "gridwidth":self.GRID_LINE_WIDTH, "range":self.get_axis_range(self.y_range) } @@ -281,14 +278,7 @@ def finalize_plot(self): "ticks":"outside", "tickson":"boundaries", "tickcolor":self.BLACK, - "tickwidth":self.LINE_WIDTH, - "mirror": True, - "linecolor":self.BLACK, - "linewidth":self.LINE_WIDTH, - "zeroline":True, - "zerolinecolor":self.GREY, - "zerolinewidth":self.LINE_WIDTH, - } + "tickwidth":self.GRID_LINE_WIDTH, else: warnings.warn(f"Subplot {subplot_number} is unused.") if not at_least_one_subplot: From 63461e4749e843ebb586336b7cb8afb1f443e285 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Wed, 8 May 2024 10:49:56 -0600 Subject: [PATCH 16/29] Move common x and y-axis format items to a dictionary. --- dimes/common.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/dimes/common.py b/dimes/common.py index ca49a1c..6ddf3e9 100644 --- a/dimes/common.py +++ b/dimes/common.py @@ -192,6 +192,14 @@ def finalize_plot(self): self.figure.layout["plot_bgcolor"] = self.WHITE self.figure.layout["font_color"] = self.BLACK self.figure.layout["title_x"] = 0.5 + xy_common_axis_format = { + "mirror": True, + "linecolor":self.BLACK, + "linewidth":self.GRID_LINE_WIDTH, + "zeroline":True, + "zerolinecolor":self.GREY, + "zerolinewidth":self.GRID_LINE_WIDTH, + } for subplot_index, subplot in enumerate(self.subplots): subplot_number = subplot_index + 1 x_axis_id = subplot_number @@ -254,15 +262,13 @@ def finalize_plot(self): ), "tickmode": "sync" if not is_base_y_axis else None, "autoshift": True if axis_number > 1 else None, - "mirror": True, - "linecolor":self.BLACK, - "linewidth":self.LINE_WIDTH, "showgrid":True, "gridcolor":self.GREY, "gridwidth":self.GRID_LINE_WIDTH, "range":self.get_axis_range(self.y_range) } + self.figure.layout[f"yaxis{y_axis_id}"].update(xy_common_axis_format) absolute_axis_index += 1 y_axis_side = "right" if y_axis_side == "left" else "left" is_last_subplot = subplot_number == number_of_subplots @@ -279,6 +285,8 @@ def finalize_plot(self): "tickson":"boundaries", "tickcolor":self.BLACK, "tickwidth":self.GRID_LINE_WIDTH, + } + self.figure.layout[f"xaxis{x_axis_id}"].update(xy_common_axis_format) else: warnings.warn(f"Subplot {subplot_number} is unused.") if not at_least_one_subplot: From 1150920f9e28a5bc34db08cd8c5eeb883afed402 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Wed, 8 May 2024 11:11:01 -0600 Subject: [PATCH 17/29] Delete whitespace. --- dimes/common.py | 1 - 1 file changed, 1 deletion(-) diff --git a/dimes/common.py b/dimes/common.py index 6ddf3e9..fe34914 100644 --- a/dimes/common.py +++ b/dimes/common.py @@ -266,7 +266,6 @@ def finalize_plot(self): "gridcolor":self.GREY, "gridwidth":self.GRID_LINE_WIDTH, "range":self.get_axis_range(self.y_range) - } self.figure.layout[f"yaxis{y_axis_id}"].update(xy_common_axis_format) absolute_axis_index += 1 From e045f41c405ac20b838c4e8806ba93a13c2242c4 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Thu, 9 May 2024 15:41:24 -0600 Subject: [PATCH 18/29] Move LegendProperties data members to DisplayData. --- dimes/__init__.py | 1 - dimes/common.py | 15 ++++++--------- test/test_timeseries.py | 19 ++++++------------- 3 files changed, 12 insertions(+), 23 deletions(-) diff --git a/dimes/__init__.py b/dimes/__init__.py index 48bac4c..bd43f5f 100644 --- a/dimes/__init__.py +++ b/dimes/__init__.py @@ -1,7 +1,6 @@ """dimes public interface""" from .common import ( - LegendProperties, LineProperties, MarkersOnly, LinesOnly, diff --git a/dimes/common.py b/dimes/common.py index 0e62134..84cad48 100644 --- a/dimes/common.py +++ b/dimes/common.py @@ -56,11 +56,6 @@ class LinesOnly(LineProperties): marker_size: Union[int, None] = 0 -@dataclass -class LegendProperties: - is_visible: bool = True - legend_group: Union[str, int, None] = None - class DimensionalData: def __init__( @@ -113,11 +108,13 @@ def __init__( native_units: str = "", display_units: Union[str, None] = None, line_properties: LineProperties = LineProperties(), - legend_properties: LegendProperties = LegendProperties(), + is_visible: bool = True, + legend_group: Union[str, None] = None ): super().__init__(data_values, name, native_units, display_units) self.line_properties = line_properties - self.legend_properties = legend_properties + self.is_visible = is_visible + self.legend_group = legend_group class DimensionalAxis: @@ -289,8 +286,8 @@ def finalize_plot(self): "width": display_data.line_properties.marker_line_width, }, }, - legendgroup=display_data.legend_properties.legend_group, - legendgrouptitle={"text":display_data.legend_properties.legend_group}, + legendgroup=display_data.legend_group, + legendgrouptitle={"text":display_data.legend_group}, ), ) is_base_y_axis = subplot_base_y_axis_id == y_axis_id diff --git a/test/test_timeseries.py b/test/test_timeseries.py index 82152bb..62b060a 100644 --- a/test/test_timeseries.py +++ b/test/test_timeseries.py @@ -76,9 +76,7 @@ def test_multi_plot(): [x * 10 for x in plot.time_values], name="Capacity", native_units="kBtu/h", - legend_properties=LegendProperties( - is_visible=False - ) + is_visible=False ) ) # Time series and axis will get name from dimensionality, subplot default to 1, new axis for new dimension on existing subplot @@ -150,10 +148,9 @@ def test_legend_group(): TimeSeriesData( data, name = city, - legend_properties=LegendProperties( - legend_group = year - ), - ) + legend_group = str(year), + ), + ) plot.write_html_plot(Path(TESTING_DIRECTORY, "legend_group.html")) @@ -170,9 +167,7 @@ def test_is_visible(): marker_line_color="black", marker_fill_color="white", ), - legend_properties=LegendProperties( - is_visible=True, - ), + is_visible = True, name = "Visible" ) ) @@ -185,9 +180,7 @@ def test_is_visible(): marker_line_color="black", marker_fill_color="white", ), - legend_properties=LegendProperties( - is_visible=False, - ), + is_visible = False, name = "Legend Only" ) ) From 3276f10921ba7e0542fec570cd56b7d47899178d Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Thu, 9 May 2024 15:42:11 -0600 Subject: [PATCH 19/29] delete legend_properties from is_visible. --- dimes/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dimes/common.py b/dimes/common.py index 84cad48..b08b51c 100644 --- a/dimes/common.py +++ b/dimes/common.py @@ -269,7 +269,7 @@ def finalize_plot(self): mode=display_data.line_properties.get_line_mode(), visible=( "legendonly" - if not display_data.legend_properties.is_visible + if not display_data.is_visible else True ), line={ From d0554ee0be1887c2cacb75980e838876116f7393 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Thu, 9 May 2024 15:43:21 -0600 Subject: [PATCH 20/29] Modify axis range calculation. --- dimes/common.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/dimes/common.py b/dimes/common.py index b08b51c..04d22b0 100644 --- a/dimes/common.py +++ b/dimes/common.py @@ -125,11 +125,19 @@ def __init__(self, display_data: DisplayData, name: Union[str, None]) -> None: self.units = display_data.display_units self.dimensionality = display_data.dimensionality self.display_data_set: List[DisplayData] = [display_data] + self.range_min: SupportsFloat = float("inf") + self.range_max: SupportsFloat = -float("inf") def get_axis_label(self) -> str: """Make the string that appears as the axis label""" return f"{self.name} [{self.units}]" + @staticmethod + def get_axis_range(value_min, value_max): + range_buffer = 0.1 + range_values = value_max - value_min + return [value_min - range_values*range_buffer, value_max + range_values*range_buffer] + class DimensionalSubplot: """Dimensional subplot. May contain multiple `DimensionalAxis` objects.""" @@ -209,16 +217,6 @@ def add_display_data( if self.subplots[subplot_index] is None: self.subplots[subplot_index] = DimensionalSubplot() self.subplots[subplot_index].add_display_data(display_data, axis_name) # type: ignore[union-attr] - - def append_y_values_range(self, y_values): - self.y_range["min"].append(min(y_values)) - self.y_range["max"].append(max(y_values)) - - def get_axis_range(self, values): - min_value = min(values["min"]) - max_value = max(values["max"]) - range_values = max_value - min_value - return [min_value - range_values*self.RANGE_BUFFER, max_value + range_values*self.RANGE_BUFFER] def finalize_plot(self): @@ -250,7 +248,6 @@ def finalize_plot(self): y_axis_side = "left" for axis_number, axis in enumerate(subplot.axes): y_axis_id = absolute_axis_index + 1 - self.y_range: dict = {"min":[],"max":[]} for display_data in axis.display_data_set: at_least_one_subplot = True y_values = koozie.convert( @@ -258,7 +255,8 @@ def finalize_plot(self): display_data.native_units, axis.units, ) - self.append_y_values_range(y_values) + axis.range_min = min(min(y_values), axis.range_min) + axis.range_max = max(max(y_values), axis.range_max) self.figure.add_trace( Scatter( x=self.x_axis.data_values, @@ -308,7 +306,7 @@ def finalize_plot(self): "showgrid":True, "gridcolor":self.GREY, "gridwidth":self.GRID_LINE_WIDTH, - "range":self.get_axis_range(self.y_range) + "range":axis.get_axis_range(axis.range_min, axis.range_max) } self.figure.layout[f"yaxis{y_axis_id}"].update(xy_common_axis_format) absolute_axis_index += 1 From bfd69f0e5068b882e10497b0fc5d839614abd25a Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Thu, 9 May 2024 15:44:57 -0600 Subject: [PATCH 21/29] Move and rename class data members. --- dimes/common.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/dimes/common.py b/dimes/common.py index 04d22b0..db57292 100644 --- a/dimes/common.py +++ b/dimes/common.py @@ -9,6 +9,9 @@ import koozie +WHITE = "white" +BLACK = "black" +GREY = "rgba(128,128,128,0.3)" @dataclass class LineProperties: @@ -222,20 +225,21 @@ def add_display_data( def finalize_plot(self): """Once all DisplayData objects have been added, generate plot and subplots.""" if not self.is_finalized: + grid_line_width = 1.5 at_least_one_subplot = False number_of_subplots = len(self.subplots) subplot_domains = get_subplot_domains(number_of_subplots) absolute_axis_index = 0 # Used to track axes data in the plot - self.figure.layout["plot_bgcolor"] = self.WHITE - self.figure.layout["font_color"] = self.BLACK + self.figure.layout["plot_bgcolor"] = WHITE + self.figure.layout["font_color"] = BLACK self.figure.layout["title_x"] = 0.5 xy_common_axis_format = { "mirror": True, - "linecolor":self.BLACK, - "linewidth":self.GRID_LINE_WIDTH, + "linecolor":BLACK, + "linewidth":grid_line_width, "zeroline":True, - "zerolinecolor":self.GREY, - "zerolinewidth":self.GRID_LINE_WIDTH, + "zerolinecolor":GREY, + "zerolinewidth":grid_line_width, } x_axis_label = f"{self.x_axis.name}" if isinstance(self.x_axis, DimensionalData): @@ -304,8 +308,8 @@ def finalize_plot(self): "tickmode": "sync" if not is_base_y_axis else None, "autoshift": True if axis_number > 1 else None, "showgrid":True, - "gridcolor":self.GREY, - "gridwidth":self.GRID_LINE_WIDTH, + "gridcolor":GREY, + "gridwidth":grid_line_width, "range":axis.get_axis_range(axis.range_min, axis.range_max) } self.figure.layout[f"yaxis{y_axis_id}"].update(xy_common_axis_format) @@ -324,8 +328,8 @@ def finalize_plot(self): "showticklabels": None if is_last_subplot else False, "ticks":"outside", "tickson":"boundaries", - "tickcolor":self.BLACK, - "tickwidth":self.GRID_LINE_WIDTH, + "tickcolor":BLACK, + "tickwidth":grid_line_width, } self.figure.layout[f"xaxis{x_axis_id}"].update(xy_common_axis_format) else: From beee0255afb875351b824bfe063ae2ce66b54518 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Thu, 9 May 2024 15:45:51 -0600 Subject: [PATCH 22/29] Move plot_title to common.py. --- dimes/common.py | 8 ++------ dimes/timeseries.py | 5 ++--- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/dimes/common.py b/dimes/common.py index db57292..58cf128 100644 --- a/dimes/common.py +++ b/dimes/common.py @@ -182,7 +182,7 @@ class DimensionalPlot: """Plot of dimensional data.""" def __init__( - self, x_axis: Union[DimensionalData, TimeSeriesAxis, List[SupportsFloat], List[datetime]] + self, x_axis: Union[DimensionalData, TimeSeriesAxis, List[SupportsFloat], List[datetime]], plot_title: Union[str, None] = None ): self.figure = Figure() self.x_axis: Union[DimensionalData, TimeSeriesAxis] @@ -195,11 +195,7 @@ def __init__( self.x_axis = x_axis self.subplots: List[Union[DimensionalSubplot, None]] = [None] self.is_finalized = False - self.GRID_LINE_WIDTH = 1.5 - self.WHITE = "white" - self.BLACK = "black" - self.GREY = "rgba(128,128,128,0.3)" - self.RANGE_BUFFER = 0.1 + self.figure.layout["title"] = plot_title def add_display_data( diff --git a/dimes/timeseries.py b/dimes/timeseries.py index 0a145f6..83ac0bd 100644 --- a/dimes/timeseries.py +++ b/dimes/timeseries.py @@ -14,7 +14,6 @@ class TimeSeriesPlot(DimensionalPlot): """Time series plot.""" def __init__(self, time_values: list, plot_title: Union[str, None] = None): - super().__init__(time_values) + super().__init__(time_values, plot_title) self.add_time_series = self.add_display_data - self.time_values = self.x_axis.data_values - self.figure.layout["title"] = plot_title + self.time_values = self.x_axis.data_values \ No newline at end of file From 2cf9fe78c413aebec32a10a81da6abf986092d29 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Thu, 9 May 2024 15:46:32 -0600 Subject: [PATCH 23/29] Use LinesOnly in test_is_visible unit test. --- test/test_timeseries.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_timeseries.py b/test/test_timeseries.py index 62b060a..6e0c5c3 100644 --- a/test/test_timeseries.py +++ b/test/test_timeseries.py @@ -2,7 +2,7 @@ from pathlib import Path import pytest -from dimes import TimeSeriesPlot, TimeSeriesData, LineProperties, LegendProperties +from dimes import TimeSeriesPlot, TimeSeriesData, LineProperties, LinesOnly TESTING_DIRECTORY = Path("test_outputs") TESTING_DIRECTORY.mkdir(exist_ok=True) @@ -174,7 +174,7 @@ def test_is_visible(): plot.add_time_series( TimeSeriesData( [x**3 for x in plot.time_values], - line_properties=LineProperties( + line_properties=LinesOnly( color="green", marker_size=5, marker_line_color="black", From 0223761cea72c4f90233f8ac1cab4d6d68e9dea7 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Thu, 9 May 2024 15:46:58 -0600 Subject: [PATCH 24/29] Use marker_line_width in test_is_visible unit test. --- test/test_timeseries.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_timeseries.py b/test/test_timeseries.py index 6e0c5c3..a63050c 100644 --- a/test/test_timeseries.py +++ b/test/test_timeseries.py @@ -166,6 +166,7 @@ def test_is_visible(): marker_size=5, marker_line_color="black", marker_fill_color="white", + marker_line_width=1.5 ), is_visible = True, name = "Visible" From e23e3ea63973812b35dd196d9c3c8c97aeb6b268 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Thu, 9 May 2024 15:47:46 -0600 Subject: [PATCH 25/29] Change type hinting from int to SupportsFloat. --- dimes/common.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dimes/common.py b/dimes/common.py index 58cf128..623cb0f 100644 --- a/dimes/common.py +++ b/dimes/common.py @@ -17,11 +17,11 @@ class LineProperties: color: Union[str, None] = None line_type: Union[str, None] = None - line_width: Union[int, None] = None + line_width: Union[SupportsFloat, None] = None marker_symbol: Union[str, None] = None - marker_size: Union[int, None] = None + marker_size: Union[SupportsFloat, None] = None marker_line_color: Union[str, None] = None - marker_line_width: Union[int, None] = None + marker_line_width: Union[SupportsFloat, None] = None marker_fill_color: Union[str, None] = None def get_line_mode(self): @@ -51,12 +51,12 @@ def get_line_mode(self): @dataclass class MarkersOnly(LineProperties): - line_width: Union[int, None] = 0 + line_width: Union[SupportsFloat, None] = 0 @dataclass class LinesOnly(LineProperties): - marker_size: Union[int, None] = 0 + marker_size: Union[SupportsFloat, None] = 0 From 9642d9f94a9dde2db9e5b470a18084b6429482ea Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Thu, 9 May 2024 16:04:14 -0600 Subject: [PATCH 26/29] Rename self.x_axis.data_values to x_axis_values. --- dimes/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dimes/common.py b/dimes/common.py index 54ed809..56c5198 100644 --- a/dimes/common.py +++ b/dimes/common.py @@ -284,7 +284,7 @@ def finalize_plot(self): ) self.figure.add_trace( Scatter( - x=self.x_axis.data_values, + x=x_axis_values, y=y_values, name=display_data.name, yaxis=f"y{y_axis_id}", From cca2f7fa0bec2bc538e9e6a78973b1d5550bd074 Mon Sep 17 00:00:00 2001 From: Nathan Oliver Date: Thu, 9 May 2024 16:06:15 -0600 Subject: [PATCH 27/29] Edit formatting. --- dimes/common.py | 52 +++++++++++++++++++---------------------- dimes/timeseries.py | 2 +- test/test_timeseries.py | 42 ++++++++++++--------------------- 3 files changed, 40 insertions(+), 56 deletions(-) diff --git a/dimes/common.py b/dimes/common.py index 56c5198..283cdcd 100644 --- a/dimes/common.py +++ b/dimes/common.py @@ -13,6 +13,7 @@ BLACK = "black" GREY = "rgba(128,128,128,0.3)" + @dataclass class LineProperties: color: Union[str, None] = None @@ -59,7 +60,6 @@ class LinesOnly(LineProperties): marker_size: Union[SupportsFloat, None] = 0 - class DimensionalData: def __init__( self, @@ -144,7 +144,7 @@ def get_axis_label(self) -> str: def get_axis_range(value_min, value_max): range_buffer = 0.1 range_values = value_max - value_min - return [value_min - range_values*range_buffer, value_max + range_values*range_buffer] + return [value_min - range_values * range_buffer, value_max + range_values * range_buffer] class DimensionalSubplot: @@ -183,7 +183,9 @@ class DimensionalPlot: """Plot of dimensional data.""" def __init__( - self, x_axis: Union[DimensionalData, TimeSeriesAxis, List[SupportsFloat], List[datetime]], plot_title: Union[str, None] = None + self, + x_axis: Union[DimensionalData, TimeSeriesAxis, List[SupportsFloat], List[datetime]], + plot_title: Union[str, None] = None, ): self.figure = Figure() self.x_axis: Union[DimensionalData, TimeSeriesAxis] @@ -198,7 +200,6 @@ def __init__( self.is_finalized = False self.figure.layout["title"] = plot_title - def add_display_data( self, display_data: DisplayData, @@ -218,7 +219,6 @@ def add_display_data( self.subplots[subplot_index] = DimensionalSubplot() self.subplots[subplot_index].add_display_data(display_data, axis_name) # type: ignore[union-attr] - def finalize_plot(self): """Once all DisplayData objects have been added, generate plot and subplots.""" if not self.is_finalized: @@ -232,11 +232,11 @@ def finalize_plot(self): self.figure.layout["title_x"] = 0.5 xy_common_axis_format = { "mirror": True, - "linecolor":BLACK, - "linewidth":grid_line_width, - "zeroline":True, - "zerolinecolor":GREY, - "zerolinewidth":grid_line_width, + "linecolor": BLACK, + "linewidth": grid_line_width, + "zeroline": True, + "zerolinecolor": GREY, + "zerolinewidth": grid_line_width, } x_axis_label = f"{self.x_axis.name}" if isinstance(self.x_axis, DimensionalData): @@ -252,10 +252,10 @@ def finalize_plot(self): for display_data in axis.display_data_set: at_least_one_subplot = True y_values = koozie.convert( - display_data.data_values, - display_data.native_units, - axis.units, - ) + display_data.data_values, + display_data.native_units, + axis.units, + ) axis.range_min = min(min(y_values), axis.range_min) axis.range_max = max(max(y_values), axis.range_max) if display_data.x_axis is None: @@ -290,11 +290,7 @@ def finalize_plot(self): yaxis=f"y{y_axis_id}", xaxis=f"x{x_axis_id}", mode=display_data.line_properties.get_line_mode(), - visible=( - "legendonly" - if not display_data.is_visible - else True - ), + visible=("legendonly" if not display_data.is_visible else True), line={ "color": display_data.line_properties.color, "dash": display_data.line_properties.line_type, @@ -310,7 +306,7 @@ def finalize_plot(self): }, }, legendgroup=display_data.legend_group, - legendgrouptitle={"text":display_data.legend_group}, + legendgrouptitle={"text": display_data.legend_group}, ), ) is_base_y_axis = subplot_base_y_axis_id == y_axis_id @@ -322,10 +318,10 @@ def finalize_plot(self): "overlaying": (f"y{subplot_base_y_axis_id}" if not is_base_y_axis else None), "tickmode": "sync" if not is_base_y_axis else None, "autoshift": True if axis_number > 1 else None, - "showgrid":True, - "gridcolor":GREY, - "gridwidth":grid_line_width, - "range":axis.get_axis_range(axis.range_min, axis.range_max) + "showgrid": True, + "gridcolor": GREY, + "gridwidth": grid_line_width, + "range": axis.get_axis_range(axis.range_min, axis.range_max), } self.figure.layout[f"yaxis{y_axis_id}"].update(xy_common_axis_format) absolute_axis_index += 1 @@ -337,10 +333,10 @@ def finalize_plot(self): "domain": [0.0, 1.0], "matches": (f"x{number_of_subplots}" if subplot_number < number_of_subplots else None), "showticklabels": None if is_last_subplot else False, - "ticks":"outside", - "tickson":"boundaries", - "tickcolor":BLACK, - "tickwidth":grid_line_width, + "ticks": "outside", + "tickson": "boundaries", + "tickcolor": BLACK, + "tickwidth": grid_line_width, } self.figure.layout[f"xaxis{x_axis_id}"].update(xy_common_axis_format) else: diff --git a/dimes/timeseries.py b/dimes/timeseries.py index 83ac0bd..ee8bb29 100644 --- a/dimes/timeseries.py +++ b/dimes/timeseries.py @@ -16,4 +16,4 @@ class TimeSeriesPlot(DimensionalPlot): def __init__(self, time_values: list, plot_title: Union[str, None] = None): super().__init__(time_values, plot_title) self.add_time_series = self.add_display_data - self.time_values = self.x_axis.data_values \ No newline at end of file + self.time_values = self.x_axis.data_values diff --git a/test/test_timeseries.py b/test/test_timeseries.py index a63050c..b8ffe20 100644 --- a/test/test_timeseries.py +++ b/test/test_timeseries.py @@ -65,24 +65,15 @@ def test_multi_plot(): plot = TimeSeriesPlot([1, 2, 3, 4, 5]) # Time series & axis names explicit, subplot default to 1 plot.add_time_series( - TimeSeriesData( - [x**2 for x in plot.time_values], name="Power", native_units="hp", display_units="W" - ), + TimeSeriesData([x**2 for x in plot.time_values], name="Power", native_units="hp", display_units="W"), axis_name="Power or Capacity", ) # Time series name explicit, axis automatically determined by dimensionality, subplot default to 1 plot.add_time_series( - TimeSeriesData( - [x * 10 for x in plot.time_values], - name="Capacity", - native_units="kBtu/h", - is_visible=False - ) + TimeSeriesData([x * 10 for x in plot.time_values], name="Capacity", native_units="kBtu/h", is_visible=False) ) # Time series and axis will get name from dimensionality, subplot default to 1, new axis for new dimension on existing subplot - plot.add_time_series( - TimeSeriesData([x for x in plot.time_values], native_units="ft", display_units="cm") - ) + plot.add_time_series(TimeSeriesData([x for x in plot.time_values], native_units="ft", display_units="cm")) # Time series & axis names and subplot number are all explicit plot.add_time_series( TimeSeriesData([x**3 for x in plot.time_values], name="Number of Apples"), @@ -140,17 +131,18 @@ def test_missing_marker_symbol(): def test_legend_group(): """Test legend group and legend group title.""" plot = TimeSeriesPlot([1, 2, 3, 4, 5]) - city_data = {"City_A":{2000:[x**2 for x in plot.time_values],2010:[x**3 for x in plot.time_values]}, - "City_B":{2000:[x**2.5 for x in plot.time_values],2010:[x**3.5 for x in plot.time_values]}} + city_data = { + "City_A": {2000: [x**2 for x in plot.time_values], 2010: [x**3 for x in plot.time_values]}, + "City_B": {2000: [x**2.5 for x in plot.time_values], 2010: [x**3.5 for x in plot.time_values]}, + } for city, year_data in city_data.items(): for year, data in year_data.items(): plot.add_time_series( TimeSeriesData( data, - name = city, - legend_group = str(year), + name=city, + legend_group=str(year), ), - ) plot.write_html_plot(Path(TESTING_DIRECTORY, "legend_group.html")) @@ -162,14 +154,10 @@ def test_is_visible(): TimeSeriesData( [x**2 for x in plot.time_values], line_properties=LineProperties( - color="blue", - marker_size=5, - marker_line_color="black", - marker_fill_color="white", - marker_line_width=1.5 + color="blue", marker_size=5, marker_line_color="black", marker_fill_color="white", marker_line_width=1.5 ), - is_visible = True, - name = "Visible" + is_visible=True, + name="Visible", ) ) plot.add_time_series( @@ -181,8 +169,8 @@ def test_is_visible(): marker_line_color="black", marker_fill_color="white", ), - is_visible = False, - name = "Legend Only" + is_visible=False, + name="Legend Only", ) ) - plot.write_html_plot(Path(TESTING_DIRECTORY, "is_visible.html")) \ No newline at end of file + plot.write_html_plot(Path(TESTING_DIRECTORY, "is_visible.html")) From c926a748c083861a57efc072a859e658423ab4ee Mon Sep 17 00:00:00 2001 From: Neal Kruis Date: Thu, 9 May 2024 16:29:21 -0600 Subject: [PATCH 28/29] Rename plot_title ('plot' is redundant). --- dimes/common.py | 4 ++-- dimes/timeseries.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dimes/common.py b/dimes/common.py index 283cdcd..f50668e 100644 --- a/dimes/common.py +++ b/dimes/common.py @@ -185,7 +185,7 @@ class DimensionalPlot: def __init__( self, x_axis: Union[DimensionalData, TimeSeriesAxis, List[SupportsFloat], List[datetime]], - plot_title: Union[str, None] = None, + title: Union[str, None] = None, ): self.figure = Figure() self.x_axis: Union[DimensionalData, TimeSeriesAxis] @@ -198,7 +198,7 @@ def __init__( self.x_axis = x_axis self.subplots: List[Union[DimensionalSubplot, None]] = [None] self.is_finalized = False - self.figure.layout["title"] = plot_title + self.figure.layout["title"] = title def add_display_data( self, diff --git a/dimes/timeseries.py b/dimes/timeseries.py index ee8bb29..8ab4b23 100644 --- a/dimes/timeseries.py +++ b/dimes/timeseries.py @@ -13,7 +13,7 @@ class TimeSeriesData(DisplayData): class TimeSeriesPlot(DimensionalPlot): """Time series plot.""" - def __init__(self, time_values: list, plot_title: Union[str, None] = None): - super().__init__(time_values, plot_title) + def __init__(self, time_values: list, title: Union[str, None] = None): + super().__init__(time_values, title) self.add_time_series = self.add_display_data self.time_values = self.x_axis.data_values From 976e9a7a5d89ddec4d39bd802b5fb28f2caf0b17 Mon Sep 17 00:00:00 2001 From: Neal Kruis Date: Fri, 10 May 2024 11:58:32 -0600 Subject: [PATCH 29/29] Improve get_axis_range algorithm. Other minor clean up. --- dimes/common.py | 37 +++++++++++++++++++++++++++---------- test/test_timeseries.py | 10 ++++++++++ 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/dimes/common.py b/dimes/common.py index f50668e..de5ed9d 100644 --- a/dimes/common.py +++ b/dimes/common.py @@ -4,6 +4,8 @@ from dataclasses import dataclass import warnings from datetime import datetime +import math +import bisect from plotly.graph_objects import Figure, Scatter # type: ignore @@ -110,6 +112,7 @@ def __init__( is_visible: bool = True, legend_group: Union[str, None] = None, x_axis: Union[DimensionalData, TimeSeriesAxis, List[SupportsFloat], List[datetime], None] = None, + y_axis_min: Union[SupportsFloat, None] = 0.0, ): super().__init__(data_values, name, native_units, display_units) self.x_axis: Union[DimensionalData, TimeSeriesAxis, None] @@ -120,6 +123,7 @@ def __init__( self.x_axis = DimensionalData(x_axis) # type: ignore[arg-type] else: self.x_axis = x_axis + self.y_axis_min = y_axis_min self.line_properties = line_properties self.is_visible = is_visible self.legend_group = legend_group @@ -142,9 +146,19 @@ def get_axis_label(self) -> str: @staticmethod def get_axis_range(value_min, value_max): - range_buffer = 0.1 - range_values = value_max - value_min - return [value_min - range_values * range_buffer, value_max + range_values * range_buffer] + max_ticks = 6 + tick_scale_options = [1, 2, 5, 10] + + value_range = value_max - value_min + min_tick_size = value_range / max_ticks + magnitude = 10 ** math.floor(math.log(min_tick_size, 10)) + residual = min_tick_size / magnitude + tick_size = ( + tick_scale_options[bisect.bisect_right(tick_scale_options, residual)] if residual < 10 else 10 + ) * magnitude + range_min = math.floor(value_min / tick_size) * tick_size + range_max = math.ceil(value_max / tick_size) * tick_size + return [range_min, range_max] class DimensionalSubplot: @@ -234,9 +248,7 @@ def finalize_plot(self): "mirror": True, "linecolor": BLACK, "linewidth": grid_line_width, - "zeroline": True, - "zerolinecolor": GREY, - "zerolinewidth": grid_line_width, + "zeroline": False, } x_axis_label = f"{self.x_axis.name}" if isinstance(self.x_axis, DimensionalData): @@ -257,6 +269,11 @@ def finalize_plot(self): axis.units, ) axis.range_min = min(min(y_values), axis.range_min) + if display_data.y_axis_min is not None: + data_y_axis_min = koozie.convert( + display_data.y_axis_min, display_data.native_units, axis.units + ) + axis.range_min = min(data_y_axis_min, axis.range_min) axis.range_max = max(max(y_values), axis.range_max) if display_data.x_axis is None: if isinstance(display_data.x_axis, DimensionalData): @@ -333,10 +350,10 @@ def finalize_plot(self): "domain": [0.0, 1.0], "matches": (f"x{number_of_subplots}" if subplot_number < number_of_subplots else None), "showticklabels": None if is_last_subplot else False, - "ticks": "outside", - "tickson": "boundaries", - "tickcolor": BLACK, - "tickwidth": grid_line_width, + "ticks": None if not is_last_subplot else "outside", + "tickson": None if not is_last_subplot else "boundaries", + "tickcolor": None if not is_last_subplot else BLACK, + "tickwidth": None if not is_last_subplot else grid_line_width, } self.figure.layout[f"xaxis{x_axis_id}"].update(xy_common_axis_format) else: diff --git a/test/test_timeseries.py b/test/test_timeseries.py index b8ffe20..35b2c70 100644 --- a/test/test_timeseries.py +++ b/test/test_timeseries.py @@ -3,6 +3,7 @@ from pathlib import Path import pytest from dimes import TimeSeriesPlot, TimeSeriesData, LineProperties, LinesOnly +from dimes.common import DimensionalAxis TESTING_DIRECTORY = Path("test_outputs") TESTING_DIRECTORY.mkdir(exist_ok=True) @@ -174,3 +175,12 @@ def test_is_visible(): ) ) plot.write_html_plot(Path(TESTING_DIRECTORY, "is_visible.html")) + + +def test_get_axis_range(): + checks = [([0, 2], [0, 2]), ([0, 23.5], [0, 25])] + + for check in checks: + min_value = check[0][0] + max_value = check[0][1] + assert DimensionalAxis.get_axis_range(min_value, max_value) == check[1]