Movatterモバイル変換


[0]ホーム

URL:


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

Sign up
Appearance settings

ENH: Allow for non-normalized and transformed markers#16773

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Closed
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
marker-transforms
  • Loading branch information
@ImportanceOfBeingErnest
ImportanceOfBeingErnest committedMar 18, 2020
commit824ce2bb82dce5bc5926533d07331cf55bd93c8d
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -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))
65 changes: 58 additions & 7 deletionsexamples/lines_bars_and_markers/scatter_piecharts.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 thecumulativeratios
r1 = 0.2 # 20%
r2 = r1 + 0.4 # 40%

Expand All@@ -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()

#############################################################################
Expand All@@ -55,3 +104,5 @@
import matplotlib
matplotlib.axes.Axes.scatter
matplotlib.pyplot.scatter
matplotlib.patches.Wedge
matplotlib.markers.MarkerStyle
30 changes: 24 additions & 6 deletionslib/matplotlib/markers.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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"):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

is there a reason this can't be atransform= kwarg? ietransform=None would do what we did before...

"""
Attributes
----------
Expand All@@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

classic is a confusing name, because it overlaps with the classic style, but it'snot the classic style (I mean it isalso the classic style, but notonly the classic style.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I might be misreading this comment, but it seems like it's not thatclassic is a confusing name, but that the docstring here is not correct.

According to the discussion above. The docstring should read (if I understand correctly):

*'classic'*, being the default, ensures built-in markers maintain the size they have always had, and for custom markers it makes sure the marker path is normalized to fit within a unit-square by affine scaling.

This isexactly the classic style, as far as I can tell.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Hence the sentence "Only applies to custom paths...". So this is definitely correct.

I'm happy to use a name other than "classic", but since I find that name fits pretty well, I would need input with alternative ideas. I guess this is also secondary compared to naming the argument itself - for which there is also no consensus yet it seems.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

To be clear, I am 100% behind the "classic" name. I think it is consistently applied and fits well.

I am also very happy with "normalization".

However, wasn't one of the main benefits (and the impetus for this PR being revisited) that adding a "normalization" kwarg would allow us to address#15703? If so, I think that stating in the initial docstring that it only applies to custom paths (maybe just add "for now" if you don't like my more verbose wording).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I'm cautious to document any possible future behaviour. (We had some new normalization or the toolmanager being anounced as "coming soon" for years, which doesn't really help people using the library in the present.)
While it's important to think ahead and make sure to not block any roads (which is why we're having this discussion now), what's documented is theactual feature.

Copy link
Contributor

@brunobeltranbrunobeltranMar 31, 2020
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

100% onboard with not documenting "possible" future behavior. On the other hand, a wording closer to mine doesn't close the door to this feature (which is already implemented in my "paths" PR stack).

Mostly just being selfish because I have a strong feeling that if the current phrasing of the docstring gets merged, I'll get pushback when I try to change it in my own PR, whereas an equally specific and correct version could be written that doesn't specify that this keyword "only applies to custom paths".

As far as I'm concerned, feel free to merge this as-is and I'll just have that fight when it's time.

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)
Expand DownExpand Up@@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

First line of docstring should be separate.

Suggested change
Setsthetransformofthemarker.Thisisthetransformbywhichthe
Setsthetransformofthemarker.
Thisisthetransformbywhichthemarkerpathistransformed.

marker path is transformed.
"""
self._transform = transform

def get_alt_path(self):
return self._alt_path

Expand All@@ -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):
Expand DownExpand Up@@ -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

Expand Down
24 changes: 23 additions & 1 deletionlib/matplotlib/tests/test_marker.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,8 +2,9 @@
import matplotlib.pyplot as plt
from matplotlib import markers
from matplotlib.path import Path
from matplotlib.testing.decorators importcheck_figures_equal
from matplotlib.transforms importAffine2D

from matplotlib.testing.decorators import check_figures_equal
import pytest


Expand DownExpand Up@@ -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")

[8]ページ先頭

©2009-2025 Movatter.jp