이벤트 처리 및 선택 #

Matplotlib는 다양한 사용자 인터페이스 툴킷(wxpython, tkinter, qt, gtk 및 macosx)과 함께 작동하며 그림의 대화형 패닝 및 확대/축소와 같은 기능을 지원하기 위해 그림과 상호 작용하기 위한 API를 갖는 것이 개발자에게 도움이 됩니다. "GUI 중립적"인 키 누르기와 마우스 움직임을 통해 다양한 사용자 인터페이스에서 많은 코드를 반복할 필요가 없습니다. 이벤트 처리 API는 GUI 중립적이지만 Matplotlib이 지원한 최초의 사용자 인터페이스인 GTK 모델을 기반으로 합니다. 트리거되는 이벤트는 이벤트가 발생한 것과 같은 정보를 포함하여 표준 GUI 이벤트보다 Matplotlib에 대해 조금 더 풍부합니다 Axes. 이벤트는 Matplotlib 좌표계를 이해하고 픽셀 및 데이터 좌표 모두에서 이벤트 위치를 보고합니다.

이벤트 연결 #

이벤트를 수신하려면 콜백 함수를 작성한 다음 함수를 의 일부인 이벤트 관리자에 연결해야 합니다 FigureCanvasBase. 다음은 마우스 클릭 위치와 누른 버튼을 인쇄하는 간단한 예입니다.

fig, ax = plt.subplots()
ax.plot(np.random.rand(10))

def onclick(event):
    print('%s click: button=%d, x=%d, y=%d, xdata=%f, ydata=%f' %
          ('double' if event.dblclick else 'single', event.button,
           event.x, event.y, event.xdata, event.ydata))

cid = fig.canvas.mpl_connect('button_press_event', onclick)

FigureCanvasBase.mpl_connect메서드는 다음을 통해 콜백 연결을 끊는 데 사용할 수 있는 연결 ID(정수)를 반환합니다.

fig.canvas.mpl_disconnect(cid)

메모

캔버스는 콜백으로 사용되는 인스턴스 메서드에 대한 약한 참조만 유지합니다. 따라서 이러한 메서드를 소유하는 인스턴스에 대한 참조를 유지해야 합니다. 그렇지 않으면 인스턴스가 가비지 수집되고 콜백이 사라집니다.

이것은 콜백으로 사용되는 자유 함수에는 영향을 미치지 않습니다.

연결할 수 있는 이벤트, 이벤트가 발생할 때 다시 전송되는 클래스 인스턴스 및 이벤트 설명은 다음과 같습니다.

이벤트 이름

수업

설명

'button_press_event'

MouseEvent

마우스 버튼이 눌려있다

'버튼_해제_이벤트'

MouseEvent

마우스 버튼이 해제됨

'close_event'

CloseEvent

그림이 닫혀있다

'그리기 이벤트'

DrawEvent

캔버스가 그려졌습니다(그러나 화면 위젯은 아직 업데이트되지 않았습니다).

'key_press_event'

KeyEvent

키를 눌렀다

'key_release_event'

KeyEvent

열쇠가 풀린다

'motion_notify_event'

MouseEvent

마우스 움직임

'pick_event'

PickEvent

캔버스의 아티스트가 선택되었습니다.

'resize_event'

ResizeEvent

그림 캔버스의 크기가 조정됩니다.

'scroll_event'

MouseEvent

마우스 스크롤 휠이 굴러감

'figure_enter_event'

LocationEvent

마우스가 새 그림으로 들어감

'figure_leave_event'

LocationEvent

쥐가 그림을 남긴다

'axes_enter_event'

LocationEvent

마우스가 새 축에 들어감

'axes_leave_event'

LocationEvent

마우스가 축을 떠난다

메모

'key_press_event' 및 'key_release_event' 이벤트에 연결할 때 Matplotlib와 함께 작동하는 다양한 사용자 인터페이스 툴킷 간에 불일치가 발생할 수 있습니다. 이는 사용자 인터페이스 툴킷의 불일치/제한 때문입니다. 다음 표는 다양한 사용자 인터페이스 툴킷에서 키(QWERTY 키보드 레이아웃 사용)로 받을 수 있는 몇 가지 기본 예를 보여줍니다. 여기에서 쉼표는 다른 키를 구분합니다.

키 누름

WxPython

Qt

WebAgg

GTK

티킨터

맥 OS X

시프트+2

시프트, 시프트+2

옮기다, @

옮기다, @

옮기다, @

옮기다, @

옮기다, @

시프트+F1

시프트, 시프트+f1

시프트, 시프트+f1

시프트, 시프트+f1

시프트, 시프트+f1

시프트, 시프트+f1

시프트, 시프트+f1

옮기다

옮기다

옮기다

옮기다

옮기다

옮기다

옮기다

제어

제어

제어

제어

제어

제어

제어

대체

대안

대안

대안

대안

대안

대안

AltGr

아무것도 아님

아무것도 아님

대안

iso_level3_shift

iso_level3_shift

캡스락

caps_lock

caps_lock

caps_lock

caps_lock

caps_lock

caps_lock

CapsLock+A

caps_lock,

caps_lock,

caps_lock, A

caps_lock, A

caps_lock, A

caps_lock,

Shift+a

시프트, A

시프트, A

시프트, A

시프트, A

시프트, A

시프트, A

CapsLock+Shift+a

caps_lock, 시프트, A

caps_lock, 시프트, A

caps_lock, 시프트,

caps_lock, 시프트,

caps_lock, 시프트,

caps_lock, 시프트, A

Ctrl+Shift+Alt

컨트롤, Ctrl+Shift, Ctrl+Alt

컨트롤, Ctrl+Shift, Ctrl+메타

컨트롤, Ctrl+Shift, Ctrl+메타

컨트롤, Ctrl+Shift, Ctrl+메타

컨트롤, Ctrl+Shift, Ctrl+메타

컨트롤, ctrl+shift, ctrl+alt+shift

Ctrl+Shift+a

컨트롤, ctrl+shift, ctrl+A

컨트롤, ctrl+shift, ctrl+A

컨트롤, ctrl+shift, ctrl+A

컨트롤, ctrl+shift, ctrl+A

컨트롤, ctrl+shift, ctrl+a

컨트롤, ctrl+shift, ctrl+A

F1

f1

f1

f1

f1

f1

f1

Ctrl+F1

컨트롤, Ctrl+F1

컨트롤, Ctrl+F1

컨트롤, Ctrl+F1

컨트롤, Ctrl+F1

컨트롤, Ctrl+F1

제어, 아무것도

Matplotlib는 상호작용을 위해 기본적으로 일부 키 누르기 콜백을 첨부합니다. 탐색 키보드 단축키 섹션 에 설명되어 있습니다.

이벤트 속성 #

matplotlib.backend_bases.Event모든 Matplotlib 이벤트 는 속성을 저장하는 기본 클래스에서 상속됩니다 .

name

이벤트 이름

canvas

이벤트를 생성하는 FigureCanvas 인스턴스

guiEvent

Matplotlib 이벤트를 트리거한 GUI 이벤트

이벤트 처리의 기본이 되는 가장 일반적인 이벤트는 키 누름/해제 이벤트와 마우스 누름/해제 및 이동 이벤트입니다. 이러한 이벤트를 처리하는 KeyEventMouseEvent클래스는 모두 다음 속성이 있는 LocationEvent에서 파생됩니다.

x,y

캔버스의 왼쪽과 아래쪽에서 마우스 x 및 y 위치(픽셀 단위)

inaxes

마우스가 있는 Axes인스턴스(있는 경우); 그렇지 않으면 없음

xdata,ydata

마우스가 축 위에 있는 경우 데이터 좌표에서 마우스 x 및 y 위치

마우스를 누를 때마다 간단한 선분이 생성되는 캔버스의 간단한 예를 살펴보겠습니다.

from matplotlib import pyplot as plt

class LineBuilder:
    def __init__(self, line):
        self.line = line
        self.xs = list(line.get_xdata())
        self.ys = list(line.get_ydata())
        self.cid = line.figure.canvas.mpl_connect('button_press_event', self)

    def __call__(self, event):
        print('click', event)
        if event.inaxes!=self.line.axes: return
        self.xs.append(event.xdata)
        self.ys.append(event.ydata)
        self.line.set_data(self.xs, self.ys)
        self.line.figure.canvas.draw()

fig, ax = plt.subplots()
ax.set_title('click to build line segments')
line, = ax.plot([0], [0])  # empty line
linebuilder = LineBuilder(line)

plt.show()

MouseEvent방금 사용한 는 이므로 LocationEvent및 를 통해 데이터 및 픽셀 좌표에 액세스할 수 있습니다 . 속성 외에도 다음 이 있습니다.(event.x, event.y)(event.xdata, event.ydata)LocationEvent

button

눌린 버튼: None, MouseButton, '위로' 또는 '아래로' (위와 아래는 스크롤 이벤트에 사용됨)

key

누른 키: 없음, 모든 문자, 'shift', 'win' 또는 'control'

드래그 가능한 사각형 연습 #

Rectangle인스턴스로 초기화되지만 xy 드래그하면 위치 가 이동 되는 드래그 가능한 사각형 클래스를 작성 합니다. xy힌트: rect.xy로 저장된 사각형 의 원래 위치를 저장하고 마우스 누르기, 동작 및 놓기 이벤트에 연결해야 합니다. 마우스를 눌렀을 때 사각형 위에서 클릭이 발생하는지 확인하고( 참조 Rectangle.contains) 클릭이 발생하면 사각형 xy와 마우스 클릭 위치를 데이터 좌표에 저장합니다. 모션 이벤트 콜백에서 마우스 움직임의 델타 및 델타를 계산하고 해당 델타를 저장한 사각형의 원점에 추가합니다. 그림을 다시 그립니다. 버튼 해제 이벤트에서 없음으로 저장한 모든 버튼 누름 데이터를 재설정하면 됩니다.

해결책은 다음과 같습니다.

import numpy as np
import matplotlib.pyplot as plt

class DraggableRectangle:
    def __init__(self, rect):
        self.rect = rect
        self.press = None

    def connect(self):
        """Connect to all the events we need."""
        self.cidpress = self.rect.figure.canvas.mpl_connect(
            'button_press_event', self.on_press)
        self.cidrelease = self.rect.figure.canvas.mpl_connect(
            'button_release_event', self.on_release)
        self.cidmotion = self.rect.figure.canvas.mpl_connect(
            'motion_notify_event', self.on_motion)

    def on_press(self, event):
        """Check whether mouse is over us; if so, store some data."""
        if event.inaxes != self.rect.axes:
            return
        contains, attrd = self.rect.contains(event)
        if not contains:
            return
        print('event contains', self.rect.xy)
        self.press = self.rect.xy, (event.xdata, event.ydata)

    def on_motion(self, event):
        """Move the rectangle if the mouse is over us."""
        if self.press is None or event.inaxes != self.rect.axes:
            return
        (x0, y0), (xpress, ypress) = self.press
        dx = event.xdata - xpress
        dy = event.ydata - ypress
        # print(f'x0={x0}, xpress={xpress}, event.xdata={event.xdata}, '
        #       f'dx={dx}, x0+dx={x0+dx}')
        self.rect.set_x(x0+dx)
        self.rect.set_y(y0+dy)

        self.rect.figure.canvas.draw()

    def on_release(self, event):
        """Clear button press information."""
        self.press = None
        self.rect.figure.canvas.draw()

    def disconnect(self):
        """Disconnect all callbacks."""
        self.rect.figure.canvas.mpl_disconnect(self.cidpress)
        self.rect.figure.canvas.mpl_disconnect(self.cidrelease)
        self.rect.figure.canvas.mpl_disconnect(self.cidmotion)

fig, ax = plt.subplots()
rects = ax.bar(range(10), 20*np.random.rand(10))
drs = []
for rect in rects:
    dr = DraggableRectangle(rect)
    dr.connect()
    drs.append(dr)

plt.show()

추가 크레딧 : 블리팅을 사용하여 애니메이션 드로잉을 더 빠르고 부드럽게 만듭니다.

추가 크레딧 솔루션:

# Draggable rectangle with blitting.
import numpy as np
import matplotlib.pyplot as plt

class DraggableRectangle:
    lock = None  # only one can be animated at a time

    def __init__(self, rect):
        self.rect = rect
        self.press = None
        self.background = None

    def connect(self):
        """Connect to all the events we need."""
        self.cidpress = self.rect.figure.canvas.mpl_connect(
            'button_press_event', self.on_press)
        self.cidrelease = self.rect.figure.canvas.mpl_connect(
            'button_release_event', self.on_release)
        self.cidmotion = self.rect.figure.canvas.mpl_connect(
            'motion_notify_event', self.on_motion)

    def on_press(self, event):
        """Check whether mouse is over us; if so, store some data."""
        if (event.inaxes != self.rect.axes
                or DraggableRectangle.lock is not None):
            return
        contains, attrd = self.rect.contains(event)
        if not contains:
            return
        print('event contains', self.rect.xy)
        self.press = self.rect.xy, (event.xdata, event.ydata)
        DraggableRectangle.lock = self

        # draw everything but the selected rectangle and store the pixel buffer
        canvas = self.rect.figure.canvas
        axes = self.rect.axes
        self.rect.set_animated(True)
        canvas.draw()
        self.background = canvas.copy_from_bbox(self.rect.axes.bbox)

        # now redraw just the rectangle
        axes.draw_artist(self.rect)

        # and blit just the redrawn area
        canvas.blit(axes.bbox)

    def on_motion(self, event):
        """Move the rectangle if the mouse is over us."""
        if (event.inaxes != self.rect.axes
                or DraggableRectangle.lock is not self):
            return
        (x0, y0), (xpress, ypress) = self.press
        dx = event.xdata - xpress
        dy = event.ydata - ypress
        self.rect.set_x(x0+dx)
        self.rect.set_y(y0+dy)

        canvas = self.rect.figure.canvas
        axes = self.rect.axes
        # restore the background region
        canvas.restore_region(self.background)

        # redraw just the current rectangle
        axes.draw_artist(self.rect)

        # blit just the redrawn area
        canvas.blit(axes.bbox)

    def on_release(self, event):
        """Clear button press information."""
        if DraggableRectangle.lock is not self:
            return

        self.press = None
        DraggableRectangle.lock = None

        # turn off the rect animation property and reset the background
        self.rect.set_animated(False)
        self.background = None

        # redraw the full figure
        self.rect.figure.canvas.draw()

    def disconnect(self):
        """Disconnect all callbacks."""
        self.rect.figure.canvas.mpl_disconnect(self.cidpress)
        self.rect.figure.canvas.mpl_disconnect(self.cidrelease)
        self.rect.figure.canvas.mpl_disconnect(self.cidmotion)

fig, ax = plt.subplots()
rects = ax.bar(range(10), 20*np.random.rand(10))
drs = []
for rect in rects:
    dr = DraggableRectangle(rect)
    dr.connect()
    drs.append(dr)

plt.show()

마우스 입력 및 종료 #

마우스가 Figure 또는 축에 들어가거나 나올 때 알림을 받으려면 Figure/axes enter/leave 이벤트에 연결할 수 있습니다. 다음은 마우스가 있는 축과 그림 배경의 색상을 변경하는 간단한 예입니다.

"""
Illustrate the figure and axes enter and leave events by changing the
frame colors on enter and leave
"""
import matplotlib.pyplot as plt

def enter_axes(event):
    print('enter_axes', event.inaxes)
    event.inaxes.patch.set_facecolor('yellow')
    event.canvas.draw()

def leave_axes(event):
    print('leave_axes', event.inaxes)
    event.inaxes.patch.set_facecolor('white')
    event.canvas.draw()

def enter_figure(event):
    print('enter_figure', event.canvas.figure)
    event.canvas.figure.patch.set_facecolor('red')
    event.canvas.draw()

def leave_figure(event):
    print('leave_figure', event.canvas.figure)
    event.canvas.figure.patch.set_facecolor('grey')
    event.canvas.draw()

fig1, axs = plt.subplots(2)
fig1.suptitle('mouse hover over figure or axes to trigger events')

fig1.canvas.mpl_connect('figure_enter_event', enter_figure)
fig1.canvas.mpl_connect('figure_leave_event', leave_figure)
fig1.canvas.mpl_connect('axes_enter_event', enter_axes)
fig1.canvas.mpl_connect('axes_leave_event', leave_axes)

fig2, axs = plt.subplots(2)
fig2.suptitle('mouse hover over figure or axes to trigger events')

fig2.canvas.mpl_connect('figure_enter_event', enter_figure)
fig2.canvas.mpl_connect('figure_leave_event', leave_figure)
fig2.canvas.mpl_connect('axes_enter_event', enter_axes)
fig2.canvas.mpl_connect('axes_leave_event', leave_axes)

plt.show()

개체 선택 #

picker의 속성 Artist(예: Line2D, Text, Patch, Polygon, AxesImage등) 을 설정하여 선택을 활성화할 수 있습니다 .

속성 은 picker다양한 유형을 사용하여 설정할 수 있습니다.

None

이 아티스트에 대해 선택이 비활성화되어 있습니다(기본값).

boolean

True이면 피킹이 활성화되고 마우스 이벤트가 아티스트 위에 있으면 아티스트가 pick 이벤트를 발생시킵니다.

callable

picker가 콜러블인 경우 아티스트가 마우스 이벤트에 맞았는지 여부를 결정하는 사용자 제공 함수입니다. 서명은 적중 테스트를 결정하는 것입니다. 마우스 이벤트가 아티스트 위에 있으면 를 반환합니다 . 의 추가 속성이 되는 속성의 사전입니다 .hit, props = picker(artist, mouseevent)hit = TruepropsPickEvent

아티스트의 pickradius속성은 마우스가 얼마나 멀리 있고 여전히 마우스 이벤트를 트리거할 수 있는지를 결정하는 포인트(인치당 72포인트)의 허용 오차 값으로 추가로 설정할 수 있습니다.

속성 을 설정하여 아티스트의 피킹을 활성화한 후 picker 핸들러를 그림 캔버스 pick_event에 연결하여 마우스 누르기 이벤트에 대한 콜백을 가져와야 합니다. 처리기는 일반적으로 다음과 같습니다.

def pick_handler(event):
    mouseevent = event.mouseevent
    artist = event.artist
    # now do something with this...

콜백에 전달 된 PickEvent에는 항상 다음 속성이 있습니다.

mouseevent

MouseEvent선택 이벤트를 생성하는 입니다 . 마우스 이벤트에 대한 유용한 속성 목록은 이벤트 속성 을 참조하십시오 .

artist

Artistpick 이벤트를 생성한 입니다 .

또한 특정 아티스트 는 피커 기준(예: 지정된 허용 오차 내에 있는 선의 모든 지점)을 충족하는 데이터의 인덱스와 같은 추가 메타데이터를 좋아하고 첨부할 수 Line2D있습니다 .PatchCollectionpickradius

간단한 피킹 예시 #

아래 예에서는 선에서 선택을 활성화하고 선택 반경 공차를 포인트로 설정합니다. 콜백 함수 는 onpick 선으로부터 허용 오차 범위 내에서 선택 이벤트가 발생하고 선택 거리 허용 범위 내에 있는 데이터 정점의 인덱스를 가질 때 호출됩니다. 콜백 onpick 함수는 단순히 선택 위치 아래에 있는 데이터를 인쇄합니다. 다른 Matplotlib 아티스트는 PickEvent에 다른 데이터를 첨부할 수 있습니다. 예를 들어 Line2D선택 지점 아래의 선 데이터에 대한 인덱스인 ind 속성을 첨부합니다. 선의 속성에 Line2D.pick대한 자세한 내용은 를 참조하십시오 .PickEvent

import numpy as np
import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.set_title('click on points')

line, = ax.plot(np.random.rand(100), 'o',
                picker=True, pickradius=5)  # 5 points tolerance

def onpick(event):
    thisline = event.artist
    xdata = thisline.get_xdata()
    ydata = thisline.get_ydata()
    ind = event.ind
    points = tuple(zip(xdata[ind], ydata[ind]))
    print('onpick points:', points)

fig.canvas.mpl_connect('pick_event', onpick)

plt.show()

따기 운동 #

1000개의 가우시안 난수로 구성된 100개의 어레이로 구성된 데이터 세트를 생성하고 각각의 샘플 평균 및 표준 편차를 계산합니다(힌트: NumPy 어레이에는 평균 및 표준 방법이 있음). 100 평균 대 100의 xy 마커 플롯을 만듭니다. 표준 편차. plot 명령으로 만든 선을 pick 이벤트에 연결하고 클릭한 포인트를 생성한 데이터의 원래 시계열을 플로팅합니다. 둘 이상의 포인트가 클릭한 포인트의 허용 오차 내에 있는 경우 여러 서브플롯을 사용하여 여러 시계열을 그릴 수 있습니다.

운동 솔루션:

"""
Compute the mean and stddev of 100 data sets and plot mean vs. stddev.
When you click on one of the (mean, stddev) points, plot the raw dataset
that generated that point.
"""

import numpy as np
import matplotlib.pyplot as plt

X = np.random.rand(100, 1000)
xs = np.mean(X, axis=1)
ys = np.std(X, axis=1)

fig, ax = plt.subplots()
ax.set_title('click on point to plot time series')
line, = ax.plot(xs, ys, 'o', picker=True, pickradius=5)  # 5 points tolerance


def onpick(event):
    if event.artist != line:
        return
    n = len(event.ind)
    if not n:
        return
    fig, axs = plt.subplots(n, squeeze=False)
    for dataind, ax in zip(event.ind, axs.flat):
        ax.plot(X[dataind])
        ax.text(0.05, 0.9,
                f"$\\mu$={xs[dataind]:1.3f}\n$\\sigma$={ys[dataind]:1.3f}",
                transform=ax.transAxes, verticalalignment='top')
        ax.set_ylim(-0.5, 1.5)
    fig.show()
    return True


fig.canvas.mpl_connect('pick_event', onpick)
plt.show()