Reputation: 75
I need a parametric form for a matplotlib.path.Path
. So I used the .vertices
attribute, and it works fine except that the number of points given is too low for the use I want. Here is a code to illustrate :
import numpy as np
from matplotlib import pyplot as plt
import matplotlib.patches as mpat
fig, ax = plt.subplots()
ax.set(xlim=(-6, 6), ylim=(-6, 6))
# generate a circular path
circle = mpat.Arc((0, 0), 10, 10, theta1=20, theta2=220, color='green')
path = circle.get_transform().transform_path(circle.get_path()).cleaned().vertices[:-3] # get_path is not enough because of the transformation, so we apply to the path the same transformation as the circle has got from the identity circle
ax.add_patch(circle)
# plot path vertices
plt.scatter(x=path[:, 0], y=path[:, 1], color='red', s=2)
shape = len(path)
plt.show()
How to increase the number of points (red) to fetch better the path (green)? Or, how can I increase the len
of path
?
Thanks in advance!
Upvotes: 4
Views: 258
Reputation: 15243
This annoyed me enough that I wrote a simple alternative implementation. It doesn't rely on Arc
at all, and works fine:
import typing
import numpy as np
from matplotlib.patches import PathPatch
from matplotlib.path import Path
if typing.TYPE_CHECKING:
from matplotlib.transforms import Transform
from matplotlib.typing import ColourType
def hires_arc(
centre: tuple[float, float],
radius: float,
theta1: float,
theta2: float,
n: int = 10,
colour: typing.Optional['ColourType'] = None,
transform: typing.Optional['Transform'] = None,
zorder: int | None = None,
) -> PathPatch:
"""
matplotlib's Arc patch looks pretty bad; it has a low and non-configurable resolution. This does
basically the same thing but with controllable resolution.
:param centre: x, y pair, centre of circle
:param radius: from centre
:param theta1: start angle, radians
:param theta2: end angle, radians
:param n: number of vertices
:param colour: passed to the patch
:param transform: passed to the patch
:param zorder: passed to the patch
"""
codes = np.full(shape=n, fill_value=Path.LINETO, dtype=Path.code_type)
codes[0] = Path.MOVETO
angles = np.linspace(start=theta1, stop=theta2, num=n)
vertices = centre + radius*np.stack((
np.cos(angles), np.sin(angles),
), axis=1)
return PathPatch(
path=Path(vertices=vertices, codes=codes),
edgecolor=colour, facecolor='none', zorder=zorder, transform=transform,
)
Alternatively, transform a path with the built-in Path.arc
:
scale = Affine2D().scale(radius).translate(
tx=centre[0], ty=centre[1],
)
path = Path.arc(
theta1=theta1, theta2=theta2, n=n,
).transformed(scale)
return PathPatch(
path=path, edgecolor=colour, facecolor='none', zorder=zorder, transform=transform,
)
Upvotes: 1
Reputation: 6417
It's not ideal, but you can just loop over many shorter arcs, e.g.,:
import numpy as np
from matplotlib import pyplot as plt
import matplotlib.patches as mpat
fig, ax = plt.subplots()
ax.set(xlim=(-6, 6), ylim=(-6, 6))
# generate a circular path
dr = 30 # change this to control the number of points
path = np.empty((0, 2))
for i in range(20, 221, dr):
circle = mpat.Arc((0, 0), 10, 10, theta1=i, theta2=(i + dr), color='green')
tmppath = circle.get_transform().transform_path(circle.get_path()).cleaned().vertices[:-1]
path = np.vstack((path, tmppath[:, 0:2]))
ax.add_patch(circle)
# plot path vertices
plt.scatter(x=path[:, 0], y=path[:, 1], color='red', s=2)
plt.show()
Update
Another option is to hack the Arc
class, so that during initialisation, the Path.arc
takes in the n
keyword. E.g.,
from matplotlib.patches import Path, Arc
class NewArc(Arc):
def __init__(self, xy, width, height, angle=0.0, theta1=0.0, theta2=360.0, n=30, **kwargs):
"""
An Arc class with the n keyword argument
"""
fill = kwargs.setdefault('fill', False)
if fill:
raise ValueError("Arc objects can not be filled")
super().__init__(xy, width, height, angle=angle, **kwargs)
self.theta1 = theta1
self.theta2 = theta2
(self._theta1, self._theta2, self._stretched_width,
self._stretched_height) = self._theta_stretch()
# add in the n keyword here
self._path = Path.arc(self._theta1, self._theta2, n=n)
The original code (without the for loop) can then be used and the number of points increased, e.g.,
from matplotlib import pyplot as plt
fig, ax = plt.subplots()
ax.set(xlim=(-6, 6), ylim=(-6, 6))
# generate a circular path using NewArc and the n keyword
circle = NewArc((0, 0), 10, 10, theta1=20, theta2=220, n=40, color='green')
path = circle.get_transform().transform_path(circle.get_path()).cleaned().vertices[:-3]
ax.add_patch(circle)
# plot path vertices
ax.scatter(x=path[:, 0], y=path[:, 1], color='red', s=2)
fig.show()
Upvotes: 1