我想用matplotlib
生成这样的情节timescale http://www.imagenetz.de/img.php?file=37d8879009.jpg&pid=
目前我只使用matplotlib做3个子图,并在inkscape中添加红线。
我发现我可以使用Rectangle
创建虚线矩形。
ax.add_patch(Rectangle((25, -1.4), 3, 1.3, edgecolor='red',
fill=False, linestyle='dashed'))
我还没有找到任何可以绘制连接图的线条。是否有可以跨轴边界绘制的函数?
答案 0 :(得分:3)
简短回答
我们可以利用plt.annotate()在图形坐标中绘制轴边界外的线条。
答案很长
首先定义辅助函数:
from matplotlib.patches import Rectangle
def zoomingBox(ax1, roi, ax2, color='red', linewidth=2, roiKwargs={}, arrowKwargs={}):
'''
**Notes (for reasons unknown to me)**
1. Sometimes the zorder of the axes need to be adjusted manually...
2. The figure fraction is accurate only with qt backend but not inline...
'''
roiKwargs = dict([('fill',False), ('linestyle','dashed'), ('color',color), ('linewidth',linewidth)] + roiKwargs.items())
ax1.add_patch(Rectangle([roi[0],roi[2]], roi[1]-roi[0], roi[3]-roi[2], **roiKwargs))
arrowKwargs = dict([('arrowstyle','-'), ('color',color), ('linewidth',linewidth)] + arrowKwargs.items())
srcCorners = [[roi[0],roi[2]], [roi[0],roi[3]], [roi[1],roi[2]], [roi[1],roi[3]]]
dstCorners = ax2.get_position().corners()
srcBB = ax1.get_position()
dstBB = ax2.get_position()
if (dstBB.min[0]>srcBB.max[0] and dstBB.max[1]<srcBB.min[1]) or (dstBB.max[0]<srcBB.min[0] and dstBB.min[1]>srcBB.max[1]):
src = [0, 3]; dst = [0, 3]
elif (dstBB.max[0]<srcBB.min[0] and dstBB.max[1]<srcBB.min[1]) or (dstBB.min[0]>srcBB.max[0] and dstBB.min[1]>srcBB.max[1]):
src = [1, 2]; dst = [1, 2]
elif dstBB.max[1] < srcBB.min[1]:
src = [0, 2]; dst = [1, 3]
elif dstBB.min[1] > srcBB.max[1]:
src = [1, 3]; dst = [0, 2]
elif dstBB.max[0] < srcBB.min[0]:
src = [0, 1]; dst = [2, 3]
elif dstBB.min[0] > srcBB.max[0]:
src = [2, 3]; dst = [0, 1]
for k in range(2):
ax1.annotate('', xy=dstCorners[dst[k]], xytext=srcCorners[src[k]], xycoords='figure fraction', textcoords='data', arrowprops=arrowKwargs)
然后我们可以这样做:
import matplotlib.pyplot as plt
axs = plt.subplots(2, 2)[1]
axs[1,1].plot(rand(100))
zoomingBox(axs[1,1], [40,60,0.1,0.9], axs[0,0])
zoomingBox(axs[1,1], [10,30,0.1,0.9], axs[1,0], color='orange')
答案 1 :(得分:2)
根据herrilich10的回答,这里是完整的实现。现在,该图只能在一行(或一列)中包含子图(请参见下面的图1),或者可以在原始图周围有一些子图[请参见下面的图2]。
我还修复了2个错误:TypeError: can only concatenate list (not "dict_items") to list
和UnboundLocalError: local variable 'dst' referenced before assignment
。这些代码已在带有qt / pdf后端的Python 3.7.6和matplotlib 3.1.3中进行了测试。
备注:变量roi
与herrilich10的答案不同。我使用matplotlib [xmin, ymin, xmax, ymax]
中的默认顺序将值传递给此变量。 [请参阅class matplotlib.transforms.Bbox]
以下是定义助手功能的方法:
from matplotlib.patches import Rectangle
def zoom_outside(srcax, roi, dstax, color="red", linewidth=2, roiKwargs={}, arrowKwargs={}):
'''Create a zoomed subplot outside the original subplot
srcax: matplotlib.axes
Source axis where locates the original chart
dstax: matplotlib.axes
Destination axis in which the zoomed chart will be plotted
roi: list
Region Of Interest is a rectangle defined by [xmin, ymin, xmax, ymax],
all coordinates are expressed in the coordinate system of data
roiKwargs: dict (optional)
Properties for matplotlib.patches.Rectangle given by keywords
arrowKwargs: dict (optional)
Properties used to draw a FancyArrowPatch arrow in annotation
'''
roiKwargs = dict([("fill", False), ("linestyle", "dashed"),
("color", color), ("linewidth", linewidth)]
+ list(roiKwargs.items()))
arrowKwargs = dict([("arrowstyle", "-"), ("color", color),
("linewidth", linewidth)]
+ list(arrowKwargs.items()))
# draw a rectangle on original chart
srcax.add_patch(Rectangle([roi[0], roi[1]], roi[2]-roi[0], roi[3]-roi[1],
**roiKwargs))
# get coordinates of corners
srcCorners = [[roi[0], roi[1]], [roi[0], roi[3]],
[roi[2], roi[1]], [roi[2], roi[3]]]
dstCorners = dstax.get_position().corners()
srcBB = srcax.get_position()
dstBB = dstax.get_position()
# find corners to be linked
if srcBB.max[0] <= dstBB.min[0]: # right side
if srcBB.min[1] < dstBB.min[1]: # upper
corners = [1, 2]
elif srcBB.min[1] == dstBB.min[1]: # middle
corners = [0, 1]
else:
corners = [0, 3] # lower
elif srcBB.min[0] >= dstBB.max[0]: # left side
if srcBB.min[1] < dstBB.min[1]: # upper
corners = [0, 3]
elif srcBB.min[1] == dstBB.min[1]: # middle
corners = [2, 3]
else:
corners = [1, 2] # lower
elif srcBB.min[0] == dstBB.min[0]: # top side or bottom side
if srcBB.min[1] < dstBB.min[1]: # upper
corners = [0, 2]
else:
corners = [1, 3] # lower
else:
RuntimeWarning("Cannot find a proper way to link the original chart to "
"the zoomed chart! The lines between the region of "
"interest and the zoomed chart wiil not be plotted.")
return
# plot 2 lines to link the region of interest and the zoomed chart
for k in range(2):
srcax.annotate('', xy=srcCorners[corners[k]], xycoords="data",
xytext=dstCorners[corners[k]], textcoords="figure fraction",
arrowprops=arrowKwargs)
这里是使用方法:
from matplotlib import pyplot as plt
# prepare something to plot
x = range(100)
y = [-100, -50, 0, 50, 100] * int(len(x)/5)
# create a figure
fig, axes = plt.subplots(3, 3)
plt.subplots_adjust(wspace=0.2, hspace=0.2)
# plot the main chart
axes[1, 1].plot(x, y)
# plot zoomed charts
zoom_outside(srcax=axes[1, 1], roi=[0, 80, 20, 100], dstax=axes[0, 0], color="C1")
zoom_outside(axes[1, 1], [40, 80, 60, 100], axes[0, 1], "C2")
zoom_outside(axes[1, 1], [80, 80, 100, 100], axes[0, 2], "C3")
zoom_outside(axes[1, 1], [0, -20, 20, 20], axes[1, 0], "C4")
zoom_outside(axes[1, 1], [80, -20, 100, 20], axes[1, 2], "C5")
zoom_outside(axes[1, 1], [0, -100, 20, -80], axes[2, 0], "C6")
zoom_outside(axes[1, 1], [40, -100, 60, -80], axes[2, 1], "C7")
zoom_outside(axes[1, 1], [80, -100, 100, -80], axes[2, 2], "C8")
plt.show()
这里有一些示范:
享受吧!