文章目錄
- 1、matplotlib.animation
- 1.1、FuncAnimation
- 1.2、修改 matplotlib 背景
- 2、matplotlib + imageio
- 2.1、折線圖
- 2.2、條形圖
- 2.3、散點圖
- 3、參考
1、matplotlib.animation
1.1、FuncAnimation
matplotlib.animation.FuncAnimation
是 Matplotlib 庫中用于創建動畫的一個類。它允許你通過循環調用一個函數來更新圖表,從而生成動畫效果。這個函數通常被稱為“更新函數”,它決定了每一幀圖表的樣子。FuncAnimation 類提供了一種靈活而強大的方式來創建和展示動畫,使得數據可視化更加生動和直觀。
(1)基本用法
使用 FuncAnimation 創建動畫的基本步驟如下:
- 準備數據:首先,你需要準備好用于動畫的數據。這可能包括一系列的X和Y坐標點、顏色、大小等,具體取決于你要制作的動畫類型。
- 創建圖形和軸:使用 Matplotlib 創建圖形(Figure)和軸(Axes)對象,這些對象將作為動畫的畫布。
- 定義更新函數:編寫一個函數,這個函數接受當前的幀號(或其他參數)作為輸入,并返回一個更新后的圖形元素狀態。例如,如果你正在制作一個點的移動動畫,這個函數可能會更新點的位置。
- 創建 FuncAnimation 對象:使用 FuncAnimation 類創建一個動畫對象。你需要指定圖形對象、軸對象、更新函數、幀數(或時間間隔)、以及其他可選參數(如重復次數、初始延遲等)。
- 顯示或保存動畫:最后,你可以使用 Matplotlib 的顯示功能(如 plt.show())來查看動畫,或者將其保存為文件(如GIF、MP4等)。
(2)示例代碼
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation# 準備數據
x = np.linspace(0, 2 * np.pi, 100)
y = np.sin(x)# 創建圖形和軸
fig, ax = plt.subplots()
line, = ax.plot([], [], 'r-') # 初始化一個空線條對象
ax.set_xlim(0, 2 * np.pi) # 設置X軸范圍
ax.set_ylim(-1.5, 1.5) # 設置Y軸范圍# 定義更新函數
def update(frame):line.set_data(x[:frame], y[:frame]) # 更新線條數據return line,# 創建 FuncAnimation 對象
ani = FuncAnimation(fig, update, frames=len(x), interval=50, blit=True)# 顯示動畫
plt.show()
在這個例子中,update 函數根據當前的幀號(frame)更新線條的數據,使得線條逐漸變長,模擬了一個點沿正弦曲線移動的動畫效果。
再看一個例子
#coding=utf-8
import sysimport numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animationfig, ax = plt.subplots()x = np.arange(0, 2*np.pi, 0.01)
line, = ax.plot(x, np.sin(x))def animate(i):line.set_ydata(np.sin(x + i/10.0))return line,def init():line.set_ydata(np.ma.array(x, mask=True))return line,ani = animation.FuncAnimation(fig, animate, np.arange(1, 200), init_func=init,interval=25, blit=True)
ani.save("animation.gif", writer="imagemagick", fps=30)
plt.show()
(3)matplotlib.animation.FuncAnimation
class matplotlib.animation.FuncAnimation(fig, func, frames=None, init_func=None, fargs=None, save_count=None, *, cache_frame_data=True, **kwargs)
def __init__(self,fig: Figure,func: (...) -> Iterable[Artist],frames: Iterable | int | () -> Generator | None = ...,init_func: () -> Iterable[Artist] | None = ...,fargs: tuple[Any, ...] | None = ...,save_count: int | None = ...,*,cache_frame_data: bool = ...,**kwargs: Any) -> None
`TimedAnimation` subclass that makes an animation by repeatedly calling a function *func*. .. note:: You must store the created Animation in a variable that lives as long as the animation should run. Otherwise, the Animation object will be garbage-collected and the animation stops. Parameters ---------- fig : `~matplotlib.figure.Figure` The figure object used to get needed events, such as draw or resize. func : callable The function to call at each frame. The first argument will be the next value in *frames*. Any additional positional arguments can be supplied using `functools.partial` or via the *fargs* parameter. The required signature is:: def func(frame, *fargs) -> iterable_of_artists It is often more convenient to provide the arguments using `functools.partial`. In this way it is also possible to pass keyword arguments. To pass a function with both positional and keyword arguments, set all arguments as keyword arguments, just leaving the *frame* argument unset:: def func(frame, art, *, y=None): ... ani = FuncAnimation(fig, partial(func, art=ln, y='foo')) If ``blit == True``, *func* must return an iterable of all artists that were modified or created. This information is used by the blitting algorithm to determine which parts of the figure have to be updated. The return value is unused if ``blit == False`` and may be omitted in that case. frames : iterable, int, generator function, or None, optional Source of data to pass *func* and each frame of the animation - If an iterable, then simply use the values provided. If the iterable has a length, it will override the *save_count* kwarg. - If an integer, then equivalent to passing ``range(frames)`` - If a generator function, then must have the signature:: def gen_function() -> obj - If *None*, then equivalent to passing ``itertools.count``. In all of these cases, the values in *frames* is simply passed through to the user-supplied *func* and thus can be of any type. init_func : callable, optional A function used to draw a clear frame. If not given, the results of drawing from the first item in the frames sequence will be used. This function will be called once before the first frame. The required signature is:: def init_func() -> iterable_of_artists If ``blit == True``, *init_func* must return an iterable of artists to be re-drawn. This information is used by the blitting algorithm to determine which parts of the figure have to be updated. The return value is unused if ``blit == False`` and may be omitted in that case. fargs : tuple or None, optional Additional arguments to pass to each call to *func*. Note: the use of `functools.partial` is preferred over *fargs*. See *func* for details. save_count : int, optional Fallback for the number of values from *frames* to cache. This is only used if the number of frames cannot be inferred from *frames*, i.e. when it's an iterator without length or a generator. interval : int, default: 200 Delay between frames in milliseconds. repeat_delay : int, default: 0 The delay in milliseconds between consecutive animation runs, if *repeat* is True. repeat : bool, default: True Whether the animation repeats when the sequence of frames is completed. blit : bool, default: False Whether blitting is used to optimize drawing. Note: when using blitting, any animated artists will be drawn according to their zorder; however, they will be drawn on top of any previous artists, regardless of their zorder. cache_frame_data : bool, default: True Whether frame data is cached. Disabling cache might be helpful when frames contain large objects.
Params:
fig – The figure object used to get needed events, such as draw or resize.
func – The function to call at each frame. The first argument will be the next value in *frames*. Any additional positional arguments can be supplied using `functools.partial` or via the *fargs* parameter. The required signature is:: def func(frame, *fargs) -> iterable_of_artists It is often more convenient to provide the arguments using `functools.partial`. In this way it is also possible to pass keyword arguments. To pass a function with both positional and keyword arguments, set all arguments as keyword arguments, just leaving the *frame* argument unset:: def func(frame, art, *, y=None): ... ani = FuncAnimation(fig, partial(func, art=ln, y='foo')) If ``blit == True``, *func* must return an iterable of all artists that were modified or created. This information is used by the blitting algorithm to determine which parts of the figure have to be updated. The return value is unused if ``blit == False`` and may be omitted in that case.
frames – Source of data to pass *func* and each frame of the animation - If an iterable, then simply use the values provided. If the iterable has a length, it will override the *save_count* kwarg. - If an integer, then equivalent to passing ``range(frames)`` - If a generator function, then must have the signature:: def gen_function() -> obj - If *None*, then equivalent to passing ``itertools.count``. In all of these cases, the values in *frames* is simply passed through to the user-supplied *func* and thus can be of any type.
init_func – A function used to draw a clear frame. If not given, the results of drawing from the first item in the frames sequence will be used. This function will be called once before the first frame. The required signature is:: def init_func() -> iterable_of_artists If ``blit == True``, *init_func* must return an iterable of artists to be re-drawn. This information is used by the blitting algorithm to determine which parts of the figure have to be updated. The return value is unused if ``blit == False`` and may be omitted in that case.
fargs – Additional arguments to pass to each call to *func*. Note: the use of `functools.partial` is preferred over *fargs*. See *func* for details.
save_count – Fallback for the number of values from *frames* to cache. This is only used if the number of frames cannot be inferred from *frames*, i.e. when it's an iterator without length or a generator.
cache_frame_data – Whether frame data is cached. Disabling cache might be helpful when frames contain large objects.
- fig:圖形對象(Figure),用于獲取繪制、調整大小等事件。這是動畫的畫布。
- func:可調用對象(函數),每幀調用的函數。該函數的第一個參數將是 frames 中的下一個值。任何其他的位置參數可以通過 fargs 參數提供。如果 blit 為 True,則該函數必須返回一個被修改或創建的所有圖形元素(artists)的可迭代對象。
- frames:可迭代對象、整數、生成器函數或 None,可選。用于傳遞給 func 和動畫的每一幀的數據源。如果是可迭代對象,則直接使用提供的值。如果是一個整數,則相當于傳遞 range(frames)。如果是一個生成器函數,則必須具有特定的簽名。如果為 None,則相當于傳遞 itertools.count。
- init_func:可調用對象(函數),可選。用于繪制清空畫面的函數。如果未提供,則將使用 frames 序列中的第一個項目的繪圖結果。此函數將在第一幀之前被調用一次。如果 blit 為 True,則 init_func 必須返回一個將被重新繪制的圖形元素(artists)的可迭代對象。
- fargs:元組或 None,可選。傳遞給每次調用 func 的附加參數。
- save_count:整數,可選。要緩存的 frames 中的值的數量。
- interval:數字,可選。幀之間的延遲時間(以毫秒為單位)。默認為 200。
- blit:布爾值,可選。控制是否使用 blitting 來優化繪制。當使用 blitting 時,只有變化的圖形元素會被重新繪制,從而提高性能。
- cache_frame_data:布爾值,可選。控制是否緩存幀數據。默認為 True。
方法說明
- save:將動畫保存為電影文件。
- to_html5_video:將動畫轉換為 HTML5 視頻。
- to_jshtml:生成動畫的 HTML 表示形式。
(4)注意事項
- 性能:對于復雜的動畫,可能需要優化性能,比如通過減少每次更新的數據量(使用 blit=True 參數)或調整幀的更新間隔。
- 兼容性:保存動畫時,不同的文件格式(如GIF、MP4)可能需要不同的編解碼器支持。確保你的環境中安裝了必要的編解碼器。
- 交互性:動畫在Jupyter Notebook等交互式環境中可能表現不同,需要根據具體環境調整顯示方式。
1.2、修改 matplotlib 背景
在上述示例代碼的情況下,我們引入一些修改顏色的配置,
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation# 準備數據
x = np.linspace(0, 2 * np.pi, 100)
y = np.sin(x)# 創建圖形和軸
fig, ax = plt.subplots()
line, = ax.plot([], [], 'r-') # 初始化一個空線條對象
ax.set_xlim(0, 2 * np.pi) # 設置X軸范圍
ax.set_ylim(-1.5, 1.5) # 設置Y軸范圍# 修改軸背景顏色
ax.set_facecolor("orange")
# OR
# ax.set(facecolor = "orange")# 修改繪圖背景顏色
fig.patch.set_facecolor('yellow')
fig.patch.set_alpha(1.0)# 移除圖表的上邊框和右邊框
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)# 設置虛線網格線
ax.set_axisbelow(True)
ax.yaxis.grid(color='gray', linestyle='dashed', alpha=0.7)# 定義更新函數
def update(frame):line.set_data(x[:frame], y[:frame]) # 更新線條數據return line,# 創建 FuncAnimation 對象
ani = FuncAnimation(fig, update, frames=len(x), interval=50, blit=True)# ani.save("animation.gif", writer="imagemagick", fps=30)# 顯示動畫
plt.show()
修改前
修改后
換個背景圖試試
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation# 準備數據
x = np.linspace(0, 20 * np.pi, 100)
y = 9* np.sin(x)# 創建圖形和軸
fig, ax = plt.subplots()
line, = ax.plot([], [], 'r-') # 初始化一個空線條對象img = plt.imread("123.jpg")
ax.imshow(img, extent=[0, 65, -10, 10]) # 橫縱坐標范圍# 定義更新函數
def update(frame):line.set_data(x[:frame], y[:frame]) # 更新線條數據return line,# 創建 FuncAnimation 對象
ani = FuncAnimation(fig, update, frames=len(x), interval=50, blit=True)ani.save("animation.gif", writer="imagemagick", fps=30)# 顯示動畫
plt.show()
原始圖片
添加之后的效果
2、matplotlib + imageio
2.1、折線圖
先畫個簡單的折線圖
import os
import numpy as np
import matplotlib.pyplot as plt
import imageionp.random.seed(1234)# 生成40個取值在30-40的數
y = np.random.randint(30, 40, size=(40))
print(y)
"""
[33 36 35 34 38 39 31 37 39 36 38 30 35 30 39 36 32 30 35 32 36 33 37 3039 30 33 32 33 31 33 31 33 37 31 37 34 30 35 31]
"""
# 繪制折線
plt.plot(y)
# 設置y軸最小值和最大值
plt.ylim(20, 50)# 顯示
plt.show()
保存最后幾個點的數據,然后繪制成 gif
import os
import numpy as np
import matplotlib.pyplot as plt
import imageionp.random.seed(1234)# 生成40個取值在30-40的數
y = np.random.randint(30, 40, size=(40))
print(y)
"""
[33 36 35 34 38 39 31 37 39 36 38 30 35 30 39 36 32 30 35 32 36 33 37 3039 30 33 32 33 31 33 31 33 37 31 37 34 30 35 31]
"""
# 繪制折線
plt.plot(y)
# 設置y軸最小值和最大值
plt.ylim(20, 50)# 顯示
plt.show()# 第一張圖
plt.plot(y[:-3])
plt.ylim(20, 50)
plt.savefig('1.png')
plt.show()# 第二張圖
plt.plot(y[:-2])
plt.ylim(20, 50)
plt.savefig('2.png')
plt.show()# 第三張圖
plt.plot(y[:-1])
plt.ylim(20, 50)
plt.savefig('3.png')
plt.show()# 第四張圖
plt.plot(y)
plt.ylim(20, 50)
plt.savefig('4.png')
plt.show()# 生成Gif
with imageio.get_writer('mygif.gif', mode='I') as writer:for filename in ['1.png', '2.png', '3.png', '4.png']:image = imageio.imread(filename)writer.append_data(image)
橫坐標 0 至 36
橫坐標 0 至 37
橫坐標 0 至 38
橫坐標 0 至 39
合并成為 gif(僅播放一次)
下面把所有點都保存下來,繪制動態圖(僅播放一次)
import os
import numpy as np
import matplotlib.pyplot as plt
import imageionp.random.seed(1234)# 生成40個取值在30-40的數
y = np.random.randint(30, 40, size=(40))
print(y)
"""
[33 36 35 34 38 39 31 37 39 36 38 30 35 30 39 36 32 30 35 32 36 33 37 3039 30 33 32 33 31 33 31 33 37 31 37 34 30 35 31]
"""
# 繪制折線
plt.plot(y)
# 設置y軸最小值和最大值
plt.ylim(20, 50)# 顯示
plt.show()filenames = []
num = 0
for i in y:num += 1# 繪制40張折線圖plt.plot(y[:num])plt.ylim(20, 50)# 保存圖片文件filename = f'{num}.png'filenames.append(filename)plt.savefig(filename)plt.close()# 生成gif
with imageio.get_writer('mygif.gif', mode='I') as writer:for filename in filenames:image = imageio.imread(filename)writer.append_data(image)# 刪除40張折線圖
for filename in set(filenames):os.remove(filename)
2.2、條形圖
import os
import numpy as np
import matplotlib.pyplot as plt
import imageionp.random.seed(1234)x = [1, 2, 3, 4, 5]
coordinates_lists = [[0, 0, 0, 0, 0],[10, 30, 60, 30, 10],[70, 40, 20, 40, 70],[10, 20, 30, 40, 50],[50, 40, 30, 20, 10],[75, 0, 75, 0, 75],[0, 0, 0, 0, 0]]
filenames = []
for index, y in enumerate(coordinates_lists):# 條形圖plt.bar(x, y)plt.ylim(0, 80)# 保存圖片文件filename = f'{index}.png'filenames.append(filename)# 重復最后一張圖形15幀(數值都為0),15張圖片if (index == len(coordinates_lists) - 1):for i in range(15):filenames.append(filename)# 保存plt.savefig(filename)plt.close()# 生成gif
with imageio.get_writer('mygif.gif', mode='I') as writer:for filename in filenames:image = imageio.imread(filename)writer.append_data(image)# 刪除20張柱狀圖
for filename in set(filenames):os.remove(filename)
生成的圖片
生成的 gif(播放一次)
看起來太快了,優化代碼使其平滑
import os
import numpy as np
import matplotlib.pyplot as plt
import imageionp.random.seed(1234)n_frames = 10 # 怕內存不夠的話可以設置小一些
x = [1, 2, 3, 4, 5]
coordinates_lists = [[0, 0, 0, 0, 0],[10, 30, 60, 30, 10],[70, 40, 20, 40, 70],[10, 20, 30, 40, 50],[50, 40, 30, 20, 10],[75, 0, 75, 0, 75],[0, 0, 0, 0, 0]]
print('生成圖表\n')
filenames = []
for index in np.arange(0, len(coordinates_lists) - 1):# 獲取當前圖像及下一圖像的y軸坐標值y = coordinates_lists[index]y1 = coordinates_lists[index + 1]# 計算當前圖像與下一圖像y軸坐標差值y_path = np.array(y1) - np.array(y)for i in np.arange(0, n_frames + 1):# 分配每幀的y軸移動距離# 逐幀增加y軸的坐標值y_temp = (y + (y_path / n_frames) * i)# 繪制條形圖plt.bar(x, y_temp)plt.ylim(0, 80)# 保存每一幀的圖像filename = f'frame_{index}_{i}.png'filenames.append(filename)# 最后一幀重復,畫面停留一會if (i == n_frames):for i in range(5):filenames.append(filename)# 保存圖片plt.savefig(filename)plt.close()
print('保存圖表\n')# 生成GIF
print('生成GIF\n')
with imageio.get_writer('mybars.gif', mode='I') as writer:for filename in filenames:image = imageio.imread(filename)writer.append_data(image)
print('保存GIF\n')print('刪除圖片\n')
# 刪除圖片
for filename in set(filenames):os.remove(filename)
print('完成')
原理解釋統計柱狀圖當前幀和下一幀的差值,然后插幀平滑過去,這里插幀數量配置為了 n_frames = 10
最終生成的 gif 如下(僅播放一次),可以觀察到平滑了很多
接下來美化下界面
import os
import numpy as np
import matplotlib.pyplot as plt
import imageionp.random.seed(1234)n_frames = 5
bg_color = '#95A4AD'
bar_color = '#283F4E'
gif_name = 'bars'
x = [1, 2, 3, 4, 5]
coordinates_lists = [[0, 0, 0, 0, 0],[10, 30, 60, 30, 10],[70, 40, 20, 40, 70],[10, 20, 30, 40, 50],[50, 40, 30, 20, 10],[75, 0, 75, 0, 75],[0, 0, 0, 0, 0]]
print('生成圖表\n')
filenames = []
for index in np.arange(0, len(coordinates_lists) - 1):y = coordinates_lists[index]y1 = coordinates_lists[index + 1]y_path = np.array(y1) - np.array(y)for i in np.arange(0, n_frames + 1):y_temp = (y + (y_path / n_frames) * i)# 繪制條形圖fig, ax = plt.subplots(figsize=(8, 4))ax.set_facecolor(bg_color)plt.bar(x, y_temp, width=0.4, color=bar_color)plt.ylim(0, 80)# 移除圖表的上邊框和右邊框ax.spines['right'].set_visible(False)ax.spines['top'].set_visible(False)# 設置虛線網格線ax.set_axisbelow(True)ax.yaxis.grid(color='gray', linestyle='dashed', alpha=0.7)# 保存每一幀的圖像filename = f'images/frame_{index}_{i}.png'filenames.append(filename)# 最后一幀重復,畫面停留一會if (i == n_frames):for i in range(5):filenames.append(filename)# 保存圖片plt.savefig(filename, dpi=96, facecolor=bg_color)plt.close()
print('保存圖表\n')# 生成GIF
print('生成GIF\n')
with imageio.get_writer(f'{gif_name}.gif', mode='I') as writer:for filename in filenames:image = imageio.imread(filename)writer.append_data(image)
print('保存GIF\n')print('刪除圖片\n')
# 刪除圖片
for filename in set(filenames):os.remove(filename)
print('完成')
看看生成的 gif 效果(僅播放一次)
給圖表添加了背景色、條形圖上色、去除邊框、增加網格線等。
2.3、散點圖
import os
import numpy as np
import matplotlib.pyplot as plt
import imageionp.random.seed(1234)coordinates_lists = [[[0], [0]],[[100, 200, 300], [100, 200, 300]],[[400, 500, 600], [400, 500, 600]],[[400, 500, 600, 400, 500, 600], [400, 500, 600, 600, 500, 400]],[[500], [500]],[[0], [0]]]
gif_name = 'movie'
n_frames = 5
bg_color = '#95A4AD'
marker_color = '#283F4E'
marker_size = 25print('生成圖表\n')
filenames = []
for index in np.arange(0, len(coordinates_lists) - 1):# 獲取當前圖像及下一圖像的x與y軸坐標值x = coordinates_lists[index][0] # 當前幀y = coordinates_lists[index][1]x1 = coordinates_lists[index + 1][0] # 下一幀y1 = coordinates_lists[index + 1][1]# 查看兩點差值while len(x) < len(x1):diff = len(x1) - len(x)x = x + x[:diff]y = y + y[:diff]while len(x1) < len(x):diff = len(x) - len(x1)x1 = x1 + x1[:diff]y1 = y1 + y1[:diff]# 計算路徑x_path = np.array(x1) - np.array(x)y_path = np.array(y1) - np.array(y)for i in np.arange(0, n_frames + 1):# 計算當前位置x_temp = (x + (x_path / n_frames) * i)y_temp = (y + (y_path / n_frames) * i)# 繪制圖表fig, ax = plt.subplots(figsize=(6, 6), subplot_kw=dict(aspect="equal"))ax.set_facecolor(bg_color)plt.scatter(x_temp, y_temp, c=marker_color, s=marker_size)plt.xlim(0, 1000)plt.ylim(0, 1000)# 移除邊框線ax.spines['right'].set_visible(False)ax.spines['top'].set_visible(False)# 網格線ax.set_axisbelow(True)ax.yaxis.grid(color='gray', linestyle='dashed', alpha=0.7)ax.xaxis.grid(color='gray', linestyle='dashed', alpha=0.7)# 保存圖片filename = f'images/frame_{index}_{i}.png'filenames.append(filename)if (i == n_frames):for i in range(5):filenames.append(filename)# 保存plt.savefig(filename, dpi=96, facecolor=bg_color)plt.close()
print('保存圖表\n')# 生成GIF
print('生成GIF\n')
with imageio.get_writer(f'{gif_name}.gif', mode='I') as writer:for filename in filenames:image = imageio.imread(filename)writer.append_data(image)
print('保存GIF\n')print('刪除圖片\n')
# 刪除圖片
for filename in set(filenames):os.remove(filename)
print('完成')
思路,計算前后幀坐標點數量的差 diff
,然后 while 循環來復制以實現數量平衡 x = x + x[:diff]
,最后插幀平滑移動 x_temp = (x + (x_path / n_frames) * i)
3、參考
- 太強了,用 Matplotlib+Imageio 制作動畫!
- 如何在 Matplotlib 中更改繪圖背景