使用子图放大为时间表,或者如何在轴边框外面绘制线条

您所在的位置:网站首页 时间轴制作超详细教程图解 使用子图放大为时间表,或者如何在轴边框外面绘制线条

使用子图放大为时间表,或者如何在轴边框外面绘制线条

2023-03-30 03:31| 来源: 网络整理| 查看: 265

百度翻译此文   有道翻译此文 问题描述

I would like to generate a plot like this with matplotlib

timescale http://www.imagenetz.de/img.php?file=37d8879009.jpg&pid=

Currently I just do the 3 subplots with matplotlib and add the red lines in inkscape. I found out that I can create the dashed rectangles with Rectangle.

ax.add_patch(Rectangle((25, -1.4), 3, 1.3, edgecolor='red', fill=False, linestyle='dashed'))

I haven't found anything yet to draw the lines connecting the plots. Is there a function that can draw across axis borders?

推荐答案

Short answer

We can exploit plt.annotate() to draw lines outside axis borders in figure coordinates.

Long answer

First define a helper function:

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

Then we can do this:

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')

enter image description here

其他推荐答案

Based on herrilich10's answer, here is full implementation. The figure now can have subplots only in a row (or in a column) [see Figure 1 below], or some subplots around the original one [see Figure 2 below].

I also fixed 2 bugs: TypeError: can only concatenate list (not "dict_items") to list and UnboundLocalError: local variable 'dst' referenced before assignment. The codes have been tested in Python 3.7.6 and matplotlib 3.1.3 with qt/pdf backend.

Remark: the variable roi is different from that in herrilich10's answer. I use the default order from matplotlib [xmin, ymin, xmax, ymax] to pass values to this variable. [see class matplotlib.transforms.Bbox]

Here's how to define the helper function:

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

Here's how to use it:

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()

Here's some demonstrations:

Figure 1

Figure 2

Enjoy it !



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3