python3环境下用matplotlib库实现UI交互

在python的学习过程中我看到一个模拟鸟群的例子,并可以用鼠标左键创建一只新的鸟,用鼠标右键驱散鸟群。在我运行代码的时候发现,点击鼠标左键和右键均没有反应。于是查阅资料,最后找到了问题所在,现在将解决问题的过程记录下来,(如果只要结果的同学直接拉到最底下就可以了)

测试环境:
Win10,python3.6.4,matplotlib 2.1.2
测试代码:
(代码来源:《python极客项目编程》第五章)

import sys, argparse
import math
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from scipy.spatial.distance import squareform, pdist, cdist
from numpy.linalg import norm

width, height = 640, 480

class Boids:
    """class that represents Boids simulation"""
    def __init__(self, N):
        """initialize the Boid simulation"""
        # initial position and velocities
        self.pos = [width/2.0, height/2.0] + 10*np.random.rand(2*N).reshape(N, 2)
        # normalized random velocities
        angles = 2*math.pi*np.random.rand(N)
        self.vel = np.array(list(zip(np.sin(angles), np.cos(angles))))
        self.N = N
        # minimum distance of approach
        self.minDist = 25.0
        # maximum magnitude of velocities calculated by "rules"
        self.maxRuleVel = 0.03
        # maximum magnitude of the final velocity
        self.maxVel = 2.0

    def tick(self, frameNum, pts, beak):
        """Update the simulation by one time step."""
        # get pairwise distances
        self.distMatrix = squareform(pdist(self.pos))
        # apply rules:
        self.vel += self.applyRules()
        self.limit(self.vel, self.maxVel)
        self.pos += self.vel
        self.applyBC()
        # update data
        pts.set_data(self.pos.reshape(2*self.N)[::2],
                     self.pos.reshape(2*self.N)[1::2])
        vec = self.pos + 10*self.vel/self.maxVel
        beak.set_data(vec.reshape(2*self.N)[::2],
                      vec.reshape(2*self.N)[1::2])

    def limitVec(self, vec, maxVel):
        """limit the magnitide of the 2D vector"""
        mag = norm(vec)
        if mag > maxVel:
            vec[0], vec[1] = vec[0]*maxVel/mag, vec[1]*maxVel/mag

    def limit(self, X, maxVel):
        """limit the magnitide of 2D vectors in array X to maxValue"""
        for vec in X:
            self.limitVec(vec, maxVel)

    def applyBC(self):
        """apply boundary conditions"""
        deltaR = 2.0
        for coord in self.pos:
            if coord[0] > width + deltaR:
                coord[0] = -deltaR
            if coord[0] < - deltaR:
                coord[0] = width + deltaR
            if coord[1] > height + deltaR:
                coord[1] = - deltaR
            if coord[1] < - deltaR:
                coord[1] = height + deltaR

    def applyRules(self):
        # apply rule #1: Separation
        D = self.distMatrix < 25.0
        vel = self.pos * D.sum(axis=1).reshape(self.N, 1) - D.dot(self.pos)
        self.limit(vel, self.maxRuleVel)

        # distance threshold for alignment (different from separation)
        D = self.distMatrix < 50.0

        # apply rule #2: Alignment
        vel2 = D.dot(self.vel)
        self.limit(vel2, self.maxRuleVel)
        vel += vel2

        # apply rule #3: Cohesion
        vel3 = D.dot(self.pos) - self.pos
        self.limit(vel3, self.maxRuleVel)
        vel += vel3

        return vel

    def buttonPress(self, event):
        """event handler for matplotlib button presses"""
        # print('run')
        # left-click to add a boid
        if event.button is 1:
            print('left button')
            self.pos = np.concatenate((self.pos, np.array([[event.xdata, event.ydata]])), axis=0)
            # generate a random velocity
            angles = 2*math.pi*np.random.rand(1)
            v = np.array(list(zip(np.sin(angles), np.cos(angles))))
            self.vel = np.concatenate((self.vel, v), axis=0)
            self.N += 1
        # right-click to scatter boids
        elif event.button is 3:
            print('right button')
            # add scattering velocity
            self.vel += 0.1*(self.pos - np.array([[event.xdata, event.ydata]]))


def tick(frameNum, pts, beak, boids):
    # print frameNum
    """update function for animation"""
    boids.tick(frameNum, pts, beak)
    return pts, beak

# main() function
def main():
    # use sys.argv if needed
    print('starting boids...')

    parser = argparse.ArgumentParser(description="Implementing Craig Reynold's Boids...")
    # add arguments
    parser.add_argument('--num-boids', dest='N', required=False)
    args = parser.parse_args()

    # set the initial number of boids
    N = 100
    if args.N:
        N = int(args.N)

    # create boids
    boids = Boids(N)

    # set up plot
    fig = plt.figure()
    ax = plt.axes(xlim=(0, width), ylim=(0, height))

    pts, = ax.plot([], [], markersize=10, c='k', marker='o', ls='None')
    beak, = ax.plot([], [], markersize=4, c='r', marker='o', ls='None')


    # add a "button press" event handler
    fig.canvas.mpl_connect("button_press_event", boids.buttonPress)
    anim = animation.FuncAnimation(fig, tick, fargs=(pts, beak, boids), interval=50)
    plt.show()

# call main
if __name__ == '__main__':
    main()

正常运行,但是点击鼠标无反应。

思路:
首先,我想到的原因是matplotlib的UI交互部分可能已过时或已修改过。所以我谷歌了下matplotlib的UI交互功能实现。找到了这个网页(http://www.guofei.site/2017/09/26/matplotlib3.html)
这个网页写的比较清楚,而且配有例子,所以我单独新建一个文件测试这个网页上的两个例子。两个例子都是可以正常运行的。这就令我十分好奇,因为源代码中需要判断的是 event.button 这个变量,所以我就将例子做了下修改,只输出 event.button 变量。结果如下
测试代码:

import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots()
x = np.linspace(0, 10, 1000)
y = np.sin(x)
line = ax.plot(x, y)[0]


def on_key_press(event):
    # print(event.name)
    print(event.button)
    print(type(event.button))
    print(event.button is 1)
    print(event.button == 1)

fig.canvas.mpl_disconnect(fig.canvas.manager.key_press_handler_id) # 取消默认快捷键的注册
fig.canvas.mpl_connect('button_press_event', on_key_press)
plt.show()

结果当运行代码后,我点击鼠标左键,输出却是:

1
<class 'int'>
False
True

这时我突然想起之前看到的 is== 是有区别的。 is 判断的是前后两个值的地址是否相同,而 == 判断的才是前后两个值是否相同,如下:

>>> a = 1
>>> b = 1.0
>>> a is b
False
>>> a == b
True
>>> id(a)
1356033504
>>> id(b)
1802191773368

所以只需要将源代码中的 is 换为 == ,就可以创建和驱散鸟群了。

总结:
通过这个模拟鸟群的代码,学习到了在python中用matplotlib实现UI交互功能及 is== 的区别。

你可能感兴趣的:(python学习)