Skip to content

indicate_inset_zoom sometimes draws incorrect connector lines #12323

Closed

Description

@dstraus

Bug report

Bug summary

Depending on location of inset, the connector lines are not drawn to the nearest two corners.

Code for reproduction

import matplotlib.pyplot as plt
import numpy as np
line=np.linspace(10,0, 101)
fig, ax=plt.subplots()
ax.plot(line, line)
axins=ax.inset_axes([0, .5, .4, .4])
axins.plot(line, line)
axins.set_xlim(7,8)
axins.set_ylim(7,8)
ax.indicate_inset_zoom(axins)

Actual outcome

image

Expected outcome

import matplotlib.pyplot as plt
import numpy as np
line=np.linspace(10,0, 101)
fig, ax=plt.subplots()
ax.plot(line, line)
axins=ax.inset_axes([0, .5, .4, .4])
axins.plot(line, line)
axins.set_xlim(7,8)
axins.set_ylim(7,8)
rectpatch, connects=ax.indicate_inset_zoom(axins)
connects[0].set_visible(False)
connects[1].set_visible(False)
connects[2].set_visible(True)
connects[3].set_visible(True)

image

Matplotlib version

  • Operating system: Windows 10 1803
  • Matplotlib version: 3.0.0
  • Matplotlib backend (print(matplotlib.get_backend())): module://ipykernel.pylab.backend_inline
  • Python version: Python 3.6.5 (Anaconda Python)
  • Jupyter version (if applicable): 6.4.0
  • Other libraries:

Installed matplotlib 3.0.0 from conda-forge

Activity

  1. dstraus commented on Sep 28, 2018

    @dstraus
    Author

    Actually, I think my "expected outcome" is not the proper solution, but looks the best without modifying the source. My ideal output would have the connecting lines drawn from the two corners of the rectangle closest to the inset to the closest two inset corners.

  2. jklymak commented on Sep 28, 2018

    @jklymak
    Member

    The algorithm tries to draw the best opposing corners. Your inset axes is basically centered on the indicator on the main axes so it chooses something that looks a little funny. Thats why we put the manual visibility toggles in there, for cases where the naive algorithm returns something that doesn't look nice.

    Actually, I think my "expected outcome" is not the proper solution, but looks the best without modifying the source. My ideal output would have the connecting lines drawn from the two corners of the rectangle closest to the inset to the closest two inset corners.

    That would be easy, but unexpected. The lines connect corners in the indicator with corners on the inset axes. I'd be against a change that didn't conserve this mapping.

  3. ImportanceOfBeingErnest commented on Sep 28, 2018

    @ImportanceOfBeingErnest
    Member

    There is mpl_toolkits.axes_grid1.inset_locator.inset_axes and mark_inset. You may get the "desired outcome" with those as

    import numpy as np
    import matplotlib.pyplot as plt
    from mpl_toolkits.axes_grid1.inset_locator import inset_axes, mark_inset
    
    line=np.linspace(10,0, 101)
    
    fig, ax=plt.subplots()
    ax.plot(line, line)
    
    axins = inset_axes(ax, "100%", "100%", bbox_to_anchor=[0, .5, .4, .4],
                       bbox_transform=ax.transAxes, borderpad=0)
    
    axins.plot(line, line)
    axins.set(xlim=(7,8), ylim=(7,8))
    
    mark_inset(ax, axins, loc1=1, loc2=4, fc="none", ec="0.5")
    plt.show()
    

    image

    You may get the "ideal" outcome with a bit of customization.

    import numpy as np
    import matplotlib.pyplot as plt
    from mpl_toolkits.axes_grid1.inset_locator import (inset_axes, TransformedBbox,
                                                       BboxPatch, BboxConnector)
    
    line=np.linspace(10,0, 101)
    
    fig, ax=plt.subplots()
    ax.plot(line, line)
    
    axins = inset_axes(ax, "100%", "100%", bbox_to_anchor=[0, .5, .4, .4],
                       bbox_transform=ax.transAxes, borderpad=0)
    
    axins.plot(line, line)
    axins.set(xlim=(7,8), ylim=(7,8))
    
    def my_mark_inset(parent_axes, inset_axes, loc1a=1, loc1b=1, loc2a=2, loc2b=2, **kwargs):
        rect = TransformedBbox(inset_axes.viewLim, parent_axes.transData)
        pp = BboxPatch(rect, fill=False, **kwargs)
        parent_axes.add_patch(pp)
        p1 = BboxConnector(inset_axes.bbox, rect, loc1=loc1a, loc2=loc1b, **kwargs)
        inset_axes.add_patch(p1)
        p1.set_clip_on(False)
        p2 = BboxConnector(inset_axes.bbox, rect, loc1=loc2a, loc2=loc2b, **kwargs)
        inset_axes.add_patch(p2)
        p2.set_clip_on(False)
    
        return pp, p1, p2
    
    my_mark_inset(ax, axins, loc1a=1, loc1b=2, loc2a=4, loc2b=3, fc="none", ec="0.5")
    
    plt.show()
    

    image

  4. jklymak commented on Sep 28, 2018

    @jklymak
    Member

    OK, but fair warning that the goal is to deprecate axes_grid1. If you want to make your own connection lines, you can use patches.ConnectionPatch:

    xyA = (1, 0)
    xyB = (bounds[0], bounds[0])
    connect = mpatches.ConnectionPatch(xyA, xyB, 'axes fraction', 'data',
                                axesA=inset_ax, axesB=parent_ax, arrowstyle="-", edgecolor=edgecolor)
    self.add_patch(connect)
    

    will add a line from the lower right of the inset to the lower left of the inset indicator.

  5. jklymak commented on Sep 28, 2018

    @jklymak
    Member
    import matplotlib.pyplot as plt
    import numpy as np
    import matplotlib.patches as mpatches
    
    line=np.linspace(10,0, 101)
    fig, ax=plt.subplots()
    ax.plot(line, line)
    axins=ax.inset_axes([0, .5, .4, .4])
    axins.plot(line, line)
    xlims = (7, 8)
    ylims = (7, 8)
    axins.set_xlim(xlims)
    axins.set_ylim(ylims)
    rectpatch, connects=ax.indicate_inset_zoom(axins)
    for con in connects:
        con.set_visible(False)
    
    for j in range(2):
        # inset axes co-ordinates
        xyA = (1, j)
        # data co-ordinates
        xyB = (xlims[0], ylims[j])
        connect = mpatches.ConnectionPatch(xyA, xyB, 'axes fraction', 'data',
                                    axesA=axins, axesB=ax, arrowstyle="-", edgecolor='0.6')
        ax.add_patch(connect)
    
    plt.show()

    inset

  6. ImportanceOfBeingErnest commented on Sep 28, 2018

    @ImportanceOfBeingErnest
    Member

    Some people's goal it is to deprecate axes_grid1. I would be careful to make that a self-fulfilling prophecy. People's bandwidth is much better used with solving real-world problems than with deconstructing parts of the library.

    I think indicate_inset_zoom (or indicate_inset) could well take 4 location parameters as input, similar to the hand-written function above, or as part of a single list of dict, to allow the user to easily modify the connection lines.

  7. jklymak commented on Sep 28, 2018

    @jklymak
    Member

    Maintaining and documenting axes_grid1 was also taking up a lot of bandwidth. 😉

    In this case the new API has the same coverage as the old.

    We could discuss a more flexible connector line API, but again, there is a cost/benefit to doing so.

  8. QuLogic commented on Sep 29, 2018

    @QuLogic
    Member

    I think getting the "expected result" automatically is not too difficult, but the so-called "ideal result" is perhaps not so easy and open to debate as to whether it's wanted. I know how to write the former at least and can open a PR for it.

  9. ImportanceOfBeingErnest commented on Sep 29, 2018

    @ImportanceOfBeingErnest
    Member

    I don't feel like what's called "ideal" here should be the default. The "expected" is probably what people would expect indeed.
    But giving users the option to easily obtain their custom desired look is very desireable in my eyes.

  10. jklymak commented on Sep 29, 2018

    @jklymak
    Member

    Changing the defaults is fine w me. It’s just an aesthetic decision and for most placements I thought what was implemented was the best without doing anything too complicated. But if something can be done that’s reasonable that’s be great.

    Expanding the API could also be nice, but a bit of thought should go into the best form.

    Thanks for the interest in improving this!

  11. ImportanceOfBeingErnest commented on Sep 29, 2018

    @ImportanceOfBeingErnest
    Member

    The immediate issue ("expected outcome") is solved by #12334. Should this be reopened to discuss adding customization ("ideal outcome")?

  12. dstraus commented on Sep 30, 2018

    @dstraus
    Author

    You should leave it closed. I think the "expected outcome" is correct and my "ideal outcome" was not, after reading the comments.

  13. jklymak commented on Sep 30, 2018

    @jklymak
    Member

    Thanks for the report @dstraus - that lead to a nice improvement by @QuLogic !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions