I am a bit confused by matplotlib coordinate system (using it in a Jupyter notebook for interactive animation). If you create a patch, say a Circle, and translate it, using set_transform() I'm finding that the transformation is not persistent, meaning that if you apply the transformation again with the same (x,y) shifts the circle will not move because it appears that the Circle position does not get updated after a transformation, so a subsequent transform is applied to the same original patch position. My question is How do I apply a transformation that actually not only moves the patch after it's applied, but also updates the patch position?. Say I want to apply a series of translations, then the circle should move around NOT wiggle back and forth from it's original position. Here's an example code:
%import matplotlib
%matplotlib notebook
fig = plt.figure()
ax = fig.add_subplot(111, xlim=(-10, 10), ylim=(-10, 10))
c = ax.add_patch(plt.Circle((x, y), radius=0.5)
c.set_transform(ax.transData + mpl.transforms.Affine2D().translate(-5,-5))
c.set_transform(ax.transData + mpl.transforms.Affine2D().translate(10,10))
if you run this, you'll see that commenting out the first translation will not affect the final position of the circle. I would have expected that the final position of the circle center to be (5,5) NOT (10,10). This means that the transformation does not actually update the patch (circle) position; it just translates it in the figure/axes.
Question 2: Another thing I find confusing is that the circle produced by the code above does seem to have a radius of 0.5 as requested, however after applying a translation by (10,10) it is translated much less in the figure!! as if the translation shifts are scaled down by some factor before they are applied!! I have no explanation for this and it just shows that I do not understand matplotlib coordinate systems and transformations.
On the other hand, objects produced by plot(), which are Line2D objects, can be translated via the set_data() method, which updates the object's position as follows (assuming fig, and ax objects from code segment above):
L, = ax.plot(0, 0, 'ro', ms=8)
sx = 10 # shift in x
sy = 10 # shift in y
L.set_data(L.get_data()[0] + sx, L.get_data()[1] + sy)
I'm not sure how to do the same for matplotlib patches?
The ax.transData
transform converts data coordinates to display coordinates.
When you add transforms together, they are applied from left to right. So
ax.transData + mpl.transforms.Affine2D().translate(-5,-5)
first converts from data to display coordinates, then translates by an offset of (-5, -5) in display coordinates (pixels).
In contrast,
mpl.transforms.Affine2D().translate(-5,-5) + ax.transData
would first shift by (-5, -5) in data coordinates, and then convert data coordinates to display coordinates.
Since transforms can be composed by addition, to update a transformation use
transform = transform + mpl.transforms.Affine2D().translate(...)
For example,
import matplotlib.pyplot as plt
import matplotlib as mpl
fig = plt.figure()
ax = fig.add_subplot(111, xlim=(-10, 10), ylim=(-10, 10))
x, y = 0, 0
c = ax.add_patch(plt.Circle((x, y), radius=5))
transform = mpl.transforms.Affine2D().translate(-5,-5)
transform += mpl.transforms.Affine2D().translate(10,10)
c.set_transform(transform+ax.transData)
ax.set_aspect('equal')
plt.show()
The center of the circle is at (5,5)
, as expected, since (x,y)+(-5,-5)+(10,10) = (5,5)
.
Alternatively, instead of composing transforms, you could compute (or keep track of) a sequence of offsets, and generate the transforms as you need them. See this post for an example of this approach.
Also note that transforms are not applied until the patch is rendered (such as by a call to plt.show()
). This explains why multiple calls to c.set_transform
have the same effect as a single call to c.set_transform
. Only the last transform is applied to the patch.
The patch, c
, stores a single transform in a private attribute which you can access using c.get_transform
:
In [10]: c.get_transform()
Out[15]:
CompositeGenericTransform(Affine2D(array([[ 5., 0., 0.],
[ 0., 5., 0.],
[ 0., 0., 1.]])), CompositeGenericTransform(TransformWrapper(BlendedAffine2D(IdentityTransform(),IdentityTransform())), CompositeGenericTransform(BboxTransformFrom(TransformedBbox(Bbox([[-10.0, -10.0], [10.0, 10.0]]), TransformWrapper(BlendedAffine2D(IdentityTransform(),IdentityTransform())))), BboxTransformTo(TransformedBbox(Bbox([[0.125, 0.09999999999999998], [0.9, 0.9]]), BboxTransformTo(TransformedBbox(Bbox([[0.0, 0.0], [6.4, 4.8]]), Affine2D(array([[ 100., 0., 0.],
[ 0., 100., 0.],
[ 0., 0., 1.]])))))))))
c.set_transform
reassigns that private attribute to a new transform. But no transform gets applied until plt.show()
or some other rendering call like plt.savefig
is made.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With