绘制闪烁的斑点群,需要考虑几个群体属性:群体的生成位置 (xylim),斑点数量 (n),斑点的半径均值 (r),斑点的寿命均值 (delta)
而对于每一个斑点,又需要考虑斑点个体属性:出生时间 (start)、出生位置 (xy)、半径 (radius)、颜色 (color)、寿命 (deltas),以控制斑点的外形和闪烁速度
在 Spot 这个对象中,我定义了以下几个函数:
- __init__:将形参写入闪烁斑点的群体属性,并调用 produce 生成指定数量的斑点
- produce:筛除寿命耗尽的斑点,并生成新的斑点的个体属性
- scatter:根据群体属性 xylim 在指定方框内生成斑点的中心点,可重写
- __call__:根据闪烁斑点的生存时间,计算闪烁斑点的不透明度,并利用 CoordSys_2d 对斑点的位置进行仿射变换,再逐一绘制斑点
import timeimport matplotlib.patches as pch
import matplotlib.pyplot as plt
import numpy as np# coord.py 详见: https://blog.csdn.net/qq_55745968/article/details/129912954
from coord import CoordSys_2dred = 'orangered'
orange = 'orange'
yellow = 'yellow'
green = 'greenyellow'
cyan = 'aqua'
blue = 'deepskyblue'
purple = 'mediumpurple'
pink = 'violet'class Spot:''' 闪烁斑点对象xylim: xy 坐标区间, [[xmin, ymin], [xmax, ymax]]n: 闪烁斑点的数量r: 斑点的半径均值, 标准差为 r/2, 最小值为 r/4delta: 斑点生存时间的均值, 标准差为 delta, 最小值为 delta/10'''colors = [red, orange, yellow, green, cyan, blue, purple, pink]def __init__(self, xylim: np.ndarray, n: int,r: float = .2, delta: float = 1., alpha: float = .7):# <群体属性>self.xylim = xylimself.n, self.r = n, rself.delta = deltaself.alpha = alpha# <个体属性># 出生时间, 生存时间self.start = np.array([])self.deltas = np.array([])# 出生位置, 半径, 颜色self.xy = np.ones([0, 2])self.radius = np.array([])self.color = np.array([])# 生产斑点self.produce()def scatter(self, n):return self.xylim[0] + np.random.rand(n, 2) * (self.xylim[1] - self.xylim[0])def produce(self, filt=None):# 筛除生存时间耗尽的斑点if isinstance(filt, np.ndarray):for key in ('start', 'color', 'xy', 'radius', 'deltas'):setattr(self, key, getattr(self, key)[filt])# 补全缺失的斑点lack = self.n - self.xy.shape[0]if lack > 0:# 记录出生时间, 生存时间self.start = np.concatenate((self.start, np.full(lack, fill_value=time.time())))self.deltas = np.concatenate((self.deltas, np.maximum(np.random.normal(loc=self.delta, scale=self.delta, size=lack), self.delta / 10)))# 随机位置, 随机半径, 随机颜色self.xy = np.concatenate((self.xy, self.scatter(lack)), axis=0)self.radius = np.concatenate((self.radius, np.maximum(np.random.normal(loc=self.r, scale=self.r / 2, size=lack), self.r / 4)))self.color = np.concatenate((self.color, np.random.choice(self.colors, size=lack)))def __call__(self, fig, state: CoordSys_2d = None):''' 刷新斑点的透明度state: CoordSys_2d 对象'''x = time.time() - self.start# y = 4/d^2 x (d - x)alpha = self.alpha * np.maximum(4 / self.deltas ** 2 * x * (self.deltas - x), 0)# 向图像添加斑点for i, xy in enumerate(np.stack(state.apply(*self.xy.T), axis=-1) if state else self.xy):patch = pch.Circle(xy, self.radius[i], alpha=alpha[i], edgecolor=None, facecolor=self.color[i])fig.add_patch(patch)self.produce(alpha > 0)
而对于闪烁斑点的旋转、平移,主要使用了 https://hebitzj.blog.csdn.net/article/details/129912954 中所编写的类 CoordSys_2d,这个类有以下几个主要函数:
- transform:给定 x,y 轴上的偏移量、旋转角,并执行相对变换 / 绝对变换
- apply:给定描述 x,y 的点集,根据齐次坐标系矩阵对该点集进行平移和旋转
利用这个类,可以实现闪烁斑点的持续旋转
if __name__ == '__main__':plt.rcParams['figure.figsize'] = [6.4, 6.4]fig = plt.subplot()# 初始化齐次坐标系state = CoordSys_2d()# 初始化闪烁斑点spot = Spot(xylim=np.array([[-2, 2], [-1, 1]]).T, n=100, r=.2, delta=1)while True:fig.cla()plt.xlim([-2, 2])plt.ylim([-2, 2])# 使当前的齐次坐标系旋转 2°state = state.transform(theta=2)# 调用闪烁斑点的 __call__ 函数绘制spot(fig, state=state)plt.pause(0.01)