Size distortion when rotating (custom path) marker in matplotlib

Viewed 363

I'm using a path to define a custom marker for a plot using the matplotlib package. In addition, I need to be able to rotate the marker to show direction.

I followed this example, and the marker rotates. However, depending on the angle, the size of the marker changes.

Here is the code I am using:

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()

The commented out lines convert the path to patches (incidentally, if anyone is willing to explain what a patch is vs a path, I'd be grateful) to show that the transformation on the underlying path works fine, it just warps size when you plot it.

This is the output of the current program:

link to imgur

Thank you!

1 Answers

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.

Related