2014年1月31日金曜日

matplotlibのMultiCursor

matplotlibは、wxPythonと簡単に組み合わせて利用できて便利ですね。
matplotlibのグラフ上にマウスカーソルに合わせて動く十字のカーソルを表示するMultiCursorというものがありますが、 twinx()してY2軸を利用すると奇妙なことがおこりました。
カーソルは、Y軸に描画されながらも、その表示位置はY2軸上の値になるという状態です。
もちろん、カーソルを描画する対象をY2軸のほうにすると矛盾のない状態にはなります。
そもそもMultiCursorを利用する対象にY2軸を追加するような利用の仕方は、あまりなさそうです。そうはいっても、そのような要求があったため、Y2軸を追加しても、カーソル位置は、Y軸のほうで決まるようにしてみました。
マウス移動のイベントを処理するonmove()に渡されるeventからキャンバス上のx,y座標が得られるので、それを利用してグラフ軸上のデータに焼きなおすようにしました。
使い方はオリジナルのMultiCursorとほぼ同じですが、parentウィンドウを渡すようにして、そちらへ座標を通知するイベントをポストしています。parentウィンドウでそのイベントを拾えば、カーソル移動にあわせて座標位置を表示できます。

from matplotlib.widgets import MultiCursor
import wx
import wx.lib.newevent
class MyMultiCursor(MultiCursor):
    def __init__(self, parent, canvas, axes, color='r', lw=1,  
            useblit = True, horizOn=True, vertOn=True): 
        if matplotlib.__version__ >= '1.3.0':
            MultiCursor.__init__(self, canvas, axes, color=color, 
                    lw=lw, useblit=useblit, horizOn=horizOn, vertOn=vertOn)
        else:
            MultiCursor.__init__(self, canvas, axes, color=color, lw=lw, useblit=useblit)
        
        # Produce New Event for Cursor Move Notification to parent window.
        self.parent = parent
        self.CursorMoveEvent, self.EVT_CURSOR_MOVE = wx.lib.newevent.NewEvent()
        self.visible = False

    def xydata(self, x, y):
        #make new xdata, ydata
        cwidth, cheight = self.canvas.GetSize()
        target = None
        for ax in self.axes:
            #find the axes in which the mouse cursor is.
            bbox = ax.get_position()
            [[left,bottom], [right, top]] = bbox.get_points()
            xc = (x+1)/float(cwidth)
            yc = y/float(cheight) 
            if ((left < xc) and (xc < right) and 
                (bottom < yc) and (yc < top)):
                target = ax
                break
        if not target: 
            # if there is no target
            return (None, None)

        width = right-left
        height =top-bottom
        xmin, xmax = ax.get_xlim()
        ymin, ymax = ax.get_ylim()
        xdata = xmin + (xc - left) * (xmax - xmin)/width
        ydata = ymin + (yc - bottom) * (ymax - ymin)/height
        return (xdata, ydata)

    def onmove(self, event):
        event.xdata, event.ydata = self.xydata(event.x, event.y)
        MultiCursor.onmove(self, event)
        #make event data
        evt = self.CursorMoveEvent(pos = (event.xdata, event.ydata))
        #post event to parent window
        wx.PostEvent(self.parent, evt)
        self.visible = True


0 件のコメント: