在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
和 ==
的区别。