From e487b1c0d91f01e51c3203666b1a5e19d7c43920 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Fri, 23 Jul 2021 08:57:10 +0200 Subject: [PATCH 01/22] Add user supplied transforms and join/cap styles Improvement was done in instantiating new instance utilizing deep copy should preserve immutability of MarkerStyle members (e.g. Path, or Transform). --- lib/matplotlib/markers.py | 50 ++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index 8fbbb71818a4..644ab2a1aba7 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -127,6 +127,7 @@ .. |m36| image:: /_static/markers/m36.png .. |m37| image:: /_static/markers/m37.png """ +import copy from collections.abc import Sized import inspect @@ -221,7 +222,8 @@ class MarkerStyle: _unset = object() # For deprecation of MarkerStyle(). - def __init__(self, marker=_unset, fillstyle=None): + def __init__(self, marker=_unset, fillstyle=None, + transform=None, capstyle=None, joinstyle=None): """ Parameters ---------- @@ -236,6 +238,9 @@ def __init__(self, marker=_unset, fillstyle=None): One of 'full', 'left', 'right', 'bottom', 'top', 'none'. """ self._marker_function = None + self._user_transform = transform + self._user_capstyle = capstyle + self._user_joinstyle = joinstyle self._set_fillstyle(fillstyle) # Remove _unset and signature rewriting after deprecation elapses. if marker is self._unset: @@ -265,7 +270,7 @@ def _recache(self): self._alt_transform = None self._snap_threshold = None self._joinstyle = JoinStyle.round - self._capstyle = CapStyle.butt + self._capstyle = self._user_capstyle or CapStyle.butt # Initial guess: Assume the marker is filled unless the fillstyle is # set to 'none'. The marker function will override this for unfilled # markers. @@ -342,7 +347,8 @@ def _set_marker(self, marker): self._marker_function = getattr( self, '_set_' + self.markers[marker]) elif isinstance(marker, MarkerStyle): - self.__dict__.update(marker.__dict__) + self.__dict__ = copy.deepcopy(marker.__dict__) + else: try: Path(marker) @@ -369,7 +375,10 @@ def get_transform(self): Return the transform to be applied to the `.Path` from `MarkerStyle.get_path()`. """ - return self._transform.frozen() + if self._user_transform is not None: + return (self._transform + self._user_transform).frozen() + else: + return self._transform.frozen() def get_alt_path(self): """ @@ -385,7 +394,10 @@ def get_alt_transform(self): Return the transform to be applied to the `.Path` from `MarkerStyle.get_alt_path()`. """ - return self._alt_transform.frozen() + if self._user_transform is not None: + return (self._alt_transform + self._user_transform).frozen() + else: + return self._alt_transform.frozen() def get_snap_threshold(self): return self._snap_threshold @@ -413,14 +425,14 @@ def _set_tuple_marker(self): symstyle = marker[1] if symstyle == 0: self._path = Path.unit_regular_polygon(numsides) - self._joinstyle = JoinStyle.miter + self._joinstyle = self._user_joinstyle or JoinStyle.miter elif symstyle == 1: self._path = Path.unit_regular_star(numsides) - self._joinstyle = JoinStyle.bevel + self._joinstyle = self._user_joinstyle or JoinStyle.bevel elif symstyle == 2: self._path = Path.unit_regular_asterisk(numsides) self._filled = False - self._joinstyle = JoinStyle.bevel + self._joinstyle = self._user_joinstyle or JoinStyle.bevel else: raise ValueError(f"Unexpected tuple marker: {marker}") self._transform = Affine2D().scale(0.5).rotate_deg(rotation) @@ -521,7 +533,7 @@ def _set_triangle(self, rot, skip): self._alt_transform = self._transform - self._joinstyle = JoinStyle.miter + self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_triangle_up(self): return self._set_triangle(0.0, 0) @@ -551,7 +563,7 @@ def _set_square(self): self._transform.rotate_deg(rotate) self._alt_transform = self._transform - self._joinstyle = JoinStyle.miter + self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_diamond(self): self._transform = Affine2D().translate(-0.5, -0.5).rotate_deg(45) @@ -565,7 +577,7 @@ def _set_diamond(self): rotate = {'right': 0, 'top': 90, 'left': 180, 'bottom': 270}[fs] self._transform.rotate_deg(rotate) self._alt_transform = self._transform - self._joinstyle = JoinStyle.miter + self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_thin_diamond(self): self._set_diamond() @@ -592,7 +604,7 @@ def _set_pentagon(self): }[self.get_fillstyle()] self._alt_transform = self._transform - self._joinstyle = JoinStyle.miter + self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_star(self): self._transform = Affine2D().scale(0.5) @@ -614,7 +626,7 @@ def _set_star(self): }[self.get_fillstyle()] self._alt_transform = self._transform - self._joinstyle = JoinStyle.bevel + self._joinstyle = self._user_joinstyle or JoinStyle.bevel def _set_hexagon1(self): self._transform = Affine2D().scale(0.5) @@ -638,7 +650,7 @@ def _set_hexagon1(self): }[self.get_fillstyle()] self._alt_transform = self._transform - self._joinstyle = JoinStyle.miter + self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_hexagon2(self): self._transform = Affine2D().scale(0.5).rotate_deg(30) @@ -664,7 +676,7 @@ def _set_hexagon2(self): }[self.get_fillstyle()] self._alt_transform = self._transform - self._joinstyle = JoinStyle.miter + self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_octagon(self): self._transform = Affine2D().scale(0.5) @@ -685,7 +697,7 @@ def _set_octagon(self): {'left': 0, 'bottom': 90, 'right': 180, 'top': 270}[fs]) self._alt_transform = self._transform.frozen().rotate_deg(180.0) - self._joinstyle = JoinStyle.miter + self._joinstyle = self._user_joinstyle or JoinStyle.miter _line_marker_path = Path([[0.0, -1.0], [0.0, 1.0]]) @@ -759,7 +771,7 @@ def _set_caretdown(self): self._snap_threshold = 3.0 self._filled = False self._path = self._caret_path - self._joinstyle = JoinStyle.miter + self._joinstyle = self._user_joinstyle or JoinStyle.miter def _set_caretup(self): self._set_caretdown() @@ -825,7 +837,7 @@ def _set_x(self): def _set_plus_filled(self): self._transform = Affine2D() self._snap_threshold = 5.0 - self._joinstyle = JoinStyle.miter + self._joinstyle = self._user_joinstyle or JoinStyle.miter if not self._half_fill(): self._path = self._plus_filled_path else: @@ -849,7 +861,7 @@ def _set_plus_filled(self): def _set_x_filled(self): self._transform = Affine2D() self._snap_threshold = 5.0 - self._joinstyle = JoinStyle.miter + self._joinstyle = self._user_joinstyle or JoinStyle.miter if not self._half_fill(): self._path = self._x_filled_path else: From 37859ea33869d86c4e57aaa3512a705092e774be Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Thu, 26 Aug 2021 20:40:53 +0200 Subject: [PATCH 02/22] Avoid reinstanting MarkerStyle in lines.py --- lib/matplotlib/lines.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/lines.py b/lib/matplotlib/lines.py index ea3b98b63208..f1efd125a4ed 100644 --- a/lib/matplotlib/lines.py +++ b/lib/matplotlib/lines.py @@ -370,7 +370,10 @@ def __init__(self, xdata, ydata, self.set_color(color) if marker is None: marker = 'none' # Default. - self._marker = MarkerStyle(marker, fillstyle) + if not isinstance(marker, MarkerStyle): + self._marker = MarkerStyle(marker, fillstyle) + else: + self._marker = marker self._markevery = None self._markersize = None From 81536320d4a702db9376782ee7ac6b5a6f43971c Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Sun, 5 Sep 2021 23:30:26 +0200 Subject: [PATCH 03/22] Update dosctring. --- lib/matplotlib/markers.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index 644ab2a1aba7..b4413bbe3ba0 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -236,6 +236,18 @@ def __init__(self, marker=_unset, fillstyle=None, fillstyle : str, default: :rc:`markers.fillstyle` One of 'full', 'left', 'right', 'bottom', 'top', 'none'. + + transform : Affine2D, default: None + User supplied transformation that will be combined with the + native transformation of selected marker. + + capstyle : CapStyle, default: None + User supplied cap style that will override the default cap + style of selected marker. + + joinstyle : JoinStyle, default: None + User supplied join style that will override the default cap + style of selected marker. """ self._marker_function = None self._user_transform = transform From 4469006fb4b8720876b938023561233dd9588993 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Mon, 6 Sep 2021 22:56:14 +0200 Subject: [PATCH 04/22] Add marker.transformed + test --- lib/matplotlib/markers.py | 51 +++++++++++++++++++++++++++-- lib/matplotlib/tests/test_marker.py | 11 +++++++ 2 files changed, 59 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index b4413bbe3ba0..5652382c945a 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -236,7 +236,7 @@ def __init__(self, marker=_unset, fillstyle=None, fillstyle : str, default: :rc:`markers.fillstyle` One of 'full', 'left', 'right', 'bottom', 'top', 'none'. - + transform : Affine2D, default: None User supplied transformation that will be combined with the native transformation of selected marker. @@ -319,10 +319,10 @@ def _set_fillstyle(self, fillstyle): self._recache() def get_joinstyle(self): - return self._joinstyle + return self._user_joinstyle or self._joinstyle def get_capstyle(self): - return self._capstyle + return self._user_capstyle or self._capstyle def get_marker(self): return self._marker @@ -414,6 +414,51 @@ def get_alt_transform(self): def get_snap_threshold(self): return self._snap_threshold + def get_user_transform(self): + """Return user supplied part of marker transform.""" + if self._user_transform is not None: + return self._user_transform.frozen() + + def transformed(self, transform:Affine2D): + """ + Return new marker with combined transformation. + + Parameters + ---------- + transform : Affine2D, default: None + - transform will be combined with current user supplied transform. + """ + new_marker = MarkerStyle(self) + if new_marker._user_transform is not None: + new_marker._user_transform += transform + else: + new_marker._user_transform = transform + return new_marker + + def rotated(self, deg=None, rad=None): + """ + Return new marker rotated by specified angle. + + Parameters + ---------- + deg : float, default: None + + rad : float, default: None + """ + if not ((deg is None) ^ (rad is None)): + raise Exception("Only one of deg or rad shall be used.") + + _transform = self._user_transform or Affine2D() + + if deg is not None: + _transform = _transform.rotate_deg(deg) + + if rad is not None: + _transform = _transform.rotate(rad) + + new_marker = MarkerStyle(self) + new_marker._user_transform = _transform + def _set_nothing(self): self._filled = False diff --git a/lib/matplotlib/tests/test_marker.py b/lib/matplotlib/tests/test_marker.py index 75681b0e1a9b..11c9564a9218 100644 --- a/lib/matplotlib/tests/test_marker.py +++ b/lib/matplotlib/tests/test_marker.py @@ -4,6 +4,7 @@ from matplotlib._api.deprecation import MatplotlibDeprecationWarning from matplotlib.path import Path from matplotlib.testing.decorators import check_figures_equal +from matplotlib.transforms import Affine2D import pytest @@ -204,3 +205,13 @@ def test_marker_clipping(fig_ref, fig_test): ax_test.set(xlim=(-0.5, ncol), ylim=(-0.5, 2 * nrow)) ax_ref.axis('off') ax_test.axis('off') + +@pytest.mark.parametrize("marker,transform,expected", [ + (markers.MarkerStyle("o"), Affine2D().translate(1,1), Affine2D().translate(1,1)), + (markers.MarkerStyle("o", transform=Affine2D().translate(1,1)), Affine2D().translate(1,1), Affine2D().translate(2,2)), +]) +def test_marker_transformed(marker, transform, expected): + new_marker = marker.transformed(transform) + assert new_marker is not marker + assert new_marker.get_user_transform() == expected + assert marker.get_user_transform() is not new_marker.get_user_transform() \ No newline at end of file From 4ea2b913da2932e08a3d8eea4ad0ff63290d4306 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Fri, 10 Sep 2021 23:10:54 +0200 Subject: [PATCH 05/22] Add affine transform primitives to markers Added, translated, scaled, and rotated methods with test. --- lib/matplotlib/markers.py | 61 ++++++++++++++++++++++++----- lib/matplotlib/tests/test_marker.py | 41 +++++++++++++++++-- 2 files changed, 90 insertions(+), 12 deletions(-) diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index 5652382c945a..90532104f8a9 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -419,7 +419,7 @@ def get_user_transform(self): if self._user_transform is not None: return self._user_transform.frozen() - def transformed(self, transform:Affine2D): + def transformed(self, transform: Affine2D): """ Return new marker with combined transformation. @@ -442,22 +442,65 @@ def rotated(self, deg=None, rad=None): Parameters ---------- deg : float, default: None + - use this parameter to specify rotation angle in degrees. rad : float, default: None + - use this parameter to specify rotation angle in radians. + + Note: you must specify exactly one of deg or rad. """ if not ((deg is None) ^ (rad is None)): - raise Exception("Only one of deg or rad shall be used.") - - _transform = self._user_transform or Affine2D() + raise ValueError("Exactly one of deg or rad shall be used.") + + new_marker = MarkerStyle(self) + if new_marker._user_transform is None: + new_marker._user_transform = Affine2D() - if deg is not None: - _transform = _transform.rotate_deg(deg) - + if deg is not None: + new_marker._user_transform.rotate_deg(deg) if rad is not None: - _transform = _transform.rotate(rad) + new_marker._user_transform.rotate(rad) + + return new_marker + + def scaled(self, sx, sy=None): + """ + Return new marker scaled by specified scale factors. + + If *sy* is None, the same scale is applied in both the *x*- and + *y*-directions. + + Parameters + ---------- + sx : float + - *x*-direction scaling factor. + + sy : float, default: None + - *y*-direction scaling factor. + """ + if sy is None: + sy = sx + + new_marker = MarkerStyle(self) + _transform = new_marker._user_transform or Affine2D() + new_marker._user_transform = _transform.scale(sx, sy) + return new_marker + + def translated(self, tx, ty): + """ + Return new marker translated by tx and ty. + Parameters + ---------- + tx : float + + ty : float + + """ new_marker = MarkerStyle(self) - new_marker._user_transform = _transform + _transform = new_marker._user_transform or Affine2D() + new_marker._user_transform = _transform.translate(tx, ty) + return new_marker def _set_nothing(self): self._filled = False diff --git a/lib/matplotlib/tests/test_marker.py b/lib/matplotlib/tests/test_marker.py index 11c9564a9218..db2aaa768e8f 100644 --- a/lib/matplotlib/tests/test_marker.py +++ b/lib/matplotlib/tests/test_marker.py @@ -206,12 +206,47 @@ def test_marker_clipping(fig_ref, fig_test): ax_ref.axis('off') ax_test.axis('off') + @pytest.mark.parametrize("marker,transform,expected", [ - (markers.MarkerStyle("o"), Affine2D().translate(1,1), Affine2D().translate(1,1)), - (markers.MarkerStyle("o", transform=Affine2D().translate(1,1)), Affine2D().translate(1,1), Affine2D().translate(2,2)), + (markers.MarkerStyle("o"), Affine2D().translate(1, 1), + Affine2D().translate(1, 1)), + (markers.MarkerStyle("o", transform=Affine2D().translate(1, 1)), + Affine2D().translate(1, 1), Affine2D().translate(2, 2)), + # (markers.MarkerStyle("$|||$", transform=Affine2D().translate(1, 1)), + # Affine2D().translate(1, 1), Affine2D().translate(2, 2)), + (markers.MarkerStyle( + markers.TICKLEFT, transform=Affine2D().translate(1, 1)), + Affine2D().translate(1, 1), Affine2D().translate(2, 2)), ]) def test_marker_transformed(marker, transform, expected): new_marker = marker.transformed(transform) assert new_marker is not marker assert new_marker.get_user_transform() == expected - assert marker.get_user_transform() is not new_marker.get_user_transform() \ No newline at end of file + assert marker._user_transform is not new_marker._user_transform + + +def test_marker_rotated_invalid(): + marker = markers.MarkerStyle("o") + with pytest.raises(ValueError): + new_marker = marker.rotated() + new_marker = marker.rotated(deg=10, rad=10) + + +@pytest.mark.parametrize("marker,deg,rad,expected", [ + (markers.MarkerStyle("o"), 10, None, Affine2D().rotate_deg(10)), + (markers.MarkerStyle("o"), None, 0.01, Affine2D().rotate(0.01)), + (markers.MarkerStyle("o", transform=Affine2D().translate(1, 1)), + 10, None, Affine2D().translate(1, 1).rotate_deg(10)), + (markers.MarkerStyle("o", transform=Affine2D().translate(1, 1)), + None, 0.01, Affine2D().translate(1, 1).rotate(0.01)), + # (markers.MarkerStyle("$|||$", transform=Affine2D().translate(1, 1)), + # 10, None, Affine2D().translate(1, 1).rotate_deg(10)), + (markers.MarkerStyle( + markers.TICKLEFT, transform=Affine2D().translate(1, 1)), + 10, None, Affine2D().translate(1, 1).rotate_deg(10)), +]) +def test_marker_rotated_deg(marker, deg, rad, expected): + new_marker = marker.rotated(deg=deg, rad=rad) + assert new_marker is not marker + assert new_marker.get_user_transform() == expected + assert marker._user_transform is not new_marker._user_transform From 7f6d1bece34f6dbbb8ab917e828acc2edda34425 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Sat, 11 Sep 2021 20:20:54 +0200 Subject: [PATCH 06/22] Tweaking docstrings in MarkerStyle. --- lib/matplotlib/markers.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index 90532104f8a9..fc639fd6fbd8 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -426,7 +426,7 @@ def transformed(self, transform: Affine2D): Parameters ---------- transform : Affine2D, default: None - - transform will be combined with current user supplied transform. + Transform will be combined with current user supplied transform. """ new_marker = MarkerStyle(self) if new_marker._user_transform is not None: @@ -442,12 +442,12 @@ def rotated(self, deg=None, rad=None): Parameters ---------- deg : float, default: None - - use this parameter to specify rotation angle in degrees. + Use this parameter to specify rotation angle in degrees. rad : float, default: None - - use this parameter to specify rotation angle in radians. + Use this parameter to specify rotation angle in radians. - Note: you must specify exactly one of deg or rad. + .. note:: You must specify exactly one of deg or rad. """ if not ((deg is None) ^ (rad is None)): raise ValueError("Exactly one of deg or rad shall be used.") @@ -473,10 +473,9 @@ def scaled(self, sx, sy=None): Parameters ---------- sx : float - - *x*-direction scaling factor. - + *X*-direction scaling factor. sy : float, default: None - - *y*-direction scaling factor. + *Y*-direction scaling factor. """ if sy is None: sy = sx @@ -493,9 +492,9 @@ def translated(self, tx, ty): Parameters ---------- tx : float - + Coordinate for translation in *x*-direction. ty : float - + Coordinate for translation in *y*-direction. """ new_marker = MarkerStyle(self) _transform = new_marker._user_transform or Affine2D() From 2729fe43217ea810c33131eb640aac8c5c45abd3 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Sun, 12 Sep 2021 17:05:03 +0200 Subject: [PATCH 07/22] Update MarkerStyle module documentation --- lib/matplotlib/markers.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index fc639fd6fbd8..b380e1203484 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -82,6 +82,13 @@ plt.plot([1, 2, 3], marker=11) plt.plot([1, 2, 3], marker=matplotlib.markers.CARETDOWNBASE) +Markers have some reasonable default settings for join and cap styles. +However, those can be overriden when creating a new instance of MarkerStyle. +Furthermore, the marker shape can be modified by supplying +`~matplotlib .transforms.Transform` during creation. +Some markers are created as internally rotated shapes (e.g. triangles). +For such cases, both internal and user supplied transforms are combined. + Examples showing the use of markers: * :doc:`/gallery/lines_bars_and_markers/marker_reference` From 435799533a9a2a741d0b04aad23b64730263ebd7 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Sun, 12 Sep 2021 17:05:38 +0200 Subject: [PATCH 08/22] Update marker Reference documentation --- .../marker_reference.py | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/examples/lines_bars_and_markers/marker_reference.py b/examples/lines_bars_and_markers/marker_reference.py index 98af2519124c..d1d675c5c268 100644 --- a/examples/lines_bars_and_markers/marker_reference.py +++ b/examples/lines_bars_and_markers/marker_reference.py @@ -19,8 +19,10 @@ .. redirect-from:: /gallery/shapes_and_collections/marker_path """ +from matplotlib.markers import MarkerStyle import matplotlib.pyplot as plt from matplotlib.lines import Line2D +from matplotlib.transforms import Affine2D text_style = dict(horizontalalignment='right', verticalalignment='center', @@ -159,3 +161,98 @@ def split_list(a_list): format_axes(ax) plt.show() + +############################################################################### +# Advanced marker modifications with transform +# ============================================ +# +# All markers can be modified by a user transform in MarkerStyle constructor. +# Supplied transform is combined with the default transforms needed for +# selected marker shape (e.g. caret up, caret down). Following example shows +# how user supplied rotation applies to several marker shapes. + +common_style = {k: v for k, v in filled_marker_style.items() if v != 'marker'} +angles = [0, 10, 20, 30, 45, 60, 90] + +fig, ax = plt.subplots() +fig.suptitle('Rotated markers', fontsize=14) + +ax.text(-0.5, 0, 'Filled marker', **text_style) +for x, theta in enumerate(angles): + t = Affine2D().rotate_deg(theta) + ax.plot(x, 0, marker=MarkerStyle('o', 'left', t), **common_style) + +ax.text(-0.5, 1, 'Un-filled marker', **text_style) +for x, theta in enumerate(angles): + t = Affine2D().rotate_deg(theta) + ax.plot(x, 1, marker=MarkerStyle('1', 'left', t), **common_style) + +ax.text(-0.5, 2, 'Equation marker', **text_style) +for x, theta in enumerate(angles): + t = Affine2D().rotate_deg(theta) + eq = r'$\frac{1}{x}$' + ax.plot(x, 2, marker=MarkerStyle(eq, 'left', t), **common_style) + +for x, theta in enumerate(angles): + ax.text(x, 2.5, f"{theta}°", horizontalalignment="center") +format_axes(ax) + +plt.show() + +############################################################################### +# Setting marker cap style and join style +# ======================================= +# +# All markers have predefined cap style and join style, but this can be +# overriden during creation of MarkerStyle. Follwing example show how to +# change the cap style and how different styles look. + +from matplotlib.markers import JoinStyle, CapStyle + +marker_inner = dict(markersize=35, + markerfacecolor='tab:blue', + markerfacecoloralt='lightsteelblue', + markeredgecolor='brown', + markeredgewidth=8, + ) + +marker_outer = dict(markersize=35, + markerfacecolor='tab:blue', + markerfacecoloralt='lightsteelblue', + markeredgecolor='white', + markeredgewidth=1, + ) + +fig, ax = plt.subplots() +fig.suptitle('Marker CapStyle', fontsize=14) +fig.subplots_adjust(left=0.1) + +for y, cap_style in enumerate([None, *CapStyle]): + ax.text(-0.5, y, cap_style.name, **text_style) + for x, theta in enumerate(angles): + t = Affine2D().rotate_deg(theta) + m = MarkerStyle('1', transform=t, capstyle=cap_style) + ax.plot(x, y, marker=m, **marker_inner) + ax.plot(x, y, marker=m, **marker_outer) + ax.text(x, len(CapStyle) - .5, f'{theta}°', ha='center') +format_axes(ax) +plt.show() + +############################################################################### +# Follwing example show how to change the join style and how different styles +# look. + +fig, ax = plt.subplots() +fig.suptitle('Marker JoinStyle', fontsize=14) +fig.subplots_adjust(left=0.05) + +for y, join_style in enumerate(JoinStyle): + ax.text(-0.5, y, join_style.name, **text_style) + for x, theta in enumerate(angles): + t = Affine2D().rotate_deg(theta) + m = MarkerStyle('*', transform=t, joinstyle=join_style) + ax.plot(x, y, marker=m, **marker_inner) + ax.text(x, len(JoinStyle) - .5, f'{theta}°', ha='center') +format_axes(ax) + +plt.show() From b1bbdff12758e8e0a30841ba075cdd6c9c2485c6 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Sun, 12 Sep 2021 17:06:03 +0200 Subject: [PATCH 09/22] Add whats new for MarkerStyle features --- .../next_whats_new/extending_MarkerStyle.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 doc/users/next_whats_new/extending_MarkerStyle.rst diff --git a/doc/users/next_whats_new/extending_MarkerStyle.rst b/doc/users/next_whats_new/extending_MarkerStyle.rst new file mode 100644 index 000000000000..06479a7badf8 --- /dev/null +++ b/doc/users/next_whats_new/extending_MarkerStyle.rst @@ -0,0 +1,19 @@ +New customization of MarkerStyle +-------------------------------- + +New MarkerStyle parameters allow control of join style and cap style. +The appearance of individual markers can be further controlled by +transform supplied during creation. + +.. plot:: + :include-source: + import matplotlib.pyplot as plt + from matplotlib.markers import MarkerStyle + from matplotlib.transforms import Affine2D + fig, ax = plt.subplots(figsize=(6, 1)) + fig.suptitle('New markers', fontsize=14) + for col, (size, rot) in enumerate(zip([2, 5, 10], [0, 45, 90])): + t = Affine2D().rotate_deg(rot).scale(size) + ax.plot(col, 0, marker=MarkerStyle("*", transform=t)) + ax.axis("off") + ax.set_xlim(-0.1, 2.4) From 9ac3ad2f6e5a14ab12a1dabf975ba71fa466f42f Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Sun, 12 Sep 2021 19:27:36 +0200 Subject: [PATCH 10/22] Add new example with advanced MarkerStyle mapping --- .../multivariate_marker_plot.py | 45 +++++++++++++++++++ lib/matplotlib/markers.py | 2 +- 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 examples/lines_bars_and_markers/multivariate_marker_plot.py diff --git a/examples/lines_bars_and_markers/multivariate_marker_plot.py b/examples/lines_bars_and_markers/multivariate_marker_plot.py new file mode 100644 index 000000000000..274a1113fbc1 --- /dev/null +++ b/examples/lines_bars_and_markers/multivariate_marker_plot.py @@ -0,0 +1,45 @@ +""" +============================================== +Mapping marker properties to multivariate data +============================================== + +This example shows how to use different properties of markers to plot +multivariate datasets. Following example shows an illustrative case of +plotting success of baseball throw as an smiley face with size mapped to +the skill of thrower, rotation mapped to the take-off angle, and thrust +to the color of the marker. +""" + +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.markers import MarkerStyle +from matplotlib.transforms import Affine2D +from matplotlib.textpath import TextPath +from matplotlib.colors import Normalize + +SUCCESS_SYMBOLS = [ + TextPath((0,0), "☹"), + TextPath((0,0), "😒"), + TextPath((0,0), "☺"), +] + +N = 25 +np.random.seed(42) +skills = np.random.uniform(5, 80, size=N) * 0.1 + 5 +takeoff_angles = np.random.normal(0, 90, N) +thrusts = np.random.uniform(size=N) +successfull = np.random.randint(0, 3, size=N) +positions = np.random.normal(size=(N, 2)) * 5 +data = zip(skills, takeoff_angles, thrusts, successfull, positions) + +cmap = plt.cm.get_cmap("plasma") +fig, ax = plt.subplots() +fig.suptitle("Throwing success", size=14) +for skill, takeoff, thrust, mood, pos in data: + t = Affine2D().scale(skill).rotate_deg(takeoff) + m = MarkerStyle(SUCCESS_SYMBOLS[mood], transform=t) + ax.plot(pos[0], pos[1], marker=m, color=cmap(thrust)) +fig.colorbar(plt.cm.ScalarMappable(norm=Normalize(0, 1), cmap=cmap), + ax=ax, label="Normalized Thrust [a.u.]") +ax.set_xlabel("X position [m]") +ax.set_ylabel("Y position [m]") diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index b380e1203484..86b3492d8776 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -93,7 +93,7 @@ * :doc:`/gallery/lines_bars_and_markers/marker_reference` * :doc:`/gallery/lines_bars_and_markers/scatter_star_poly` - +* :doc:`/gallery/lines_bars_and_markers/multivariate_marker_plot` .. |m00| image:: /_static/markers/m00.png .. |m01| image:: /_static/markers/m01.png From 41808430ace77c5dfdbb815c1d2e26ad3c1e8181 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Sun, 12 Sep 2021 23:40:33 +0200 Subject: [PATCH 11/22] Fix errors discovered by CI --- doc/users/next_whats_new/extending_MarkerStyle.rst | 3 ++- examples/lines_bars_and_markers/marker_reference.py | 6 +++--- .../lines_bars_and_markers/multivariate_marker_plot.py | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/doc/users/next_whats_new/extending_MarkerStyle.rst b/doc/users/next_whats_new/extending_MarkerStyle.rst index 06479a7badf8..fbe0fadd7f0c 100644 --- a/doc/users/next_whats_new/extending_MarkerStyle.rst +++ b/doc/users/next_whats_new/extending_MarkerStyle.rst @@ -6,7 +6,8 @@ The appearance of individual markers can be further controlled by transform supplied during creation. .. plot:: - :include-source: + :include-source: true + import matplotlib.pyplot as plt from matplotlib.markers import MarkerStyle from matplotlib.transforms import Affine2D diff --git a/examples/lines_bars_and_markers/marker_reference.py b/examples/lines_bars_and_markers/marker_reference.py index d1d675c5c268..f82ec36035e6 100644 --- a/examples/lines_bars_and_markers/marker_reference.py +++ b/examples/lines_bars_and_markers/marker_reference.py @@ -171,7 +171,7 @@ def split_list(a_list): # selected marker shape (e.g. caret up, caret down). Following example shows # how user supplied rotation applies to several marker shapes. -common_style = {k: v for k, v in filled_marker_style.items() if v != 'marker'} +common_style = {k: v for k, v in filled_marker_style.items() if k != 'marker'} angles = [0, 10, 20, 30, 45, 60, 90] fig, ax = plt.subplots() @@ -227,7 +227,7 @@ def split_list(a_list): fig.suptitle('Marker CapStyle', fontsize=14) fig.subplots_adjust(left=0.1) -for y, cap_style in enumerate([None, *CapStyle]): +for y, cap_style in enumerate(CapStyle): ax.text(-0.5, y, cap_style.name, **text_style) for x, theta in enumerate(angles): t = Affine2D().rotate_deg(theta) @@ -240,7 +240,7 @@ def split_list(a_list): ############################################################################### # Follwing example show how to change the join style and how different styles -# look. +# looks like. fig, ax = plt.subplots() fig.suptitle('Marker JoinStyle', fontsize=14) diff --git a/examples/lines_bars_and_markers/multivariate_marker_plot.py b/examples/lines_bars_and_markers/multivariate_marker_plot.py index 274a1113fbc1..51d7d4bca30e 100644 --- a/examples/lines_bars_and_markers/multivariate_marker_plot.py +++ b/examples/lines_bars_and_markers/multivariate_marker_plot.py @@ -18,9 +18,9 @@ from matplotlib.colors import Normalize SUCCESS_SYMBOLS = [ - TextPath((0,0), "☹"), - TextPath((0,0), "😒"), - TextPath((0,0), "☺"), + TextPath((0, 0), "☹"), + TextPath((0, 0), "😒"), + TextPath((0, 0), "☺"), ] N = 25 @@ -40,6 +40,6 @@ m = MarkerStyle(SUCCESS_SYMBOLS[mood], transform=t) ax.plot(pos[0], pos[1], marker=m, color=cmap(thrust)) fig.colorbar(plt.cm.ScalarMappable(norm=Normalize(0, 1), cmap=cmap), - ax=ax, label="Normalized Thrust [a.u.]") + ax=ax, label="Normalized Thrust [a.u.]") ax.set_xlabel("X position [m]") ax.set_ylabel("Y position [m]") From 905eba488f9d2f0db420bd710b4fdc465397a2d8 Mon Sep 17 00:00:00 2001 From: Jakub Klus <48711526+deep-jkl@users.noreply.github.com> Date: Mon, 13 Sep 2021 08:07:33 +0200 Subject: [PATCH 12/22] Fix typo in markers api documentation. Apply suggestion from @timhoffm, update lib/matplotlib/markers.py Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> --- lib/matplotlib/markers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index 86b3492d8776..ad06879532f6 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -85,7 +85,7 @@ Markers have some reasonable default settings for join and cap styles. However, those can be overriden when creating a new instance of MarkerStyle. Furthermore, the marker shape can be modified by supplying -`~matplotlib .transforms.Transform` during creation. +`~matplotlib.transforms.Transform` during creation. Some markers are created as internally rotated shapes (e.g. triangles). For such cases, both internal and user supplied transforms are combined. From 3fc6140998d3a8cf130d53e161c0b2b4ff852a18 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Wed, 15 Sep 2021 00:32:03 +0200 Subject: [PATCH 13/22] Improve test for invalid rotated inputs --- lib/matplotlib/markers.py | 2 +- lib/matplotlib/tests/test_marker.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index ad06879532f6..2a51d8fa5e9a 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -456,7 +456,7 @@ def rotated(self, deg=None, rad=None): .. note:: You must specify exactly one of deg or rad. """ - if not ((deg is None) ^ (rad is None)): + if not (deg is None) ^ (rad is None): raise ValueError("Exactly one of deg or rad shall be used.") new_marker = MarkerStyle(self) diff --git a/lib/matplotlib/tests/test_marker.py b/lib/matplotlib/tests/test_marker.py index db2aaa768e8f..e2b18354f34a 100644 --- a/lib/matplotlib/tests/test_marker.py +++ b/lib/matplotlib/tests/test_marker.py @@ -229,6 +229,7 @@ def test_marker_rotated_invalid(): marker = markers.MarkerStyle("o") with pytest.raises(ValueError): new_marker = marker.rotated() + with pytest.raises(ValueError): new_marker = marker.rotated(deg=10, rad=10) From 04a3e7046b0ae8c54f3fbd70b35d975005731a03 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Wed, 15 Sep 2021 00:39:35 +0200 Subject: [PATCH 14/22] Add tests for MarkerStyle scaled and translated --- lib/matplotlib/tests/test_marker.py | 38 +++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/tests/test_marker.py b/lib/matplotlib/tests/test_marker.py index e2b18354f34a..7925f22f54f7 100644 --- a/lib/matplotlib/tests/test_marker.py +++ b/lib/matplotlib/tests/test_marker.py @@ -229,7 +229,7 @@ def test_marker_rotated_invalid(): marker = markers.MarkerStyle("o") with pytest.raises(ValueError): new_marker = marker.rotated() - with pytest.raises(ValueError): + with pytest.raises(ValueError): new_marker = marker.rotated(deg=10, rad=10) @@ -246,8 +246,42 @@ def test_marker_rotated_invalid(): markers.TICKLEFT, transform=Affine2D().translate(1, 1)), 10, None, Affine2D().translate(1, 1).rotate_deg(10)), ]) -def test_marker_rotated_deg(marker, deg, rad, expected): +def test_marker_rotated(marker, deg, rad, expected): new_marker = marker.rotated(deg=deg, rad=rad) assert new_marker is not marker assert new_marker.get_user_transform() == expected assert marker._user_transform is not new_marker._user_transform + + +def test_marker_translated(): + marker = markers.MarkerStyle("1") + new_marker = marker.translated(1, 1) + assert new_marker is not marker + assert new_marker.get_user_transform() == Affine2D().translate(1, 1) + assert marker._user_transform is not new_marker._user_transform + + marker = markers.MarkerStyle("1", transform=Affine2D().translate(1, 1)) + new_marker = marker.translated(1, 1) + assert new_marker is not marker + assert new_marker.get_user_transform() == Affine2D().translate(2, 2) + assert marker._user_transform is not new_marker._user_transform + + +def test_marker_scaled(): + marker = markers.MarkerStyle("1") + new_marker = marker.scaled(2) + assert new_marker is not marker + assert new_marker.get_user_transform() == Affine2D().scale(2) + assert marker._user_transform is not new_marker._user_transform + + new_marker = marker.scaled(2, 3) + assert new_marker is not marker + assert new_marker.get_user_transform() == Affine2D().scale(2, 3) + assert marker._user_transform is not new_marker._user_transform + + marker = markers.MarkerStyle("1", transform=Affine2D().translate(1, 1)) + new_marker = marker.scaled(2) + assert new_marker is not marker + expected = Affine2D().translate(1, 1).scale(2) + assert new_marker.get_user_transform() == expected + assert marker._user_transform is not new_marker._user_transform From af6b89ad58025d24fe73d8292a8c5e08f4d330bd Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Wed, 15 Sep 2021 22:09:27 +0200 Subject: [PATCH 15/22] Add function for optimized None+transform --- lib/matplotlib/markers.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index 2a51d8fa5e9a..e8fafd909e19 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -154,6 +154,14 @@ _empty_path = Path(np.empty((0, 2))) +def _fast_transform_combine(t1, t2): + """Combine two transformations where the second one can be None.""" + if t2 is None: + return t1.frozen() + else: + return (t1 + t2).frozen() + + class MarkerStyle: """ A class representing marker types. @@ -394,10 +402,7 @@ def get_transform(self): Return the transform to be applied to the `.Path` from `MarkerStyle.get_path()`. """ - if self._user_transform is not None: - return (self._transform + self._user_transform).frozen() - else: - return self._transform.frozen() + return _fast_transform_combine(self._transform, self._user_transform) def get_alt_path(self): """ @@ -413,10 +418,8 @@ def get_alt_transform(self): Return the transform to be applied to the `.Path` from `MarkerStyle.get_alt_path()`. """ - if self._user_transform is not None: - return (self._alt_transform + self._user_transform).frozen() - else: - return self._alt_transform.frozen() + return _fast_transform_combine(self._alt_transform, + self._user_transform) def get_snap_threshold(self): return self._snap_threshold From c01e8f1b5e1786b7be1159e92833496712ac7e89 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Wed, 15 Sep 2021 22:11:05 +0200 Subject: [PATCH 16/22] Remove ambiguous translated primitive for MarkerStyle --- lib/matplotlib/markers.py | 16 ---------------- lib/matplotlib/tests/test_marker.py | 14 -------------- 2 files changed, 30 deletions(-) diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index e8fafd909e19..6a839d040495 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -495,22 +495,6 @@ def scaled(self, sx, sy=None): new_marker._user_transform = _transform.scale(sx, sy) return new_marker - def translated(self, tx, ty): - """ - Return new marker translated by tx and ty. - - Parameters - ---------- - tx : float - Coordinate for translation in *x*-direction. - ty : float - Coordinate for translation in *y*-direction. - """ - new_marker = MarkerStyle(self) - _transform = new_marker._user_transform or Affine2D() - new_marker._user_transform = _transform.translate(tx, ty) - return new_marker - def _set_nothing(self): self._filled = False diff --git a/lib/matplotlib/tests/test_marker.py b/lib/matplotlib/tests/test_marker.py index 7925f22f54f7..f7f5d5614cac 100644 --- a/lib/matplotlib/tests/test_marker.py +++ b/lib/matplotlib/tests/test_marker.py @@ -253,20 +253,6 @@ def test_marker_rotated(marker, deg, rad, expected): assert marker._user_transform is not new_marker._user_transform -def test_marker_translated(): - marker = markers.MarkerStyle("1") - new_marker = marker.translated(1, 1) - assert new_marker is not marker - assert new_marker.get_user_transform() == Affine2D().translate(1, 1) - assert marker._user_transform is not new_marker._user_transform - - marker = markers.MarkerStyle("1", transform=Affine2D().translate(1, 1)) - new_marker = marker.translated(1, 1) - assert new_marker is not marker - assert new_marker.get_user_transform() == Affine2D().translate(2, 2) - assert marker._user_transform is not new_marker._user_transform - - def test_marker_scaled(): marker = markers.MarkerStyle("1") new_marker = marker.scaled(2) From 95d93d27a3e82a311d86e33007668c36e11cb745 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Wed, 15 Sep 2021 22:25:18 +0200 Subject: [PATCH 17/22] Add missing test for MarkerStyle.get_alt_transform --- lib/matplotlib/tests/test_marker.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/matplotlib/tests/test_marker.py b/lib/matplotlib/tests/test_marker.py index f7f5d5614cac..d3665d9ce09d 100644 --- a/lib/matplotlib/tests/test_marker.py +++ b/lib/matplotlib/tests/test_marker.py @@ -271,3 +271,9 @@ def test_marker_scaled(): expected = Affine2D().translate(1, 1).scale(2) assert new_marker.get_user_transform() == expected assert marker._user_transform is not new_marker._user_transform + + +def test_alt_transform(): + m1 = markers.MarkerStyle("o", "left") + m2 = markers.MarkerStyle("o", "left", Affine2D().rotate_deg(90)) + assert m1.get_alt_transform().rotate_deg(90) == m2.get_alt_transform() From 75a92d81edfb69c0da319ced1f9f1e8b590e4b1f Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Thu, 16 Sep 2021 23:02:53 +0200 Subject: [PATCH 18/22] Improve documentation outputs. --- examples/lines_bars_and_markers/marker_reference.py | 1 + examples/lines_bars_and_markers/multivariate_marker_plot.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/examples/lines_bars_and_markers/marker_reference.py b/examples/lines_bars_and_markers/marker_reference.py index f82ec36035e6..f751bfb975b8 100644 --- a/examples/lines_bars_and_markers/marker_reference.py +++ b/examples/lines_bars_and_markers/marker_reference.py @@ -197,6 +197,7 @@ def split_list(a_list): ax.text(x, 2.5, f"{theta}°", horizontalalignment="center") format_axes(ax) +fig.tight_layout() plt.show() ############################################################################### diff --git a/examples/lines_bars_and_markers/multivariate_marker_plot.py b/examples/lines_bars_and_markers/multivariate_marker_plot.py index 51d7d4bca30e..0a79026b2772 100644 --- a/examples/lines_bars_and_markers/multivariate_marker_plot.py +++ b/examples/lines_bars_and_markers/multivariate_marker_plot.py @@ -43,3 +43,5 @@ ax=ax, label="Normalized Thrust [a.u.]") ax.set_xlabel("X position [m]") ax.set_ylabel("Y position [m]") + +plt.show() From e4f845643b0989ea6604e64181c1802ddc151aae Mon Sep 17 00:00:00 2001 From: Jakub Klus <48711526+deep-jkl@users.noreply.github.com> Date: Tue, 28 Sep 2021 09:26:39 +0200 Subject: [PATCH 19/22] Update docstrings according to jklymak Apply suggestions from @jklymak code review Co-authored-by: Jody Klymak --- .../next_whats_new/extending_MarkerStyle.rst | 5 ++-- .../marker_reference.py | 14 ++++------ .../multivariate_marker_plot.py | 7 +++-- lib/matplotlib/markers.py | 27 +++++++------------ 4 files changed, 20 insertions(+), 33 deletions(-) diff --git a/doc/users/next_whats_new/extending_MarkerStyle.rst b/doc/users/next_whats_new/extending_MarkerStyle.rst index fbe0fadd7f0c..6e970d0738fe 100644 --- a/doc/users/next_whats_new/extending_MarkerStyle.rst +++ b/doc/users/next_whats_new/extending_MarkerStyle.rst @@ -1,9 +1,8 @@ New customization of MarkerStyle -------------------------------- -New MarkerStyle parameters allow control of join style and cap style. -The appearance of individual markers can be further controlled by -transform supplied during creation. +New MarkerStyle parameters allow control of join style and cap style, and for +the user to supply a transformation to be applied to the marker (e.g. a rotation). .. plot:: :include-source: true diff --git a/examples/lines_bars_and_markers/marker_reference.py b/examples/lines_bars_and_markers/marker_reference.py index f751bfb975b8..ca9d1a77470a 100644 --- a/examples/lines_bars_and_markers/marker_reference.py +++ b/examples/lines_bars_and_markers/marker_reference.py @@ -166,10 +166,8 @@ def split_list(a_list): # Advanced marker modifications with transform # ============================================ # -# All markers can be modified by a user transform in MarkerStyle constructor. -# Supplied transform is combined with the default transforms needed for -# selected marker shape (e.g. caret up, caret down). Following example shows -# how user supplied rotation applies to several marker shapes. +# Markers can be modified by passing a transform to the MarkerStyle constructor. +# Following example shows how a supplied rotation is applied to several marker shapes. common_style = {k: v for k, v in filled_marker_style.items() if k != 'marker'} angles = [0, 10, 20, 30, 45, 60, 90] @@ -204,9 +202,8 @@ def split_list(a_list): # Setting marker cap style and join style # ======================================= # -# All markers have predefined cap style and join style, but this can be -# overriden during creation of MarkerStyle. Follwing example show how to -# change the cap style and how different styles look. +# Markers have default cap and join styles, but these can be +# customized when creating a MarkerStyle. from matplotlib.markers import JoinStyle, CapStyle @@ -240,8 +237,7 @@ def split_list(a_list): plt.show() ############################################################################### -# Follwing example show how to change the join style and how different styles -# looks like. +# Modifying the join style: fig, ax = plt.subplots() fig.suptitle('Marker JoinStyle', fontsize=14) diff --git a/examples/lines_bars_and_markers/multivariate_marker_plot.py b/examples/lines_bars_and_markers/multivariate_marker_plot.py index 0a79026b2772..284b21571d59 100644 --- a/examples/lines_bars_and_markers/multivariate_marker_plot.py +++ b/examples/lines_bars_and_markers/multivariate_marker_plot.py @@ -4,10 +4,9 @@ ============================================== This example shows how to use different properties of markers to plot -multivariate datasets. Following example shows an illustrative case of -plotting success of baseball throw as an smiley face with size mapped to -the skill of thrower, rotation mapped to the take-off angle, and thrust -to the color of the marker. +multivariate datasets. Here we represent a successful baseball throw as a smiley +face with marker size mapped to the skill of thrower, marker rotation to the take-off angle, +and thrust to the marker color. """ import numpy as np diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index 6a839d040495..1436a8434d60 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -82,13 +82,9 @@ plt.plot([1, 2, 3], marker=11) plt.plot([1, 2, 3], marker=matplotlib.markers.CARETDOWNBASE) -Markers have some reasonable default settings for join and cap styles. -However, those can be overriden when creating a new instance of MarkerStyle. -Furthermore, the marker shape can be modified by supplying -`~matplotlib.transforms.Transform` during creation. -Some markers are created as internally rotated shapes (e.g. triangles). -For such cases, both internal and user supplied transforms are combined. - +Markers join and cap styles can be customized by creating a new instance of MarkerStyle. +A MarkerStyle can also have a custom +`~matplotlib.transforms.Transform` allowing it to be arbitrarily rotated or offset. Examples showing the use of markers: * :doc:`/gallery/lines_bars_and_markers/marker_reference` @@ -253,16 +249,13 @@ def __init__(self, marker=_unset, fillstyle=None, One of 'full', 'left', 'right', 'bottom', 'top', 'none'. transform : Affine2D, default: None - User supplied transformation that will be combined with the - native transformation of selected marker. + Transform that will be combined with the native transform of the marker. capstyle : CapStyle, default: None - User supplied cap style that will override the default cap - style of selected marker. + Cap style that will override the default cap style of the marker. joinstyle : JoinStyle, default: None - User supplied join style that will override the default cap - style of selected marker. + Join style that will override the default join style of the marker. """ self._marker_function = None self._user_transform = transform @@ -431,7 +424,7 @@ def get_user_transform(self): def transformed(self, transform: Affine2D): """ - Return new marker with combined transformation. + Return a new version of this marker with the transform applied. Parameters ---------- @@ -447,15 +440,15 @@ def transformed(self, transform: Affine2D): def rotated(self, deg=None, rad=None): """ - Return new marker rotated by specified angle. + Return a new version of this marker rotated by specified angle. Parameters ---------- deg : float, default: None - Use this parameter to specify rotation angle in degrees. + Rotation angle in degrees. rad : float, default: None - Use this parameter to specify rotation angle in radians. + Rotation angle in radians. .. note:: You must specify exactly one of deg or rad. """ From 5aa5bbd071acd6285f43c387a6a8538ffa57520a Mon Sep 17 00:00:00 2001 From: Jakub Klus <48711526+deep-jkl@users.noreply.github.com> Date: Tue, 28 Sep 2021 09:31:55 +0200 Subject: [PATCH 20/22] Make MarkerStyle.rotated more concise Apply suggestions from code review Co-authored-by: Jody Klymak --- lib/matplotlib/markers.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index 1436a8434d60..a2189ea23d6a 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -438,7 +438,7 @@ def transformed(self, transform: Affine2D): new_marker._user_transform = transform return new_marker - def rotated(self, deg=None, rad=None): + def rotated(self, *, deg=None, rad=None): """ Return a new version of this marker rotated by specified angle. @@ -452,9 +452,10 @@ def rotated(self, deg=None, rad=None): .. note:: You must specify exactly one of deg or rad. """ - if not (deg is None) ^ (rad is None): - raise ValueError("Exactly one of deg or rad shall be used.") - + if deg is None and rad is None: + raise ValueError('One of deg or rad is required') + if deg is not None and rad is not None: + raise ValueError('Only one of deg and rad can be supplied') new_marker = MarkerStyle(self) if new_marker._user_transform is None: new_marker._user_transform = Affine2D() From 214ea3f8fc9d6313c857ad6ffe8b13457a1a58d0 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Tue, 28 Sep 2021 10:37:44 +0200 Subject: [PATCH 21/22] Refactor code, linting. --- .../marker_reference.py | 5 +-- .../multivariate_marker_plot.py | 6 ++-- lib/matplotlib/markers.py | 34 +++++++++---------- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/examples/lines_bars_and_markers/marker_reference.py b/examples/lines_bars_and_markers/marker_reference.py index ca9d1a77470a..c4564d2b027d 100644 --- a/examples/lines_bars_and_markers/marker_reference.py +++ b/examples/lines_bars_and_markers/marker_reference.py @@ -166,8 +166,9 @@ def split_list(a_list): # Advanced marker modifications with transform # ============================================ # -# Markers can be modified by passing a transform to the MarkerStyle constructor. -# Following example shows how a supplied rotation is applied to several marker shapes. +# Markers can be modified by passing a transform to the MarkerStyle +# constructor. Following example shows how a supplied rotation is applied to +# several marker shapes. common_style = {k: v for k, v in filled_marker_style.items() if k != 'marker'} angles = [0, 10, 20, 30, 45, 60, 90] diff --git a/examples/lines_bars_and_markers/multivariate_marker_plot.py b/examples/lines_bars_and_markers/multivariate_marker_plot.py index 284b21571d59..487bb2b85fd3 100644 --- a/examples/lines_bars_and_markers/multivariate_marker_plot.py +++ b/examples/lines_bars_and_markers/multivariate_marker_plot.py @@ -4,9 +4,9 @@ ============================================== This example shows how to use different properties of markers to plot -multivariate datasets. Here we represent a successful baseball throw as a smiley -face with marker size mapped to the skill of thrower, marker rotation to the take-off angle, -and thrust to the marker color. +multivariate datasets. Here we represent a successful baseball throw as a +smiley face with marker size mapped to the skill of thrower, marker rotation to +the take-off angle, and thrust to the marker color. """ import numpy as np diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index a2189ea23d6a..85be6d7651bc 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -82,9 +82,11 @@ plt.plot([1, 2, 3], marker=11) plt.plot([1, 2, 3], marker=matplotlib.markers.CARETDOWNBASE) -Markers join and cap styles can be customized by creating a new instance of MarkerStyle. -A MarkerStyle can also have a custom -`~matplotlib.transforms.Transform` allowing it to be arbitrarily rotated or offset. +Markers join and cap styles can be customized by creating a new instance of +MarkerStyle. +A MarkerStyle can also have a custom `~matplotlib.transforms.Transform` +allowing it to be arbitrarily rotated or offset. + Examples showing the use of markers: * :doc:`/gallery/lines_bars_and_markers/marker_reference` @@ -150,14 +152,6 @@ _empty_path = Path(np.empty((0, 2))) -def _fast_transform_combine(t1, t2): - """Combine two transformations where the second one can be None.""" - if t2 is None: - return t1.frozen() - else: - return (t1 + t2).frozen() - - class MarkerStyle: """ A class representing marker types. @@ -248,8 +242,9 @@ def __init__(self, marker=_unset, fillstyle=None, fillstyle : str, default: :rc:`markers.fillstyle` One of 'full', 'left', 'right', 'bottom', 'top', 'none'. - transform : Affine2D, default: None - Transform that will be combined with the native transform of the marker. + transform : transforms.Transform, default: None + Transform that will be combined with the native transform of the + marker. capstyle : CapStyle, default: None Cap style that will override the default cap style of the marker. @@ -395,7 +390,10 @@ def get_transform(self): Return the transform to be applied to the `.Path` from `MarkerStyle.get_path()`. """ - return _fast_transform_combine(self._transform, self._user_transform) + if self._user_transform is None: + return self._transform.frozen() + else: + return (self._transform + self._user_transform).frozen() def get_alt_path(self): """ @@ -411,8 +409,10 @@ def get_alt_transform(self): Return the transform to be applied to the `.Path` from `MarkerStyle.get_alt_path()`. """ - return _fast_transform_combine(self._alt_transform, - self._user_transform) + if self._user_transform is None: + return self._alt_transform.frozen() + else: + return (self._alt_transform + self._user_transform).frozen() def get_snap_threshold(self): return self._snap_threshold @@ -424,7 +424,7 @@ def get_user_transform(self): def transformed(self, transform: Affine2D): """ - Return a new version of this marker with the transform applied. + Return a new version of this marker with the transform applied. Parameters ---------- From 7101e3c1216c48e65711a97926ccb56027d06c94 Mon Sep 17 00:00:00 2001 From: Jakub Klus Date: Tue, 28 Sep 2021 15:30:05 +0200 Subject: [PATCH 22/22] Rework get_cap(join)style, add tests Check for user style in get_cap(join)style is removed. This is also supported with two new tests. Additional test checks that get_transform returns combination of supplied and internal transformation. --- lib/matplotlib/markers.py | 4 ++-- lib/matplotlib/tests/test_marker.py | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index 85be6d7651bc..b1d6fa2be6a4 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -322,10 +322,10 @@ def _set_fillstyle(self, fillstyle): self._recache() def get_joinstyle(self): - return self._user_joinstyle or self._joinstyle + return self._joinstyle def get_capstyle(self): - return self._user_capstyle or self._capstyle + return self._capstyle def get_marker(self): return self._marker diff --git a/lib/matplotlib/tests/test_marker.py b/lib/matplotlib/tests/test_marker.py index d3665d9ce09d..894dead24e84 100644 --- a/lib/matplotlib/tests/test_marker.py +++ b/lib/matplotlib/tests/test_marker.py @@ -207,6 +207,30 @@ def test_marker_clipping(fig_ref, fig_test): ax_test.axis('off') +def test_marker_init_transforms(): + """Test that initializing marker with transform is a simple addition.""" + marker = markers.MarkerStyle("o") + t = Affine2D().translate(1, 1) + t_marker = markers.MarkerStyle("o", transform=t) + assert marker.get_transform() + t == t_marker.get_transform() + + +def test_marker_init_joinstyle(): + marker = markers.MarkerStyle("*") + jstl = markers.JoinStyle.round + styled_marker = markers.MarkerStyle("*", joinstyle=jstl) + assert styled_marker.get_joinstyle() == jstl + assert marker.get_joinstyle() != jstl + + +def test_marker_init_captyle(): + marker = markers.MarkerStyle("*") + capstl = markers.CapStyle.round + styled_marker = markers.MarkerStyle("*", capstyle=capstl) + assert styled_marker.get_capstyle() == capstl + assert marker.get_capstyle() != capstl + + @pytest.mark.parametrize("marker,transform,expected", [ (markers.MarkerStyle("o"), Affine2D().translate(1, 1), Affine2D().translate(1, 1)), pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

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:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy