Search code examples
pythonmatplotlibaffinetransform

How to create a custom blended_transform that acts on rotated directions


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.


Solution

  • 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.