2015年5月23日土曜日

matplotlibのMultiCursor AutoScale時の挙動修正

matplotlibのMultiCursor()の使い方は、公式のページにありますが、matplotlib 1.3.0あたりからクロスラインカーソルも利用できます。

multi = MultiCursor(fig.canvas, (ax1, ax2), color='r', lw=1, horizOn=True)
のように、horizOnキーワード引数にTrueを渡すと水平線を描画します。

ここで、クロスラインカーソルを表示したいaxes、(ax1, ax2)を渡しているのですが、
最後のax2でないaxes、ここではax1の軸がオートスケールになっていると、あれっ?
と思う挙動をします。

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import MultiCursor

t = np.arange(0.0, 2.0, 0.01)
s1 = np.sin(2*np.pi*t)+400
s2 = np.sin(4*np.pi*t)
fig = plt.figure()
ax1 = fig.add_subplot(211)
ax1.plot(t, s1)

ax2 = fig.add_subplot(212, sharex=ax1)
ax2.plot(t, s2)

multi = MultiCursor(fig.canvas, (ax1, ax2), color='r', lw=1, horizOn=True)
plt.show()
ax1に描画する波形を400だけオフセットしただけですが、実行すると、以下のようになります。
y軸のレンジが0からになってしまいます。 オートスケールで期待する結果とは異なります。 matplotlibパッケージのwidgets.pyにある、MultiCursorのクラスを見てみると、 どうやら、カーソル線の初期位置の計算に、渡したaxesの最後のものだけを使っているために 生じるようです。

これをwidgets.pyをいじらずに回避するには、MultiCursorの__init__()で行なわれる カーソル線の生成を、別に行えばよさそうです。 そこで、MultiCursorの__init__()には、vertOn、 horizOnともにFalseを渡します。
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import MultiCursor

class MyMultiCursor(MultiCursor):
    def __init__(self, canvas, axes, 
            useblit=True, horizOn=True, vertOn=True, **lineprops): 
        MultiCursor.__init__(self, canvas, axes, 
                useblit=useblit, horizOn=False, vertOn=False, **lineprops)

        xmids = [0.5 * (xmin + xmax) for xmin, xmax in [ax.get_xlim() for ax in axes]] 
        ymids = [0.5 * (ymin + ymax) for ymin, ymax in [ax.get_ylim() for ax in axes]] 

        if self.useblit:
            lineprops['animated'] = True
        if vertOn:
            self.vlines = [ax.axvline(xmids[i], visible=False, **lineprops)
                           for i, ax in enumerate(axes)]
        else:
            self.vlines = []

        if horizOn:
            self.hlines = [ax.axhline(ymids[i], visible=False, **lineprops)
                           for i, ax in enumerate(axes)]
        else:
            self.hlines = []

        self.vertOn, self.horizOn = vertOn, horizOn


t = np.arange(0.0, 2.0, 0.01)
s1 = np.sin(2*np.pi*t)+400
s2 = np.sin(4*np.pi*t)
fig = plt.figure()
ax1 = fig.add_subplot(211)
ax1.plot(t, s1)

ax2 = fig.add_subplot(212, sharex=ax1)
ax2.plot(t, s2)

multi = MyMultiCursor(fig.canvas, (ax1, ax2), color='r', lw=1, horizOn=True)
plt.show()

MultiCursorを継承したMyMultiCursorを利用します。 実行すると、以下のように期待されたグラフが得られました。

0 件のコメント: