Skip to content

savefig messes up coordinate transformation of annotate #4375

Closed

Description

@PiQuer

I want to annotate a data point with an inset axis (matplotlib-1.4.3, Linux):

import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.axes_grid1.inset_locator import inset_axes

x=np.linspace(0,10,200)
y=np.exp(-x)*np.sin(x)

fig,ax = plt.subplots(figsize=(3.39,3))

ax.plot(x,y)

axins=inset_axes(ax, width=0.6, height=0.6, bbox_to_anchor=(6, 0.2),bbox_transform=ax.transData)
axins.axes.get_xaxis().set_visible(False)
axins.axes.get_yaxis().set_visible(False)
ax.annotate("",xy=(x[150],y[150]), xycoords=ax.transData,
        xytext=(1, 0), textcoords=axins.transAxes,
        arrowprops=dict(arrowstyle="->"))

fig.savefig('example.pdf')
plt.show()

This is the correct plot shown on screen:
shown
This is what savefig produces:
example

Apparently savefig changes the coordinates (1,0) from the inset axes to the parent axes for some reason.

Activity

  1. PiQuer commented on Apr 23, 2015

    @PiQuer
    Author

    Strangely enough, when commenting out the savefig, what is shown interactively on screen is the wrong version, until plt.draw() is called explicitely. After that, also savefig works correctly, so this is a workaround.

  2. added this to the milestone on Apr 23, 2015
  3. self-assigned this
    on Apr 23, 2015
  4. adrn commented on Jul 29, 2015

    @adrn
    Contributor

    @PiQuer are you saying that explicitly calling draw() before savefig() is a workaround?

    I'm running in to this exact issue, I think, see my griping here.

  5. adrn commented on Jul 29, 2015

    @adrn
    Contributor

    BTW different weirdness happens whether I save as PDF or PNG

  6. WeatherGod commented on Jul 29, 2015

    @WeatherGod
    Member

    attn: @mdboom. From the looks of it, it seems like the transform stack of
    the annotated object (or the inset axes) is getting modified at each draw?

    On Wed, Jul 29, 2015 at 8:29 AM, Adrian Price-Whelan <
    notifications@github.com> wrote:

    BTW different weirdness happens whether I save as PDF or PNG

    —
    Reply to this email directly or view it on GitHub
    #4375 (comment)
    .

  7. tacaswell commented on Aug 1, 2015

    @tacaswell
    Member

    I wonder if this is fixed by #4843

  8. tacaswell commented on Aug 1, 2015

    @tacaswell
    Member

    no 😞 not fixed that branch.

  9. tacaswell commented on Aug 6, 2015

    @tacaswell
    Member

    Maybe (?) related to #2831 (comment)

  10. tacaswell commented on Aug 6, 2015

    @tacaswell
    Member

    @adrn That sort of griping is not the most effective at making devs want to help you. Without seeing your code I suspect you are using display-space units at some point which you should not be doing. Please open a new issue or send a note to the mailing list.

    @PiQuer Does this work reliably on any version of mpl? Trying to sort out if this is a regression or a long standing bug.

    I suspect this issues is related to the use of the inset axes which has some non-trivial logic in how it's transforms get managed. The inset axes has it's transforms defined in terms of the transforms of the parent axes. Currently the details of those transforms get set up (somehow, have not traced the code path yet) as part of the draw process off the inset axes. When it is initialized it's transforms have some default value. The annotation in the above code is a child of the inset axes. The way the draw tree is worked is that the parent axes, then all it's children, then the inset axes, then all of it's children are drawn. The source of the error here is that the annotation is drawn before the inset axes is drawn, and hence before it's transforms have been updated to reflect that it is an inset axes. The simplest work around is to just make the annotation belong to the inset axes so it is always drawn after the transfroms have been properly updated:

    import matplotlib.pyplot as plt
    import numpy as np
    from mpl_toolkits.axes_grid1.inset_locator import inset_axes
    
    x=np.linspace(0,10,200)
    y=np.exp(-x)*np.sin(x)
    
    fig,ax = plt.subplots(figsize=(3.39,3))
    
    ax.plot(x,y)
    
    axins=inset_axes(ax, width=0.6, height=0.6, bbox_to_anchor=(6, 0.2),bbox_transform=ax.transData)
    axins.axes.get_xaxis().set_visible(False)
    axins.axes.get_yaxis().set_visible(False)
    axins.annotate("",xy=(x[150],y[150]), xycoords=ax.transData,
            xytext=(1, 0), textcoords=axins.transAxes,
            arrowprops=dict(arrowstyle="->"), clip_on=False)
    
    fig.savefig('example.pdf')
    plt.show()

    (the clip_on=False is not really needed, but I don't understand why off the top of my head)

    Removing this from the next point release milestone because:

    • there is a simple workaround
    • the root cause of this may not be fixable with out major changes to axes_grid, the draw process, or both.

    cc @leejjoon

  11. modified the milestones: , on Aug 6, 2015
  12. removed their assignment
    on Aug 6, 2015
  13. PiQuer commented on Aug 6, 2015

    @PiQuer
    Author

    I will try to test in a couple of days, at the moment unfortunately I have no time to do it.

  14. modified the milestones: , on Oct 3, 2017
  15. leejjoon commented on Jul 23, 2018

    @leejjoon
    Contributor

    I know this is rather old, but I will make a quick comment as this is still open.
    I would say this is more like a design issue with the draw process. We can easily reproduce a similar behavior with two subplots where an annotation in the 1st subplot refers the the transAxes of the second subplot. I think we need to split the drawing process into two: first update the geometry of all the artists, and then draw. Or maybe three: update their extent, update location, and then draw. We may need some dependency tracking scheme with their geometry.

    Maybe, we'd better create a new general issue for the discussion (and assemble all the related issues under it)?

  16. jklymak commented on Jul 23, 2018

    @jklymak
    Member

    FWIW:

    import matplotlib.pyplot as plt
    import numpy as np
    
    x=np.linspace(0,10,200)
    y=np.exp(-x)*np.sin(x)
    
    fig,ax = plt.subplots(figsize=(3.39,3))
    
    ax.plot(x,y)
    
    axins = ax.inset_axes([0.4, 0.5, 0.3, 0.3])
    axins.axes.get_xaxis().set_visible(False)
    axins.axes.get_yaxis().set_visible(False)
    ax.annotate("",xy=(x[150],y[150]), xycoords=ax.transData,
            xytext=(1, 0), textcoords=axins.transAxes,
            arrowprops=dict(arrowstyle="->"))
    
    fig.savefig('example.pdf')
    plt.show()

    Works fine on master.
    example.pdf

  17. leejjoon commented on Jul 23, 2018

    @leejjoon
    Contributor

    I guess this works because the inset location is given in normalized axes coordinate, so its location is unchanged during its draw process. If you put 'axins.set_aspect(1.)', you will see that the location of the arrow becomes incorrect.

  18. jklymak commented on Jul 23, 2018

    @jklymak
    Member

    Yes, agreed - had the same problem w/ inset axes locators.

    Lets start a new issue with as simple as possible a failing case and maybe we can work out a fix. But I think you are correct that the issue is the draw process sometimes needs to iterate, or at least be done twice before complicated layouts will work.

    Calling apply_aspect (which calls the axes locator) seems to fix things.

    A dangerous idea is for when the figure gets drawn call apply_aspect on all the children axes before the rest of the draw gets done. The danger is that apply_aspect is expensive, but I don't think its that expensive.

    import matplotlib.pyplot as plt
    import numpy as np
    
    x=np.linspace(0,10,200)
    y=np.exp(-x)*np.sin(x)
    
    fig,ax = plt.subplots(figsize=(3.39,3))
    
    ax.plot(x,y)
    
    axins = ax.inset_axes([0.4, 0.5, 0.3, 0.3])
    axins.xaxis.set_visible(False)
    axins.yaxis.set_visible(False)
    axins.set_aspect(0.2)
    ax.annotate("",xy=(x[150],y[150]), xycoords=ax.transData,
            xytext=(1, 0), textcoords=axins.transAxes,
            arrowprops=dict(arrowstyle="->"))
    axins.apply_aspect()
    
    fig.savefig('example.pdf')
    plt.show()

    example.pdf

  19. ImportanceOfBeingErnest commented on Jul 12, 2019

    @ImportanceOfBeingErnest
    Member

    Closing because this seems to be fixed by #11753. At least the pdf and screen output are now the same.

  20. removed this from the future releases milestone on Oct 6, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions