3

I am developing a python GUI that plots many lines, arrows and rectangles on a matplotlib canvas. The rectangles go aligned with the lines: Rotated rectangle above line

Here is the picture.

I want to set a transform on the Rectangle, so that the side's length perpendicular to the line are in axes coordinates units (transAxes), and the sides parallel to the line are in data coordinates units (transData).

I know that blended_transform is can be used to define to different transforms for x-axis and y-axis. This is similar, but the directions in which the transforms are applied are not neccessary the horizontal and vertical direction. Is there a way of defining a custom blended transform that works on rotated directions instead of x-y directions? The documentation on transforms is not very helpful when trying to create a custom one.

Thanks!

Saad
  • 3,340
  • 2
  • 10
  • 32
  • I think it's pretty obvious that there is no blended coordinate system that can fulfill the requirement, simply because the two systems would be coupled by the rotation. So is the rotaton happening in Axes or Data coordinates? How would you define rotated Axes coordinates within a non-square axes? – ImportanceOfBeingErnest May 09 '19 at 21:59
  • It makes sense what you say about blended transform. Is there another way of doing what I need? I've seen very sophisticated transformations in the documentation, so I would find it surprising if there was no way. – Mariano Balbi May 10 '19 at 02:28

1 Answers1

4

The questions in the comments weren't answered, so one needs to make some assumptions. Let's say the rotation is supposed to happen in display space and the axes coordinates are those in y-axis direction. Then a possible transform could look like

trans = ax.get_xaxis_transform() + mtrans.Affine2D().rotate_deg(angle)

In this case the first dimension are data coordinates, the second are axes coordinates.

Some example:

import matplotlib.pyplot as plt
import matplotlib.transforms as mtrans

fig, ax = plt.subplots()
angle = 38 # degrees
trans = ax.get_xaxis_transform() + mtrans.Affine2D().rotate_deg(angle)

ax.plot([5,9],[0,0], marker="o", transform=trans)

rect = plt.Rectangle((5,0), width=4, height=0.2, alpha=0.3,
                     transform=trans)
ax.add_patch(rect)

ax.set(xlim=(3,10))
plt.show()

enter image description here

If instead you want rotation about a point in data coordinates, a single transform is not doing the job. For example for a rotation about (5,5) in data space,

import matplotlib.pyplot as plt
import matplotlib.transforms as mtrans

fig, ax = plt.subplots()
ax.set(xlim=(3,10),ylim=(4,10))
fig.canvas.draw()


angle = 38 # degrees
x, y = ax.transData.transform((5,5))
_, yax = ax.transAxes.inverted().transform((0,y))
transblend = ax.get_xaxis_transform()
x, y = transblend.transform((5,yax))

trans = transblend + mtrans.Affine2D().rotate_deg_around(x,y, angle)

ax.plot([5,9],[yax,yax], marker="o", transform=trans)

rect = plt.Rectangle((5,yax), width=4, height=0.2, alpha=0.3,
                     transform=trans)
ax.add_patch(rect)


plt.show()

Note that this invalidates as soon as you change the limits or figure size.

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • Thanks, this was exactly what I was looking for. – Mariano Balbi May 10 '19 at 15:05
  • This could be further extended to a Polygon instead of a Rectangle using the same transformation right? – Mariano Balbi May 10 '19 at 15:13
  • Yes, it can be anything, the x coordinate is data, y is axes, and then everything is rotated by angle. You will probably run into trouble with the axis limits, but that's inherent to this simple solution. – ImportanceOfBeingErnest May 10 '19 at 15:37
  • Thanks again. One last question: if I want to locate the first point of the bar at a given point in data coordinates space (let's say (5,5), I should use a translate transform in data coordinates. Should this be done before or after the rotate transform? – Mariano Balbi May 10 '19 at 15:49
  • There is no data coordinates in y direction any more. Unless you define a much more complicated transform that updates itself, similar to [this](https://stackoverflow.com/a/56030879/4124317). – ImportanceOfBeingErnest May 10 '19 at 15:54
  • Right. So how does this change if rotation needs to be in data coordinates space? – Mariano Balbi May 10 '19 at 15:59
  • That will not be possible with a single transform. I updated the answer with a code that shows that. You will need to use the result of one transform to feed it into another one. – ImportanceOfBeingErnest May 10 '19 at 17:05
  • This is getting very close to what I need! Here if I use ax.set_aspect('equal') so that the bar still has length 4 when rotated, the rotation seems to be around a point slightly off (5,5). I can fix this manually by modifying the rotation point by trial and error, but I guess there should be a way to know exactly how to set the rotation point, back to (5,5). – Mariano Balbi May 10 '19 at 19:05
  • Note that the key for the second solution to work is to first set the limits. So if you want to set the aspect that necessarily needs to happen before the call to `draw()`. – ImportanceOfBeingErnest May 10 '19 at 19:24
  • @ImportanceOfBeingErnest: Where did you vanish from SO? Urlaub? – Sheldore May 16 '19 at 17:27