Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 92192aa

Browse filesBrowse files
marker-transforms
1 parent b42d6d9 commit 92192aa
Copy full SHA for 92192aa

File tree

Expand file treeCollapse file tree

4 files changed

+121
-14
lines changed
Filter options
Expand file treeCollapse file tree

4 files changed

+121
-14
lines changed
+12Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Allow for custom marker scaling
2+
-------------------------------
3+
`~.markers.MarkerStyle` gained a keyword argument *normalization*, which may be
4+
set to *"none"* to allow for custom paths to not be scaled.::
5+
6+
MarkerStyle(Path(...), normalization="none")
7+
8+
`~.markers.MarkerStyle` also gained a `~.markers.MarkerStyle`.set_transform`
9+
method to set affine transformations to existing markers.::
10+
11+
m = MarkerStyle("d")
12+
m.set_transform(m.get_transform() + Affine2D().rotate_deg(30))

‎examples/lines_bars_and_markers/scatter_piecharts.py

Copy file name to clipboardExpand all lines: examples/lines_bars_and_markers/scatter_piecharts.py
+58-7Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,19 @@
33
Scatter plot with pie chart markers
44
===================================
55
6-
This example makes custom 'pie charts' as the markers for a scatter plot.
7-
8-
Thanks to Manuel Metz for the example.
6+
This example shows two methods to make custom 'pie charts' as the markers
7+
for a scatter plot.
98
"""
109

10+
##########################################################################
11+
# Manually creating marker vertices
12+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
13+
#
14+
1115
import numpy as np
1216
import matplotlib.pyplot as plt
1317

14-
# first define the ratios
18+
# first define the cumulative ratios
1519
r1 = 0.2 # 20%
1620
r2 = r1 + 0.4 # 40%
1721

@@ -36,10 +40,55 @@
3640
s3 = np.abs(xy3).max()
3741

3842
fig, ax = plt.subplots()
39-
ax.scatter(range(3), range(3), marker=xy1, s=s1**2 * sizes, facecolor='blue')
40-
ax.scatter(range(3), range(3), marker=xy2, s=s2**2 * sizes, facecolor='green')
41-
ax.scatter(range(3), range(3), marker=xy3, s=s3**2 * sizes, facecolor='red')
43+
ax.scatter(range(3), range(3), marker=xy1, s=s1**2 * sizes, facecolor='C0')
44+
ax.scatter(range(3), range(3), marker=xy2, s=s2**2 * sizes, facecolor='C1')
45+
ax.scatter(range(3), range(3), marker=xy3, s=s3**2 * sizes, facecolor='C2')
46+
47+
plt.show()
48+
49+
50+
##########################################################################
51+
# Using wedges as markers
52+
# ~~~~~~~~~~~~~~~~~~~~~~~
53+
#
54+
# An alternative is to create custom markers from the `~.path.Path` of a
55+
# `~.patches.Wedge`, which might be more versatile.
56+
#
57+
58+
import numpy as np
59+
import matplotlib.pyplot as plt
60+
from matplotlib.patches import Wedge
61+
from matplotlib.markers import MarkerStyle
62+
63+
# first define the ratios
64+
r1 = 0.2 # 20%
65+
r2 = r1 + 0.3 # 50%
66+
r3 = 1 - r1 - r2 # 30%
67+
68+
69+
def markers_from_ratios(ratios, width=1):
70+
markers = []
71+
angles = 360*np.concatenate(([0], np.cumsum(ratios)))
72+
for i in range(len(angles)-1):
73+
# create a Wedge within the unit square in between the given angles...
74+
w = Wedge((0, 0), 0.5, angles[i], angles[i+1], width=width/2)
75+
# ... and create a custom Marker from its path.
76+
markers.append(MarkerStyle(w.get_path(), normalization="none"))
77+
return markers
78+
79+
# define some sizes of the scatter marker
80+
sizes = np.array([100, 200, 400, 800])
81+
# collect the markers and some colors
82+
markers = markers_from_ratios([r1, r2, r3], width=0.6)
83+
colors = plt.cm.tab10.colors[:len(markers)]
84+
85+
fig, ax = plt.subplots()
86+
87+
for marker, color in zip(markers, colors):
88+
ax.scatter(range(len(sizes)), range(len(sizes)), marker=marker, s=sizes,
89+
edgecolor="none", facecolor=color)
4290

91+
ax.margins(0.1)
4392
plt.show()
4493

4594
#############################################################################
@@ -55,3 +104,5 @@
55104
import matplotlib
56105
matplotlib.axes.Axes.scatter
57106
matplotlib.pyplot.scatter
107+
matplotlib.patches.Wedge
108+
matplotlib.markers.MarkerStyle

‎lib/matplotlib/markers.py

Copy file name to clipboardExpand all lines: lib/matplotlib/markers.py
+27-7Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,8 @@ class MarkerStyle:
201201
# TODO: Is this ever used as a non-constant?
202202
_point_size_reduction = 0.5
203203

204-
def __init__(self, marker=None, fillstyle=None):
204+
def __init__(self, marker=None, fillstyle=None, *,
205+
normalization="classic"):
205206
"""
206207
Attributes
207208
----------
@@ -213,18 +214,31 @@ def __init__(self, marker=None, fillstyle=None):
213214
214215
Parameters
215216
----------
216-
marker : str or array-like, optional, default: None
217+
marker : str, array-like, `~.path.Path`, or `~.markers.MarkerStyle`, \
218+
default: None
217219
See the descriptions of possible markers in the module docstring.
218220
219221
fillstyle : str, optional, default: 'full'
220222
'full', 'left", 'right', 'bottom', 'top', 'none'
223+
224+
normalization : str, {'classic', 'none'}, optional, default: "classic"
225+
The normalization of the marker size. Only applies to custom paths
226+
that are provided as array of vertices or `~.path.Path`.
227+
Can take two values:
228+
*'classic'*, being the default, makes sure the marker path is
229+
normalized to fit within a unit-square by affine scaling.
230+
*'none'*, in which case no scaling is performed on the marker path.
221231
"""
232+
if normalization not in ["classic", "none"]:
233+
raise ValueError("normalization={!r} is not valid; it must be "
234+
"'classic' or 'none'".format(normalization))
235+
self._normalize = normalization
222236
self._marker_function = None
223237
self.set_fillstyle(fillstyle)
224238
self.set_marker(marker)
225239

226240
def _recache(self):
227-
if self._marker_function is None:
241+
if self._marker_function is None or self._normalize == "none":
228242
return
229243
self._path = _empty_path
230244
self._transform = IdentityTransform()
@@ -303,6 +317,13 @@ def get_path(self):
303317
def get_transform(self):
304318
return self._transform.frozen()
305319

320+
def set_transform(self, transform):
321+
"""
322+
Sets the transform of the marker. This is the transform by which the
323+
marker path is transformed.
324+
"""
325+
self._transform = transform
326+
306327
def get_alt_path(self):
307328
return self._alt_path
308329

@@ -316,8 +337,9 @@ def _set_nothing(self):
316337
self._filled = False
317338

318339
def _set_custom_marker(self, path):
319-
rescale = np.max(np.abs(path.vertices)) # max of x's and y's.
320-
self._transform = Affine2D().scale(0.5 / rescale)
340+
if self._normalize == "classic":
341+
rescale = np.max(np.abs(path.vertices)) # max of x's and y's.
342+
self._transform = Affine2D().scale(0.5 / rescale)
321343
self._path = path
322344

323345
def _set_path_marker(self):
@@ -349,8 +371,6 @@ def _set_tuple_marker(self):
349371
def _set_mathtext_path(self):
350372
"""
351373
Draws mathtext markers '$...$' using TextPath object.
352-
353-
Submitted by tcb
354374
"""
355375
from matplotlib.text import TextPath
356376

‎lib/matplotlib/tests/test_marker.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_marker.py
+24Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import numpy as np
22
from matplotlib import markers
33
from matplotlib.path import Path
4+
from matplotlib.transforms import Affine2D
5+
import matplotlib.pyplot as plt
46

7+
from matplotlib.testing.decorators import check_figures_equal
58
import pytest
69

710

@@ -26,3 +29,24 @@ def test_marker_path():
2629
path = Path([[0, 0], [1, 0]], [Path.MOVETO, Path.LINETO])
2730
# Checking this doesn't fail.
2831
marker_style.set_marker(path)
32+
33+
34+
@check_figures_equal(extensions=["png"])
35+
def test_marker_normalization(fig_test, fig_ref):
36+
plt.style.use("mpl20")
37+
38+
ax = fig_ref.subplots()
39+
ax.margins(0.3)
40+
ax.scatter([0, 1], [0, 0], s=400, marker="s", c="C2")
41+
42+
ax = fig_test.subplots()
43+
ax.margins(0.3)
44+
# test normalize
45+
p = Path([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]], closed=True)
46+
p1 = p.transformed(Affine2D().translate(-.5, -.5).scale(20))
47+
m1 = markers.MarkerStyle(p1, normalization="none")
48+
ax.scatter([0], [0], s=1, marker=m1, c="C2")
49+
# test transform
50+
m2 = markers.MarkerStyle("s")
51+
m2.set_transform(m2.get_transform() + Affine2D().scale(20))
52+
ax.scatter([1], [0], s=1, marker=m2, c="C2")

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.