Matplotlib:多线图

日期2017-07-13(最后修改),2006-01-22(创建)

多线图

通常人们希望将多个信号绘制在一起。有几种方法可以做到这一点。最简单的实现方法是为每个信号添加一个常数偏移量

In [ ]
from pylab import plot, show, ylim, yticks
from matplotlib.numerix import sin, cos, exp, pi, arange

t = arange(0.0, 2.0, 0.01)
s1 = sin(2*pi*t)
s2 = exp(-t)
s3 = sin(2*pi*t)*exp(-t)
s4 = sin(2*pi*t)*cos(4*pi*t)

t = arange(0.0, 2.0, 0.01)
plot(t, s1, t, s2+1, t, s3+2, t, s4+3, color='k')
ylim(-1,4)
yticks(arange(4), ['S1', 'S2', 'S3', 'S4'])

show()

但是这样很难以合理的方式更改 y 轴比例。例如,当你放大 y 轴时,顶部和底部的信号会超出屏幕。通常人们希望每个信号的 y 位置保持不变,而信号的增益发生变化。

使用多个轴

如果你只有几个信号,你可以将每个信号设置为一个单独的轴,并将 y 轴标签设置为水平方向。这对于少量信号(例如 4-10 个)来说效果很好,除了轴周围的额外水平线和刻度可能会让人感到厌烦。我们计划更改这些轴线的绘制方式,以便你可以将其删除,但目前还没有完成。

In [ ]
from pylab import figure, show, setp
from matplotlib.numerix import sin, cos, exp, pi, arange

t = arange(0.0, 2.0, 0.01)
s1 = sin(2*pi*t)
s2 = exp(-t)
s3 = sin(2*pi*t)*exp(-t)
s4 = sin(2*pi*t)*cos(4*pi*t)

fig = figure()
t = arange(0.0, 2.0, 0.01)

yprops = dict(rotation=0,
              horizontalalignment='right',
              verticalalignment='center',
              x=-0.01)

axprops = dict(yticks=[])

ax1 =fig.add_axes([0.1, 0.7, 0.8, 0.2], **axprops)
ax1.plot(t, s1)
ax1.set_ylabel('S1', **yprops)

axprops['sharex'] = ax1
axprops['sharey'] = ax1
# force x axes to remain in register, even with toolbar navigation
ax2 = fig.add_axes([0.1, 0.5, 0.8, 0.2], **axprops)

ax2.plot(t, s2)
ax2.set_ylabel('S2', **yprops)

ax3 = fig.add_axes([0.1, 0.3, 0.8, 0.2], **axprops)
ax3.plot(t, s4)
ax3.set_ylabel('S3', **yprops)

ax4 = fig.add_axes([0.1, 0.1, 0.8, 0.2], **axprops)
ax4.plot(t, s4)
ax4.set_ylabel('S4', **yprops)

# turn off x ticklabels for all but the lower axes
for ax in ax1, ax2, ax3:
    setp(ax.get_xticklabels(), visible=False)

show()

操作变换

对于大量线条,上述方法效率低下,因为为每条线创建单独的轴会产生大量无用的开销。产生 matplotlib 的应用程序是一个 脑电图查看器,它必须有效地处理数百条线;它作为 pbrain 包 的一部分提供。

以下是如何使用“就地”增益更改进行多线绘图的示例。请注意,这将破坏工具栏的 y 行为,因为我们更改了所有默认变换。在我的应用程序中,我有一个自定义工具栏来增加或减少 y 缩放比例。在这个例子中,我将加号/减号键绑定到一个函数,该函数增加或减少 y 增益。也许我会把它包装成一个名为 plot_signals 的函数或类似的东西,因为代码有点乱,因为它大量使用了有点神秘的 matplotlib 变换。我建议在尝试理解此示例之前阅读有关 变换模块 的内容。

In [ ]
from pylab import figure, show, setp, connect, draw
from matplotlib.numerix import sin, cos, exp, pi, arange
from matplotlib.numerix.mlab import mean
from matplotlib.transforms import Bbox, Value, Point, \
     get_bbox_transform, unit_bbox
# load the data

t = arange(0.0, 2.0, 0.01)
s1 = sin(2*pi*t)
s2 = exp(-t)
s3 = sin(2*pi*t)*exp(-t)
s4 = sin(2*pi*t)*cos(4*pi*t)
s5 = s1*s2
s6 = s1-s4
s7 = s3*s4-s1

signals = s1, s2, s3, s4, s5, s6, s7
for sig in signals:
    sig = sig-mean(sig)

lineprops = dict(linewidth=1, color='black', linestyle='-')
fig = figure()
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8])

# The normal matplotlib transformation is the view lim bounding box
# (ax.viewLim) to the axes bounding box (ax.bbox).  Where are going to
# define a new transform by defining a new input bounding box. See the
# matplotlib.transforms module helkp for more information on
# transforms

# This bounding reuses the x data of the viewLim for the xscale -10 to
# 10 on the y scale.  -10 to 10 means that a signal with a min/max
# amplitude of 10 will span the entire vertical extent of the axes
scale = 10
boxin = Bbox(
    Point(ax.viewLim.ll().x(), Value(-scale)),
    Point(ax.viewLim.ur().x(), Value(scale)))


# height is a lazy value
height = ax.bbox.ur().y() - ax.bbox.ll().y()

boxout = Bbox(
    Point(ax.bbox.ll().x(), Value(-0.5) * height),
    Point(ax.bbox.ur().x(), Value( 0.5) * height))


# matplotlib transforms can accepts an offset, which is defined as a
# point and another transform to map that point to display.  This
# transform maps x as identity and maps the 0-1 y interval to the
# vertical extent of the yaxis.  This will be used to offset the lines
# and ticks vertically
transOffset = get_bbox_transform(
    unit_bbox(),
    Bbox( Point( Value(0), ax.bbox.ll().y()),
          Point( Value(1), ax.bbox.ur().y())
          ))

# now add the signals, set the transform, and set the offset of each
# line
ticklocs = []
for i, s in enumerate(signals):
    trans = get_bbox_transform(boxin, boxout)
    offset = (i+1.)/(len(signals)+1.)
    trans.set_offset( (0, offset), transOffset)

    ax.plot(t, s, transform=trans, **lineprops)
    ticklocs.append(offset)


ax.set_yticks(ticklocs)
ax.set_yticklabels(['S%d'%(i+1) for i in range(len(signals))])

# place all the y tick attributes in axes coords  
all = []
labels = []
ax.set_yticks(ticklocs)
for tick in ax.yaxis.get_major_ticks():
    all.extend(( tick.label1, tick.label2, tick.tick1line,
                 tick.tick2line, tick.gridline))
    labels.append(tick.label1)

setp(all, transform=ax.transAxes)
setp(labels, x=-0.01)

ax.set_xlabel('time (s)')


# Because we have hacked the transforms, you need a special method to
# set the voltage gain; this is a naive implementation of how you
# might want to do this in real life (eg make the scale changes
# exponential rather than linear) but it gives you the idea
def set_ygain(direction):
    set_ygain.scale += direction
    if set_ygain.scale <=0:
        set_ygain.scale -= direction
        return

    for line in ax.lines:
        trans = line.get_transform()
        box1 =  trans.get_bbox1()
        box1.intervaly().set_bounds(-set_ygain.scale, set_ygain.scale)
    draw()
set_ygain.scale = scale

def keypress(event):
    if event.key in ('+', '='): set_ygain(-1)
    elif event.key in ('-', '_'): set_ygain(1)

connect('key_press_event', keypress)
ax.set_title('Use + / - to change y gain')
show()

章节作者:AndrewStraw、GaelVaroquaux、Christian Gagnon

附件