19

I wanted to rotate a Rectangle in matplotlib but when I apply the transformation, the rectangle doesn't show anymore:

rect = mpl.patches.Rectangle((0.0120,0),0.1,1000)
t = mpl.transforms.Affine2D().rotate_deg(45)
rect.set_transform(t)

is this a known bug or do I make a mistake?

Mermoz
  • 14,898
  • 17
  • 60
  • 85
  • could you elaborate on the question, what exactly are you trying to do here? – steabert Nov 26 '10 at 14:05
  • I want to add a `Rectangle` to my `ax` (this works fine) but instead of a straight rectangle, I want it to be tilted of 45 degrees. The final aim is to represent a "cut" in the axis. – Mermoz Nov 26 '10 at 14:11

2 Answers2

29

The patch in the provided code makes it hard to tell what's going on, so I've made a clear demonstration that I worked out from a matplotlib example:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib as mpl

fig = plt.figure()
ax = fig.add_subplot(111)

r1 = patches.Rectangle((0,0), 20, 40, color="blue", alpha=0.50)
r2 = patches.Rectangle((0,0), 20, 40, color="red",  alpha=0.50)

t2 = mpl.transforms.Affine2D().rotate_deg(-45) + ax.transData
r2.set_transform(t2)

ax.add_patch(r1)
ax.add_patch(r2)

plt.xlim(-20, 60)
plt.ylim(-20, 60)

plt.grid(True)

plt.show()

enter image description here

Nick
  • 2,342
  • 28
  • 25
  • 1
    I just wanted to compliment you on a crisp answer that illustrates exactly what to do. Thank you. – Ram Narasimhan Mar 24 '20 at 19:33
  • 2
    Thanks, the code is great! Could you explain why the red rectangle isn't a rectangle anymore, but rather a parallelogram (angles are not 90˚ visually)? Is it some matplotlib-specific logic? – olha Mar 26 '20 at 09:16
  • 6
    @OlhaPavliuk I believe it's because the x & y axes are not scaled exactly the same. Notice the grid shows rectangles instead of squares. – Nick Mar 29 '20 at 04:29
  • 1
    add plt.gca().set_aspect('equal', adjustable='box') – decades Jun 30 '21 at 07:53
9

Apparently the transforms on patches are composites of several transforms for dealing with scaling and the bounding box. Adding the transform to the existing plot transform seems to give something more like what you'd expect. Though it looks like there's still an offset to work out.

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib as mpl
fig = plt.figure()
ax = fig.add_subplot(111)

rect = patches.Rectangle((0.0120,0),0.1,1000)

t_start = ax.transData
t = mpl.transforms.Affine2D().rotate_deg(-45)
t_end = t_start + t

rect.set_transform(t_end)

print repr(t_start)
print repr(t_end)
ax.add_patch(rect)

plt.show()
mjhm
  • 16,497
  • 10
  • 44
  • 55
  • I don't see why you need to add the `ax.transData`? – Mermoz Nov 26 '10 at 17:14
  • I'm honestly in the dark about it as well. I would have thought that t_start = rect.get_transform() would have been the right incantation, but that didn't work either. – mjhm Nov 26 '10 at 17:36
  • 1
    I think the answer is correct, so I'll just add this comment about why: the fact that it didn't work before is that if you attach a patch without transform, this is defaulted to transData, as you can see from the add_path documentation. So if you do set it before, you need to add this to your transform. @mjhm: if you want to use rect.get_transform() for t_start, then you have to create the Rectangle with a transform= option. – steabert Nov 27 '10 at 15:02
  • I think that `rect.get_transform` will work fine if you do `ax.add_patch(rect)` first – Mad Physicist Jun 23 '16 at 18:35
  • When you specify a transform for a graphics primitive, it _replaces_ the normal transform that the plotting system uses by default to map user co-ordinates to the display. So, after your transform you need to _add_ the transform that takes your (already transformed) co-ordinates and maps them to the display. – holdenweb Jan 28 '18 at 02:58