首页 > 解决方案 > 在 matplotlib 中旋转(自定义路径)标记时的尺寸失真

问题描述

我正在使用路径为使用 matplotlib 包的绘图定义自定义标记。此外,我需要能够旋转标记以显示方向。

我按照这个例子,标记旋转。但是,根据角度,标记的大小会发生变化。

这是我正在使用的代码:

import matplotlib.pyplot as plt

from matplotlib.path import Path
from matplotlib import transforms
import matplotlib.patches as patches

fig = plt.figure(figsize=(10, 5))
ax = fig.add_subplot(1, 1, 1)

ax.set_xlim(-10, 10)
ax.set_ylim(-4, 4)

colors = ax.set_prop_cycle(color = ['#993F00', '#0075DC'])

triangle = Path([[-1.,-1.], [1., -1.], [0., 2.], [0.,0.],], [Path.MOVETO, Path.LINETO, Path.LINETO, 
    Path.CLOSEPOLY,])

print("First Triangle:", triangle, type(triangle))
#patch1 = patches.PathPatch(triangle, facecolor='orange', lw=2)
line1, = ax.plot(0, 0, marker=triangle, markersize=50, alpha=0.5)

R = transforms.Affine2D().rotate_deg(45)

triangle = triangle.transformed(R)
#patch2 = patches.PathPatch(triangle, facecolor='blue', lw=2)
line2, = ax.plot(0, 0, marker=triangle, markersize=50, alpha=0.5)

print("\n transform R", R)
print("\ntriangle after transform", triangle, type(triangle))

#ax.add_patch(patch1)
#ax.add_patch(patch2)

plt.show()

注释掉的行将路径转换为补丁(顺便说一句,如果有人愿意解释补丁是什么与路径,我将不胜感激)以表明底层路径上的转换工作正常,它只是扭曲大小当你绘制它。

这是当前程序的输出:

link to imgur

谢谢!

标签: pythonmatplotlib

解决方案


The problem is that the Path that is taken as input for the marker is normalized to the box (-0.5, 0.5)x(-0.5, 0.5). This is often useful because it allows to plug in arbitrarily scaled paths and still get the same sized markers out.
However in this case it will lead to the tips of the triangles to share the same y coodinate (both times y=0.5 in the pre-transformed system).

The only solution I can come up with is to subclass matplotlib.markers.MarkerStyle and replace the rescaling method with a method that does not change the path.

Now the problem is that plot does not currently take MarkerStyle instances as input for its marker argument. Hence the below solution only works for scatter.

import matplotlib.pyplot as plt
from matplotlib.path import Path
from matplotlib import transforms
from matplotlib.markers import MarkerStyle

class UnsizedMarker(MarkerStyle):
    def _set_custom_marker(self, path):
        self._transform = transforms.IdentityTransform()
        self._path = path


fig = plt.figure(figsize=(10, 5))
ax = fig.add_subplot(1, 1, 1)

ax.set_xlim(-10, 10)
ax.set_ylim(-4, 4)

colors = ax.set_prop_cycle(color = ['#993F00', '#0075DC'])

triangle1 = Path([[-1.,-1.], [1., -1.], [0., 2.], [0.,0.],], 
                [Path.MOVETO, Path.LINETO, Path.LINETO, Path.CLOSEPOLY,])


m1 = UnsizedMarker(triangle1)
line1 = ax.scatter(0, 0, marker=m1, s=50**2, alpha=0.5)

R = transforms.Affine2D().rotate_deg(45)
triangle2 = triangle1.transformed(R)
m2 = UnsizedMarker(triangle2)
line2 = ax.scatter(0, 0, marker=m2, s=50**2, alpha=0.5)


plt.show()

enter image description here


Unrelated to that, the question also asks for what a Patch is vs. a Path. A Path is an object that stores vertices and their respective properties. It's main purpose, which makes this more useful than a simple container of two lists or coordinates, is that it has methods to calculate Bezier curves between points. A path by itself can however not be added to a figure.

A Patch is a matplotlib artist, i.e. an object which can be drawn in a matplotlib figure. Matplotlib provides some useful patches like arrows, rectangles, circles etc. A PathPatch is such a patch which creates a visualization of a Path. It's hence the most obvious way to draw a path in a figure.


推荐阅读