From 0d8bfed47fc0e4facfc45de45ce3344fb3a07548 Mon Sep 17 00:00:00 2001 From: Mara Date: Fri, 31 Mar 2023 20:04:07 +0200 Subject: [PATCH 1/8] Fix trailing whitespace --- lib/matplotlib/axes/_axes.py | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 6a3889cd05f6..4e6b397542fe 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -4293,6 +4293,8 @@ def _parse_scatter_color_args(c, edgecolors, kwargs, xsize, Argument precedence for facecolors: + - kwargs['facecolor'] if 'none' + - kwargs['facecolors'] if 'none' - c (if not None) - kwargs['facecolor'] - kwargs['facecolors'] @@ -4304,6 +4306,7 @@ def _parse_scatter_color_args(c, edgecolors, kwargs, xsize, - kwargs['edgecolor'] - edgecolors (is an explicit kw argument in scatter()) - kwargs['color'] (==kwcolor) + - c (if not None) - 'face' if not in classic mode else None Parameters @@ -4337,7 +4340,7 @@ def _parse_scatter_color_args(c, edgecolors, kwargs, xsize, colors : array(N, 4) or None The facecolors as RGBA values, or *None* if a colormap is used. edgecolors - The edgecolor. + The edgecolor, or *None* if a colormap is used. """ facecolors = kwargs.pop('facecolors', None) @@ -4364,7 +4367,14 @@ def _parse_scatter_color_args(c, edgecolors, kwargs, xsize, if facecolors is None: facecolors = kwcolor - if edgecolors is None and not mpl.rcParams['_internal.classic_mode']: + edge_from_c = False + if edgecolors is None and c is not None: + edge_from_c = True + + facecolors_none = type(facecolors) == str and facecolors == 'none' + if edgecolors is None and not mpl.rcParams['_internal.classic_mode']\ + and (not edge_from_c or not facecolors_none): + edgecolors = mpl.rcParams['scatter.edgecolors'] c_was_none = c is None @@ -4415,6 +4425,8 @@ def invalid_shape_exception(csize, xsize): if not c_is_mapped: try: # Is 'c' acceptable as PathCollection facecolors? colors = mcolors.to_rgba_array(c) + if edge_from_c and facecolors is not None: + edgecolors = mcolors.to_rgba_array(c) except (TypeError, ValueError) as err: if "RGBA values should be within 0-1 range" in str(err): raise @@ -4433,6 +4445,8 @@ def invalid_shape_exception(csize, xsize): raise invalid_shape_exception(len(colors), xsize) else: colors = None # use cmap, norm after collection is created + if cbook._str_lower_equal(facecolors, 'none'): + colors = facecolors return c, colors, edgecolors @_preprocess_data(replace_names=["x", "y", "s", "linewidths", @@ -4581,10 +4595,22 @@ def scatter(self, x, y, s=None, c=None, marker=None, cmap=None, norm=None, c, edgecolors, kwargs, x.size, get_next_color_func=self._get_patches_for_fill.get_next_color) - if plotnonfinite and colors is None: + if cmap and norm and not orig_edgecolor: + # override edgecolor based on cmap and norm presence + edgecolors = 'face' + + if plotnonfinite and (colors is None and edgecolors is None): + c = np.ma.masked_invalid(c) + x, y, s, linewidths = \ + cbook._combine_masks(x, y, s, linewidths) + elif plotnonfinite and colors is None: c = np.ma.masked_invalid(c) x, y, s, edgecolors, linewidths = \ cbook._combine_masks(x, y, s, edgecolors, linewidths) + elif plotnonfinite and edgecolors is None: + c = np.ma.masked_invalid(c) + x, y, s, colors, linewidths = \ + cbook._combine_masks(x, y, s, colors, linewidths) else: x, y, s, c, colors, edgecolors, linewidths = \ cbook._combine_masks( @@ -4658,7 +4684,8 @@ def scatter(self, x, y, s=None, c=None, marker=None, cmap=None, norm=None, alpha=alpha, ) collection.set_transform(mtransforms.IdentityTransform()) - if colors is None: + if (colors is None or edgecolors is None) and not mcolors.is_color_like(c)\ + and not type(c) == np.ndarray: collection.set_array(c) collection.set_cmap(cmap) collection.set_norm(norm) From a0662dfa04ba082cbc23e021cd46c0fc4ad1af06 Mon Sep 17 00:00:00 2001 From: Mara Date: Fri, 31 Mar 2023 20:04:40 +0200 Subject: [PATCH 2/8] Update scatter test to work with new behavior --- lib/matplotlib/tests/test_axes.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 49bdc98abcb4..9193ec8322c9 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -2760,7 +2760,10 @@ def get_next_color(): _, _, result_edgecolors = \ mpl.axes.Axes._parse_scatter_color_args( c, edgecolors, kwargs, xsize=2, get_next_color_func=get_next_color) - assert result_edgecolors == expected_edgecolors + if type(expected_edgecolors) == np.ndarray: + assert_allclose(result_edgecolors, expected_edgecolors) + else: + assert result_edgecolors == expected_edgecolors def test_parse_scatter_color_args_error(): From d065990da92b96de82389fdb6ce9f562794df966 Mon Sep 17 00:00:00 2001 From: Mara Date: Fri, 31 Mar 2023 20:04:56 +0200 Subject: [PATCH 3/8] Add tests --- lib/matplotlib/tests/test_axes.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 9193ec8322c9..525854583d47 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -2750,6 +2750,8 @@ def get_next_color(): (dict(c='b', edgecolor='r', edgecolors='g'), 'r'), (dict(color='r'), 'r'), (dict(color='r', edgecolor='g'), 'g'), + (dict(facecolors='none'), None), + (dict(c='b', facecolors='none'), np.array([[0, 0, 1, 1]])) ]) def test_parse_scatter_color_args_edgecolors(kwargs, expected_edgecolors): def get_next_color(): From eb97647186927016b9364d93a6d76fc9c5238e7c Mon Sep 17 00:00:00 2001 From: ewpardijs Date: Fri, 31 Mar 2023 21:56:43 +0200 Subject: [PATCH 4/8] Changed typechecks to isinstance, line-continuations to brackets --- lib/matplotlib/axes/_axes.py | 10 +++++----- lib/matplotlib/tests/test_axes.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 4e6b397542fe..50fd2ca2e761 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -4371,9 +4371,9 @@ def _parse_scatter_color_args(c, edgecolors, kwargs, xsize, if edgecolors is None and c is not None: edge_from_c = True - facecolors_none = type(facecolors) == str and facecolors == 'none' - if edgecolors is None and not mpl.rcParams['_internal.classic_mode']\ - and (not edge_from_c or not facecolors_none): + facecolors_none = isinstance(facecolors, str) and facecolors == 'none' + if (edgecolors is None and not mpl.rcParams['_internal.classic_mode'] + and (not edge_from_c or not facecolors_none)): edgecolors = mpl.rcParams['scatter.edgecolors'] @@ -4684,8 +4684,8 @@ def scatter(self, x, y, s=None, c=None, marker=None, cmap=None, norm=None, alpha=alpha, ) collection.set_transform(mtransforms.IdentityTransform()) - if (colors is None or edgecolors is None) and not mcolors.is_color_like(c)\ - and not type(c) == np.ndarray: + if ((colors is None or edgecolors is None) and not mcolors.is_color_like(c) + and not type(c) == np.ndarray): collection.set_array(c) collection.set_cmap(cmap) collection.set_norm(norm) diff --git a/lib/matplotlib/tests/test_axes.py b/lib/matplotlib/tests/test_axes.py index 525854583d47..e28022e9fe6f 100644 --- a/lib/matplotlib/tests/test_axes.py +++ b/lib/matplotlib/tests/test_axes.py @@ -2762,7 +2762,7 @@ def get_next_color(): _, _, result_edgecolors = \ mpl.axes.Axes._parse_scatter_color_args( c, edgecolors, kwargs, xsize=2, get_next_color_func=get_next_color) - if type(expected_edgecolors) == np.ndarray: + if isinstance(expected_edgecolors, np.ndarray): assert_allclose(result_edgecolors, expected_edgecolors) else: assert result_edgecolors == expected_edgecolors From c8d7ff31377783ca12b11fb2e3016bf99ccd64a6 Mon Sep 17 00:00:00 2001 From: Mara Date: Thu, 20 Apr 2023 21:22:37 +0200 Subject: [PATCH 5/8] Use isinstance for type checking --- lib/matplotlib/axes/_axes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 2e6b6ab96b6c..bb2c85207b94 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -4717,7 +4717,7 @@ def scatter(self, x, y, s=None, c=None, marker=None, cmap=None, norm=None, ) collection.set_transform(mtransforms.IdentityTransform()) if ((colors is None or edgecolors is None) and not mcolors.is_color_like(c) - and not type(c) == np.ndarray): + and not isinstance(c, np.ndarray)): collection.set_array(c) collection.set_cmap(cmap) collection.set_norm(norm) From 00726684de6a9a56516a9414cecbdaa5eacf491c Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 20 Apr 2023 22:23:31 +0200 Subject: [PATCH 6/8] Add typecheck for maskedarrays --- lib/matplotlib/axes/_axes.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index bb2c85207b94..1a2775166fbd 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -4716,8 +4716,9 @@ def scatter(self, x, y, s=None, c=None, marker=None, cmap=None, norm=None, alpha=alpha, ) collection.set_transform(mtransforms.IdentityTransform()) - if ((colors is None or edgecolors is None) and not mcolors.is_color_like(c) - and not isinstance(c, np.ndarray)): + + if ((colors is None or edgecolors is None) and not mcolors.is_color_like(c) and + (not isinstance(c, np.ndarray) or isinstance(c, np.ma.MaskedArray))): collection.set_array(c) collection.set_cmap(cmap) collection.set_norm(norm) From 679603a4ab0209f3eee7aeefd9b108ab4ff798ce Mon Sep 17 00:00:00 2001 From: Mara Date: Thu, 20 Apr 2023 22:27:34 +0200 Subject: [PATCH 7/8] Add what's new --- doc/users/next_whats_new/updated_scatter.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 doc/users/next_whats_new/updated_scatter.rst diff --git a/doc/users/next_whats_new/updated_scatter.rst b/doc/users/next_whats_new/updated_scatter.rst new file mode 100644 index 000000000000..1d553cb95215 --- /dev/null +++ b/doc/users/next_whats_new/updated_scatter.rst @@ -0,0 +1,15 @@ +Enable configuration of empty markers in `~matplotlib.axes.Axes.scatter` +------------------------------------------------------------------------ + +`~matplotlib.axes.Axes.scatter` can now be configured to plot empty markers without additional code. Setting ``facecolors`` to *'none'* and defining ``c`` now draws only the edge colors for fillable markers. + +.. plot:: + :include-source: true + :alt: A simple scatter plot which illustrates the use of the *scatter* function to plot empty markers. + + import numpy as np + import matplotlib.pyplot as plt + + x = np.arange(0, 10) + plt.scatter(x, x, c=x, facecolors='none', marker='o') + plt.show() From 39cfd5638830e2c49a36018abdba5c82d44e0117 Mon Sep 17 00:00:00 2001 From: Erik Date: Fri, 21 Apr 2023 18:06:48 +0200 Subject: [PATCH 8/8] PR comments and updated whats new --- doc/users/next_whats_new/updated_scatter.rst | 2 +- lib/matplotlib/axes/_axes.py | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/doc/users/next_whats_new/updated_scatter.rst b/doc/users/next_whats_new/updated_scatter.rst index 1d553cb95215..d0b72b87c5b4 100644 --- a/doc/users/next_whats_new/updated_scatter.rst +++ b/doc/users/next_whats_new/updated_scatter.rst @@ -1,7 +1,7 @@ Enable configuration of empty markers in `~matplotlib.axes.Axes.scatter` ------------------------------------------------------------------------ -`~matplotlib.axes.Axes.scatter` can now be configured to plot empty markers without additional code. Setting ``facecolors`` to *'none'* and defining ``c`` now draws only the edge colors for fillable markers. +`~matplotlib.axes.Axes.scatter` can now be configured to plot empty markers by setting ``facecolors`` to *'none'* and defining ``c``. In this case, ``c`` will be now used as ``edgecolor``. .. plot:: :include-source: true diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 1a2775166fbd..1b573aa763b9 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -4387,11 +4387,9 @@ def _parse_scatter_color_args(c, edgecolors, kwargs, xsize, if facecolors is None: facecolors = kwcolor - edge_from_c = False - if edgecolors is None and c is not None: - edge_from_c = True + edge_from_c = edgecolors is None and c is not None - facecolors_none = isinstance(facecolors, str) and facecolors == 'none' + facecolors_none = cbook._str_lower_equal(facecolors, 'none') if (edgecolors is None and not mpl.rcParams['_internal.classic_mode'] and (not edge_from_c or not facecolors_none)):