简体   繁体   中英

Changing lines to inset axes

I'm using matplotlib v. 3.0.3. I wanted to use inset axes to zoom in on a location in an imshow plot and then plot the zoomed in sections outside the main image. I was playing around with the inset axes using the following code

import matplotlib.pyplot as plt
import numpy as np
#from mpl_toolkits.axes_grid1.inset_locator import (inset_axes, InsetPosition, mark_inset)

fig, ax = plt.subplots(figsize=(6,6))
Z2 = np.random.rand(512, 512)

ax.imshow(Z2, interpolation='gaussian', cmap = 'RdBu', origin='lower')
ax.tick_params(axis='both', bottom=False, top=False, right=False, left=False, labelbottom=False, labelleft=False, labeltop=False, labelright=False)

# inset axes...
axins_1 = ax.inset_axes([0, -1, 1, 1]) # bottom left, outside main plot
axins_1.imshow(Z2, interpolation="gaussian", cmap = 'RdBu', origin='lower')
axins_1.tick_params(axis='both', bottom=False, top=False, right=False, left=False, labelbottom=False, labelleft=False, labeltop=False, labelright=False)
# sub region of the original image
axins_1.set_xlim(100, 150)
axins_1.set_ylim(85, 135)

ax.indicate_inset_zoom(axins_1, edgecolor='0')
#mark_inset(ax, axins_1, loc1=2, loc2=3, fc="none", lw=1, ec='k')

# inset axes...
axins_2 = ax.inset_axes([1, -1, 1, 1]) # bottom right, outside main plot
axins_2.imshow(Z2, interpolation="gaussian", cmap = 'RdBu', origin='lower')
axins_2.tick_params(axis='both', bottom=False, top=False, right=False, left=False, labelbottom=False, labelleft=False, labeltop=False, labelright=False)
# sub region of the original image
axins_2.set_xlim(400, 450)
axins_2.set_ylim(200, 250)

ax.indicate_inset_zoom(axins_2, edgecolor='0')
#mark_inset(ax, axins_2, loc1=2, loc2=3, fc="none", lw=1, ec='k')

# inset axes...
axins_3 = ax.inset_axes([1, 0, 1, 1]) # top right, outside main plot
axins_3.imshow(Z2, interpolation="gaussian", cmap = 'RdBu', origin='lower')
axins_3.tick_params(axis='both', bottom=False, top=False, right=False, left=False, labelbottom=False, labelleft=False, labeltop=False, labelright=False)
# sub region of the original image
axins_3.set_xlim(400, 450)
axins_3.set_ylim(400, 450)

ax.indicate_inset_zoom(axins_3, edgecolor='0')
#mark_inset(ax, axins_3, loc1=2, loc2=3, fc="none", lw=1, ec='k')

plt.show()

Here, the main plot is the top left while the top right and both bottom pictures are the zoomed quantities. This generates almost exactly the plot I am after inside the Jupyter notebook . However, I have two issues that I'm looking to fix.

My first issue is that when I save the plot it comes out as follows

在此处输入图像描述

which clearly cuts off the zoomed images. I was wondering how to fix this?

The second issue is that the lines running from the subsections of the main plot to the zoomed images cross over in multiple places. What I would like is to keep the boxes around the subsections but change the lines. However, from the documentation it seems you can't remove the lines only, as changing edgecolor does the colouring for both the box and lines.

What I would like is to have an arrow leading from the section pointing to the correct zoomed image or some other way of labelling the sections and their corresponding zoomed images. However, I haven't been able to find a way to do this. Is this possible? And if so, how do we do it?

The alternative way to do this would be to make subplots and simply plot the zoomed regions in the subplots. However, in this approach I am at a loss as to how to identify each subsection with the corresponding zoomed plot.

You can always roll your own simplified inset function.

Here I'm using subplots to control my layout to help ensure I don't draw outside of the bounds of my Figure (which is why your Figure clips your Axes when saving to file).

Additionally, I set up my own rectangle and ConnectionPatch so that I'm only drawing a single ConnectionPatch per desired inset with the arguments I need. This enables me to have explicit control over where the arrow starts and ends.

In the function I provided

  • arrow_start is an xy coordinate in proportional units on the Rectangle (eg (0, 0) is the lower left hand corner of the rectanble, (0, .5) is the center left, (0, 1) is the upper left, (1, 1) is the upper right, etc.
  • arrow_end is an xy coordinate in proportional units on the inset Axes (eg (0, 0) is the lower left hand corner of the inset Axes, so on so forth.

You can use these two parameters to explicitly place your arrows where you think they go best on your figure to not overlap with other Axes

I also moved some of your style setting code to the top as default settings to be used.

import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle, ConnectionPatch
import numpy as np

plt.rc('xtick', bottom=False, top=False, labelbottom=False)
plt.rc('ytick', left=False, right=False, labelleft=False)
plt.rc('figure', facecolor='white')

def indicate_inset(axin, axout, arrow_start=(0, 0), arrow_end=(0, 0)):
    (x0, x1), (y0, y1) = axin.get_xlim(), axin.get_ylim()
    width = x1 - x0
    height = y1 - y0
    
    rect = Rectangle(
        [x0, y0], width=width, height=height, 
        transform=axout.transData, fc='none', ec='black'
    )
    axout.add_patch(rect)
    
    conn = ConnectionPatch(
        xyA=arrow_start, coordsA=rect.get_transform(),
        xyB=arrow_end, coordsB=axin.transAxes,
        arrowstyle='->'
    )
    fig.add_artist(conn)
    return rect, conn


fig, axes = plt.subplots(
    2, 2, figsize=(6, 6), 
    gridspec_kw={'wspace': 0.05, 'hspace': 0.05, 'left':.1, 'right': .9}
)
Z2 = np.random.rand(512, 512)

imshow_kws = dict(interpolation='gaussian', cmap='RdBu', origin='lower')
raw_ax = axes[0, 0]
raw_ax.imshow(Z2, **imshow_kws)

# inset axes...
axes[1, 0].imshow(Z2, **imshow_kws)
axes[1, 0].set(xlim=(100, 150), ylim=(85, 135))
indicate_inset(axes[1, 0], raw_ax, arrow_start=(.5, 0), arrow_end=(.5, 1))

# # inset axes...
axes[1, 1].imshow(Z2, **imshow_kws)
axes[1, 1].set(xlim=(400, 450), ylim=(200, 250))
indicate_inset(axes[1, 1], raw_ax, arrow_start=(.5, 0), arrow_end=(0, 1))

# # # inset axes...
axes[0, 1].imshow(Z2, **imshow_kws)
axes[0, 1].set(xlim=(400, 450), ylim=(400, 450))
indicate_inset(axes[0, 1], raw_ax, arrow_start=(1, 0), arrow_end=(0, .5))

fig.savefig('test.png') # This is the image uploaded to this answer

在此处输入图像描述

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM