From 824ce2bb82dce5bc5926533d07331cf55bd93c8d Mon Sep 17 00:00:00 2001 From: ImportanceOfBeingErnest Date: Tue, 10 Mar 2020 02:54:46 +0100 Subject: [PATCH] marker-transforms --- .../2020-03-16_markerstyle_normalization.rst | 12 ++++ .../scatter_piecharts.py | 65 +++++++++++++++++-- lib/matplotlib/markers.py | 30 +++++++-- lib/matplotlib/tests/test_marker.py | 24 ++++++- 4 files changed, 117 insertions(+), 14 deletions(-) create mode 100644 doc/users/next_whats_new/2020-03-16_markerstyle_normalization.rst diff --git a/doc/users/next_whats_new/2020-03-16_markerstyle_normalization.rst b/doc/users/next_whats_new/2020-03-16_markerstyle_normalization.rst new file mode 100644 index 000000000000..dce8f34e67bf --- /dev/null +++ b/doc/users/next_whats_new/2020-03-16_markerstyle_normalization.rst @@ -0,0 +1,12 @@ +Allow for custom marker scaling +------------------------------- +`~.markers.MarkerStyle` gained a keyword argument *normalization*, which may be +set to *"none"* to allow for custom paths to not be scaled.:: + + MarkerStyle(Path(...), normalization="none") + +`~.markers.MarkerStyle` also gained a `~.markers.MarkerStyle.set_transform` +method to set affine transformations to existing markers.:: + + m = MarkerStyle("d") + m.set_transform(m.get_transform() + Affine2D().rotate_deg(30)) diff --git a/examples/lines_bars_and_markers/scatter_piecharts.py b/examples/lines_bars_and_markers/scatter_piecharts.py index 6b2b4aa88824..b24f5fd2af8a 100644 --- a/examples/lines_bars_and_markers/scatter_piecharts.py +++ b/examples/lines_bars_and_markers/scatter_piecharts.py @@ -3,15 +3,19 @@ Scatter plot with pie chart markers =================================== -This example makes custom 'pie charts' as the markers for a scatter plot. - -Thanks to Manuel Metz for the example. +This example shows two methods to make custom 'pie charts' as the markers +for a scatter plot. """ +########################################################################## +# Manually creating marker vertices +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# + import numpy as np import matplotlib.pyplot as plt -# first define the ratios +# first define the cumulative ratios r1 = 0.2 # 20% r2 = r1 + 0.4 # 40% @@ -36,10 +40,55 @@ s3 = np.abs(xy3).max() fig, ax = plt.subplots() -ax.scatter(range(3), range(3), marker=xy1, s=s1**2 * sizes, facecolor='blue') -ax.scatter(range(3), range(3), marker=xy2, s=s2**2 * sizes, facecolor='green') -ax.scatter(range(3), range(3), marker=xy3, s=s3**2 * sizes, facecolor='red') +ax.scatter(range(3), range(3), marker=xy1, s=s1**2 * sizes, facecolor='C0') +ax.scatter(range(3), range(3), marker=xy2, s=s2**2 * sizes, facecolor='C1') +ax.scatter(range(3), range(3), marker=xy3, s=s3**2 * sizes, facecolor='C2') + +plt.show() + + +########################################################################## +# Using wedges as markers +# ~~~~~~~~~~~~~~~~~~~~~~~ +# +# An alternative is to create custom markers from the `~.path.Path` of a +# `~.patches.Wedge`, which might be more versatile. +# + +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.patches import Wedge +from matplotlib.markers import MarkerStyle + +# first define the ratios +r1 = 0.2 # 20% +r2 = r1 + 0.3 # 50% +r3 = 1 - r1 - r2 # 30% + + +def markers_from_ratios(ratios, width=1): + markers = [] + angles = 360*np.concatenate(([0], np.cumsum(ratios))) + for i in range(len(angles)-1): + # create a Wedge within the unit square in between the given angles... + w = Wedge((0, 0), 0.5, angles[i], angles[i+1], width=width/2) + # ... and create a custom Marker from its path. + markers.append(MarkerStyle(w.get_path(), normalization="none")) + return markers + +# define some sizes of the scatter marker +sizes = np.array([100, 200, 400, 800]) +# collect the markers and some colors +markers = markers_from_ratios([r1, r2, r3], width=0.6) +colors = plt.cm.tab10.colors[:len(markers)] + +fig, ax = plt.subplots() + +for marker, color in zip(markers, colors): + ax.scatter(range(len(sizes)), range(len(sizes)), marker=marker, s=sizes, + edgecolor="none", facecolor=color) +ax.margins(0.1) plt.show() ############################################################################# @@ -55,3 +104,5 @@ import matplotlib matplotlib.axes.Axes.scatter matplotlib.pyplot.scatter +matplotlib.patches.Wedge +matplotlib.markers.MarkerStyle diff --git a/lib/matplotlib/markers.py b/lib/matplotlib/markers.py index ccbdce01116b..3d36bb0c947c 100644 --- a/lib/matplotlib/markers.py +++ b/lib/matplotlib/markers.py @@ -201,7 +201,8 @@ class MarkerStyle: # TODO: Is this ever used as a non-constant? _point_size_reduction = 0.5 - def __init__(self, marker=None, fillstyle=None): + def __init__(self, marker=None, fillstyle=None, *, + normalization="classic"): """ Attributes ---------- @@ -213,12 +214,23 @@ def __init__(self, marker=None, fillstyle=None): Parameters ---------- - marker : str or array-like, optional, default: None + marker : str, array-like, `~.path.Path`, or `~.markers.MarkerStyle`, \ + default: None See the descriptions of possible markers in the module docstring. fillstyle : str, optional, default: 'full' 'full', 'left", 'right', 'bottom', 'top', 'none' + + normalization : str, {'classic', 'none'}, optional, default: "classic" + The normalization of the marker size. Only applies to custom paths + that are provided as array of vertices or `~.path.Path`. + Can take two values: + *'classic'*, being the default, makes sure the marker path is + normalized to fit within a unit-square by affine scaling. + *'none'*, in which case no scaling is performed on the marker path. """ + cbook._check_in_list(["classic", "none"], normalization=normalization) + self._normalize = normalization self._marker_function = None self.set_fillstyle(fillstyle) self.set_marker(marker) @@ -303,6 +315,13 @@ def get_path(self): def get_transform(self): return self._transform.frozen() + def set_transform(self, transform): + """ + Sets the transform of the marker. This is the transform by which the + marker path is transformed. + """ + self._transform = transform + def get_alt_path(self): return self._alt_path @@ -316,8 +335,9 @@ def _set_nothing(self): self._filled = False def _set_custom_marker(self, path): - rescale = np.max(np.abs(path.vertices)) # max of x's and y's. - self._transform = Affine2D().scale(0.5 / rescale) + if self._normalize == "classic": + rescale = np.max(np.abs(path.vertices)) # max of x's and y's. + self._transform = Affine2D().scale(0.5 / rescale) self._path = path def _set_path_marker(self): @@ -350,8 +370,6 @@ def _set_tuple_marker(self): def _set_mathtext_path(self): """ Draws mathtext markers '$...$' using TextPath object. - - Submitted by tcb """ from matplotlib.text import TextPath diff --git a/lib/matplotlib/tests/test_marker.py b/lib/matplotlib/tests/test_marker.py index e50746165792..f34c27896701 100644 --- a/lib/matplotlib/tests/test_marker.py +++ b/lib/matplotlib/tests/test_marker.py @@ -2,8 +2,9 @@ import matplotlib.pyplot as plt from matplotlib import markers from matplotlib.path import Path -from matplotlib.testing.decorators import check_figures_equal +from matplotlib.transforms import Affine2D +from matplotlib.testing.decorators import check_figures_equal import pytest @@ -133,3 +134,24 @@ def draw_ref_marker(y, style, size): ax_test.set(xlim=(-0.5, 1.5), ylim=(-0.5, 1.5)) ax_ref.set(xlim=(-0.5, 1.5), ylim=(-0.5, 1.5)) + + +@check_figures_equal(extensions=["png"]) +def test_marker_normalization(fig_test, fig_ref): + plt.style.use("mpl20") + + ax = fig_ref.subplots() + ax.margins(0.3) + ax.scatter([0, 1], [0, 0], s=400, marker="s", c="C2") + + ax = fig_test.subplots() + ax.margins(0.3) + # test normalize + p = Path([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]], closed=True) + p1 = p.transformed(Affine2D().translate(-.5, -.5).scale(20)) + m1 = markers.MarkerStyle(p1, normalization="none") + ax.scatter([0], [0], s=1, marker=m1, c="C2") + # test transform + m2 = markers.MarkerStyle("s") + m2.set_transform(m2.get_transform() + Affine2D().scale(20)) + ax.scatter([1], [0], s=1, marker=m2, c="C2") 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