From 777ffad143a7fe250fc348bd124a6f38d303c76f Mon Sep 17 00:00:00 2001 From: jvdd Date: Sun, 25 Aug 2024 14:24:13 +0200 Subject: [PATCH 01/10] fix: check if update_data contains update before batch_update --- .../figurewidget_resampler.py | 31 ++++++++++--------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/plotly_resampler/figure_resampler/figurewidget_resampler.py b/plotly_resampler/figure_resampler/figurewidget_resampler.py index ef8d871c..fcff9227 100644 --- a/plotly_resampler/figure_resampler/figurewidget_resampler.py +++ b/plotly_resampler/figure_resampler/figurewidget_resampler.py @@ -259,22 +259,23 @@ def _update_spike_ranges(self, layout, *showspikes, force_update=False): self._relayout_hist.append(["showspikes-update", len(update_data) - 1]) self._relayout_hist.append("-" * 30) - with self.batch_update(): - # First update the layout (first item of update_data) - if not force_update: - self.layout.update(self._parse_relayout(update_data[0])) - - # Also: Remove the showspikes from the layout, otherwise the autorange - # will not work as intended (it will not be triggered again) - # Note: this removal causes a second trigger of this method - # which will go in the "else" part below. - for xaxis_str in self._xaxis_list: - self.layout[xaxis_str].pop("showspikes") + if not self._is_no_update(update_data): + with self.batch_update(): + # First update the layout (first item of update_data) + if not force_update: + self.layout.update(self._parse_relayout(update_data[0])) + + # Also: Remove the showspikes from the layout, otherwise the autorange + # will not work as intended (it will not be triggered again) + # Note: this removal causes a second trigger of this method + # which will go in the "else" part below. + for xaxis_str in self._xaxis_list: + self.layout[xaxis_str].pop("showspikes") - # Then, update the data - for updated_trace in update_data[1:]: - trace_idx = updated_trace.pop("index") - self.data[trace_idx].update(updated_trace) + # Then, update the data + for updated_trace in update_data[1:]: + trace_idx = updated_trace.pop("index") + self.data[trace_idx].update(updated_trace) elif self._print_verbose: self._relayout_hist.append(["showspikes", "initial call or showspikes"]) self._relayout_hist.append("-" * 40) From 4396f0110c95bec6eccd57b467900e9f2e49cebc Mon Sep 17 00:00:00 2001 From: jvdd Date: Sun, 25 Aug 2024 15:02:11 +0200 Subject: [PATCH 02/10] add test + avoid same error when verbose=True --- .../figure_resampler/figurewidget_resampler.py | 12 +++++++----- tests/test_figurewidget_resampler.py | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/plotly_resampler/figure_resampler/figurewidget_resampler.py b/plotly_resampler/figure_resampler/figurewidget_resampler.py index fcff9227..a8ed643d 100644 --- a/plotly_resampler/figure_resampler/figurewidget_resampler.py +++ b/plotly_resampler/figure_resampler/figurewidget_resampler.py @@ -254,12 +254,14 @@ def _update_spike_ranges(self, layout, *showspikes, force_update=False): # Construct the update data update_data = self._construct_update_data(relayout_dict) - if self._print_verbose: - self._relayout_hist.append(layout) - self._relayout_hist.append(["showspikes-update", len(update_data) - 1]) - self._relayout_hist.append("-" * 30) - if not self._is_no_update(update_data): + if self._print_verbose: + self._relayout_hist.append(layout) + self._relayout_hist.append( + ["showspikes-update", len(update_data) - 1] + ) + self._relayout_hist.append("-" * 30) + with self.batch_update(): # First update the layout (first item of update_data) if not force_update: diff --git a/tests/test_figurewidget_resampler.py b/tests/test_figurewidget_resampler.py index 03f73bd4..ac341a33 100644 --- a/tests/test_figurewidget_resampler.py +++ b/tests/test_figurewidget_resampler.py @@ -142,6 +142,23 @@ def test_add_not_a_hf_trace(float_series): ) +def test_add_not_scatter(): + fw_fig = FigureWidgetResampler( + go.Figure(go.Bar(x=[1, 2, 3, 4], y=[1, 2, 3, 4])), verbose=True + ) + + # we do not want to have an relayout update + assert len(fw_fig._relayout_hist) == 0 + + # zoom in on both traces + fw_fig.layout.update( + {"xaxis": {"range": [0, 1]}}, + overwrite=False, + ) + # reset axes + fw_fig.reset_axes() + + def test_box_histogram(float_series): base_fig = make_subplots( rows=2, From 277ce367d55fdd2b8fa923e5703bdb5a32842f19 Mon Sep 17 00:00:00 2001 From: jvdd Date: Mon, 26 Aug 2024 21:40:54 +0200 Subject: [PATCH 03/10] :broom: create _hf_data_container if correct trace type --- .../figure_resampler_interface.py | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/plotly_resampler/figure_resampler/figure_resampler_interface.py b/plotly_resampler/figure_resampler/figure_resampler_interface.py index 25ce4009..5e3aee8f 100644 --- a/plotly_resampler/figure_resampler/figure_resampler_interface.py +++ b/plotly_resampler/figure_resampler/figure_resampler_interface.py @@ -44,6 +44,7 @@ class AbstractFigureAggregator(BaseFigure, ABC): """Abstract interface for data aggregation functionality for plotly figures.""" _high_frequency_traces = ["scatter", "scattergl"] + _stats_traces = ["histogram"] def __init__( self, @@ -980,21 +981,21 @@ def add_trace( uuid_str = str(uuid4()) if trace.uid is None else trace.uid trace.uid = uuid_str - # construct the hf_data_container - # TODO in future version -> maybe regex on kwargs which start with `hf_` - dc = self._parse_get_trace_props( - trace, - hf_x, - hf_y, - hf_text, - hf_hovertext, - hf_marker_size, - hf_marker_color, - ) - # These traces will determine the autoscale its RANGE! # -> so also store when `limit_to_view` is set. if trace["type"].lower() in self._high_frequency_traces: + # construct the hf_data_container + # TODO in future version -> maybe regex on kwargs which start with `hf_` + dc = self._parse_get_trace_props( + trace, + hf_x, + hf_y, + hf_text, + hf_hovertext, + hf_marker_size, + hf_marker_color, + ) + n_samples = len(dc.x) if n_samples > max_out_s or limit_to_view: self._print( From 43d01a87626e487ca1ec2f0834efcb0bbae982af Mon Sep 17 00:00:00 2001 From: jvdd Date: Tue, 27 Aug 2024 12:58:41 +0200 Subject: [PATCH 04/10] :pray: python 3.7 not supported on Apple Silicon --- .github/workflows/test.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7f2a88ba..28b6bdc5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,6 +26,12 @@ jobs: matrix: os: ['windows-latest', 'macOS-latest', 'ubuntu-latest'] python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12'] + exclude: # Python < 3.8 is not supported on Apple Silicon ARM64 + - os: macOS-latest + python-version: '3.7' + include: # So run on older version on Intel CPU + - os: macOS-13 + python-version: '3.7' defaults: run: shell: bash From cc3921e57f03d50df6a58b76d2cc640214cd9339 Mon Sep 17 00:00:00 2001 From: Jeroen Van Der Donckt <18898740+jvdd@users.noreply.github.com> Date: Tue, 27 Aug 2024 12:59:49 +0200 Subject: [PATCH 05/10] remove WIP --- plotly_resampler/figure_resampler/figure_resampler_interface.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plotly_resampler/figure_resampler/figure_resampler_interface.py b/plotly_resampler/figure_resampler/figure_resampler_interface.py index 5e3aee8f..b296cfb2 100644 --- a/plotly_resampler/figure_resampler/figure_resampler_interface.py +++ b/plotly_resampler/figure_resampler/figure_resampler_interface.py @@ -44,7 +44,6 @@ class AbstractFigureAggregator(BaseFigure, ABC): """Abstract interface for data aggregation functionality for plotly figures.""" _high_frequency_traces = ["scatter", "scattergl"] - _stats_traces = ["histogram"] def __init__( self, From ef81227e256143ba535c6ab9af9e1ccb430612c3 Mon Sep 17 00:00:00 2001 From: jvdd Date: Thu, 29 Aug 2024 10:10:54 +0200 Subject: [PATCH 06/10] :pen: more verbose asserts --- tests/fr_selenium.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/fr_selenium.py b/tests/fr_selenium.py index 659d7581..859765b4 100644 --- a/tests/fr_selenium.py +++ b/tests/fr_selenium.py @@ -146,7 +146,7 @@ def browser_independent_single_callback_request_assert( elif "chrome" in browser_name: # for some, yet unknown reason, chrome does not seem to capture the # second front-end request. - assert len(requests) == 1 + assert len(requests) == 1, f"len(requests) = {len(requests)}" fetch_data_request = requests[0] else: raise ValueError(f"invalid browser name {browser_name}") From 6de51be2f4a0309c82bbf38f5b826239080e8157 Mon Sep 17 00:00:00 2001 From: jvdd Date: Thu, 29 Aug 2024 10:11:05 +0200 Subject: [PATCH 07/10] :pen: more verbose asserts --- tests/fr_selenium.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/fr_selenium.py b/tests/fr_selenium.py index 859765b4..9db71c4b 100644 --- a/tests/fr_selenium.py +++ b/tests/fr_selenium.py @@ -136,7 +136,7 @@ def browser_independent_single_callback_request_assert( # There are 2 requests which are send # 1. first: changed-layout to server -> new data to back-end request # 2. the front-end relayout request - assert len(requests) >= 1 + assert len(requests) >= 1, f"len(requests) = {len(requests)}" if len(requests) == 2: fetch_data_request, relayout_request = requests # RequestParser.assert_front_end_relayout_request(relayout_request) From d61017ab783ebcb43756a35d9d2d8575811ddc30 Mon Sep 17 00:00:00 2001 From: jeroen Date: Fri, 30 Aug 2024 08:54:18 +0200 Subject: [PATCH 08/10] :pray: more sleep time --- tests/test_figure_resampler_selenium.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_figure_resampler_selenium.py b/tests/test_figure_resampler_selenium.py index ccdf9573..9930e8b4 100644 --- a/tests/test_figure_resampler_selenium.py +++ b/tests/test_figure_resampler_selenium.py @@ -611,7 +611,7 @@ def test_multi_trace_go_figure(driver, multi_trace_go_figure): # Note: as there is only 1 hf-scatter-trace, the reset axes command will only # update a single trace - fr.clear_requests(sleep_time_s=1) + fr.clear_requests(sleep_time_s=3) fr.reset_axes() time.sleep(3) RequestParser.browser_independent_single_callback_request_assert( @@ -629,7 +629,7 @@ def test_multi_trace_go_figure(driver, multi_trace_go_figure): fr.clear_requests(sleep_time_s=3) # First, apply some horizontal based zooms - fr.clear_requests(sleep_time_s=1) + fr.clear_requests(sleep_time_s=3) fr.drag_and_zoom("xy", x0=0.1, x1=0.2, y0=0.5, y1=0.5) time.sleep(3) # As all axes are shared, we expect at least 3 updated From b2f9975c8db34056642c28ef1f26da8a88a340b9 Mon Sep 17 00:00:00 2001 From: jeroen Date: Fri, 30 Aug 2024 09:07:23 +0200 Subject: [PATCH 09/10] :pray: --- tests/test_figure_resampler_selenium.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_figure_resampler_selenium.py b/tests/test_figure_resampler_selenium.py index 9930e8b4..ebc11c34 100644 --- a/tests/test_figure_resampler_selenium.py +++ b/tests/test_figure_resampler_selenium.py @@ -625,6 +625,7 @@ def test_multi_trace_go_figure(driver, multi_trace_go_figure): n_updated_traces=30, ) + time.sleep(5) fr.drag_and_zoom("xy", x0=0.1, x1=0.3, y0=0.6, y1=0.9) fr.clear_requests(sleep_time_s=3) From ebf6aa6204c90846adfe220515d1ee87cf16fc8b Mon Sep 17 00:00:00 2001 From: jeroen Date: Fri, 30 Aug 2024 09:29:07 +0200 Subject: [PATCH 10/10] :raised_hands: --- tests/test_figure_resampler_selenium.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/test_figure_resampler_selenium.py b/tests/test_figure_resampler_selenium.py index ebc11c34..115d68a3 100644 --- a/tests/test_figure_resampler_selenium.py +++ b/tests/test_figure_resampler_selenium.py @@ -611,7 +611,7 @@ def test_multi_trace_go_figure(driver, multi_trace_go_figure): # Note: as there is only 1 hf-scatter-trace, the reset axes command will only # update a single trace - fr.clear_requests(sleep_time_s=3) + fr.clear_requests(sleep_time_s=1) fr.reset_axes() time.sleep(3) RequestParser.browser_independent_single_callback_request_assert( @@ -625,12 +625,11 @@ def test_multi_trace_go_figure(driver, multi_trace_go_figure): n_updated_traces=30, ) - time.sleep(5) - fr.drag_and_zoom("xy", x0=0.1, x1=0.3, y0=0.6, y1=0.9) + fr.drag_and_zoom("xy", x0=0.1, x1=0.3, y0=0.3, y1=0.5) fr.clear_requests(sleep_time_s=3) # First, apply some horizontal based zooms - fr.clear_requests(sleep_time_s=3) + fr.clear_requests(sleep_time_s=1) fr.drag_and_zoom("xy", x0=0.1, x1=0.2, y0=0.5, y1=0.5) time.sleep(3) # As all axes are shared, we expect at least 3 updated