From 8a8919d4cb50aab988a480b017bdb50a0a104387 Mon Sep 17 00:00:00 2001 From: prafulgulani555 <59774145+prafulgulani555@users.noreply.github.com> Date: Fri, 7 Feb 2025 21:20:28 +0530 Subject: [PATCH 1/4] Blocked set_clim() callbacks to prevent inconsistent state (#29522) --- lib/matplotlib/colorizer.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/matplotlib/colorizer.py b/lib/matplotlib/colorizer.py index 4aebe7d0f5dc..db98edd02702 100644 --- a/lib/matplotlib/colorizer.py +++ b/lib/matplotlib/colorizer.py @@ -261,16 +261,24 @@ def set_clim(self, vmin=None, vmax=None): .. ACCEPTS: (vmin: float, vmax: float) """ # If the norm's limits are updated self.changed() will be called - # through the callbacks attached to the norm + # through the callbacks attached to the norm, this causes an inconsistent + # state, to prevent this blocked context manager is used if vmax is None: try: vmin, vmax = vmin except (TypeError, ValueError): pass - if vmin is not None: - self.norm.vmin = colors._sanitize_extrema(vmin) - if vmax is not None: - self.norm.vmax = colors._sanitize_extrema(vmax) + + # Blocked context manager prevents callbacks from being triggered + # until both vmin and vmax are updated + with self.norm.callbacks.blocked(signal='changed'): + if vmin is not None: + self.norm.vmin = colors._sanitize_extrema(vmin) + if vmax is not None: + self.norm.vmax = colors._sanitize_extrema(vmax) + + # self.changed() will now emit a update signal after both the limits are set + self.changed() def get_clim(self): """ From 0c40fe14596b46468b625ca4e49343d32c5901ee Mon Sep 17 00:00:00 2001 From: prafulgulani555 <59774145+prafulgulani555@users.noreply.github.com> Date: Tue, 11 Feb 2025 22:35:42 +0530 Subject: [PATCH 2/4] added test for set_clim() callback count --- lib/matplotlib/colorizer.py | 7 +++++-- lib/matplotlib/tests/test_colorbar.py | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/colorizer.py b/lib/matplotlib/colorizer.py index db98edd02702..412b49251248 100644 --- a/lib/matplotlib/colorizer.py +++ b/lib/matplotlib/colorizer.py @@ -269,6 +269,8 @@ def set_clim(self, vmin=None, vmax=None): except (TypeError, ValueError): pass + orig_vmin_vmax = self.norm.vmin, self.norm.vmax + # Blocked context manager prevents callbacks from being triggered # until both vmin and vmax are updated with self.norm.callbacks.blocked(signal='changed'): @@ -277,8 +279,9 @@ def set_clim(self, vmin=None, vmax=None): if vmax is not None: self.norm.vmax = colors._sanitize_extrema(vmax) - # self.changed() will now emit a update signal after both the limits are set - self.changed() + # self.changed() will now emit a update signal if the limits are changed + if orig_vmin_vmax != (self.norm.vmin, self.norm.vmax): + self.changed() def get_clim(self): """ diff --git a/lib/matplotlib/tests/test_colorbar.py b/lib/matplotlib/tests/test_colorbar.py index b53f50385aaa..a7b65c839af2 100644 --- a/lib/matplotlib/tests/test_colorbar.py +++ b/lib/matplotlib/tests/test_colorbar.py @@ -18,6 +18,8 @@ from matplotlib.ticker import FixedLocator, LogFormatter, StrMethodFormatter from matplotlib.testing.decorators import check_figures_equal +import unittest.mock + def _get_cmap_norms(): """ @@ -660,6 +662,26 @@ def test_colorbar_scale_reset(): assert pcm.norm.vmax == z.max() +def test_set_clim_emits_single_callback(): + data = np.array([[1, 2], [3, 4]]) + fig, ax = plt.subplots() + image = ax.imshow(data, cmap='viridis') + + callback = unittest.mock.Mock() + image.norm.callbacks.connect('changed', callback) + + # Initial callback count should be zero + assert callback.call_count == 0 + + # Call set_clim() to update the limits + image.set_clim(1, 5) + + # Assert that only one "changed" callback is sent after calling set_clim() + callback.assert_called_once() + + plt.close(fig) + + def test_colorbar_get_ticks_2(): plt.rcParams['_internal.classic_mode'] = False fig, ax = plt.subplots() From bf5370d8d97f5547ef338bd69f099cde02065a80 Mon Sep 17 00:00:00 2001 From: prafulgulani555 <59774145+prafulgulani555@users.noreply.github.com> Date: Wed, 12 Feb 2025 21:51:20 +0530 Subject: [PATCH 3/4] Relocated test_set_clim_emits_single_callback to test_colors.py --- lib/matplotlib/colorizer.py | 4 ++-- lib/matplotlib/tests/test_colorbar.py | 22 ---------------------- lib/matplotlib/tests/test_colors.py | 20 ++++++++++++++++++++ 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/lib/matplotlib/colorizer.py b/lib/matplotlib/colorizer.py index 412b49251248..b4223f389804 100644 --- a/lib/matplotlib/colorizer.py +++ b/lib/matplotlib/colorizer.py @@ -279,9 +279,9 @@ def set_clim(self, vmin=None, vmax=None): if vmax is not None: self.norm.vmax = colors._sanitize_extrema(vmax) - # self.changed() will now emit a update signal if the limits are changed + # emit a update signal if the limits are changed if orig_vmin_vmax != (self.norm.vmin, self.norm.vmax): - self.changed() + self.norm.callbacks.process('changed') def get_clim(self): """ diff --git a/lib/matplotlib/tests/test_colorbar.py b/lib/matplotlib/tests/test_colorbar.py index a7b65c839af2..b53f50385aaa 100644 --- a/lib/matplotlib/tests/test_colorbar.py +++ b/lib/matplotlib/tests/test_colorbar.py @@ -18,8 +18,6 @@ from matplotlib.ticker import FixedLocator, LogFormatter, StrMethodFormatter from matplotlib.testing.decorators import check_figures_equal -import unittest.mock - def _get_cmap_norms(): """ @@ -662,26 +660,6 @@ def test_colorbar_scale_reset(): assert pcm.norm.vmax == z.max() -def test_set_clim_emits_single_callback(): - data = np.array([[1, 2], [3, 4]]) - fig, ax = plt.subplots() - image = ax.imshow(data, cmap='viridis') - - callback = unittest.mock.Mock() - image.norm.callbacks.connect('changed', callback) - - # Initial callback count should be zero - assert callback.call_count == 0 - - # Call set_clim() to update the limits - image.set_clim(1, 5) - - # Assert that only one "changed" callback is sent after calling set_clim() - callback.assert_called_once() - - plt.close(fig) - - def test_colorbar_get_ticks_2(): plt.rcParams['_internal.classic_mode'] = False fig, ax = plt.subplots() diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index 24c4ebba4920..7481d617e154 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -1605,6 +1605,26 @@ def test_norm_deepcopy(): assert norm2.vmin == norm.vmin +def test_set_clim_emits_single_callback(): + data = np.array([[1, 2], [3, 4]]) + fig, ax = plt.subplots() + image = ax.imshow(data, cmap='viridis') + + callback = unittest.mock.Mock() + image.norm.callbacks.connect('changed', callback) + + # Initial callback count should be zero + assert callback.call_count == 0 + + # Call set_clim() to update the limits + image.set_clim(1, 5) + + # Assert that only one "changed" callback is sent after calling set_clim() + callback.assert_called_once() + + plt.close(fig) + + def test_norm_callback(): increment = unittest.mock.Mock(return_value=None) From 49a0ab2db7ae0e1dc9b481fd07c5ed28e506b4f3 Mon Sep 17 00:00:00 2001 From: prafulgulani555 <59774145+prafulgulani555@users.noreply.github.com> Date: Sat, 22 Feb 2025 15:46:30 +0530 Subject: [PATCH 4/4] test case corrections --- lib/matplotlib/tests/test_colors.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index 7481d617e154..8d0f3467f045 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -1613,8 +1613,7 @@ def test_set_clim_emits_single_callback(): callback = unittest.mock.Mock() image.norm.callbacks.connect('changed', callback) - # Initial callback count should be zero - assert callback.call_count == 0 + callback.assert_not_called() # Call set_clim() to update the limits image.set_clim(1, 5) @@ -1622,8 +1621,6 @@ def test_set_clim_emits_single_callback(): # Assert that only one "changed" callback is sent after calling set_clim() callback.assert_called_once() - plt.close(fig) - def test_norm_callback(): increment = unittest.mock.Mock(return_value=None)
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: