# Matplotlib 繪圖技巧:水波干涉動畫
> 作者:王一哲
> 日期:2019/12/12
<br />
## 前言
以前在維基百科上看到用 [MATLAB](https://www.mathworks.com/products/matlab.html) 繪製的兩個點波源水波干涉動畫,後來我參考網頁上的程式碼,試著用 [Scilab](https://www.scilab.org/) 畫出一樣的動畫,甚至把它改成立體圖。但是我現在比較常用 Matplotlib 套件繪圖,也在網路上看過一些用 Matplotlib 繪製的 gif 檔,於是我花了一點時間研究一下繪製動畫的方法。
<img style="display: block; margin-left: auto; margin-right: auto" height="60%" width="60%" src="https://upload.wikimedia.org/wikipedia/commons/2/2c/Two_sources_interference.gif">
<div style="text-align:center">維基百科兩個點波源水波干涉動畫,圖片來源為 https://en.wikipedia.org/wiki/File:Two_sources_interference.gif</div>
<br />
<img style="display: block; margin-left: auto; margin-right: auto" height="80%" width="80%" src="https://4.bp.blogspot.com/-AYBNRb-kg-w/UelKHdFzXkI/AAAAAAAABmQ/trD_k5yyzio/s1600/Two_sources_interference.gif">
<div style="text-align:center">使用 Scilab 自行繪製的水波干涉動畫(2D版)</div>
<br />
<img style="display: block; margin-left: auto; margin-right: auto" height="80%" width="80%" src="https://1.bp.blogspot.com/-kylwbicgThI/UelKK--ADwI/AAAAAAAABmY/oMnVSDz8DUA/s1600/Two_sources_interference_3d.gif">
<div style="text-align:center">使用 Scilab 自行繪製的水波干涉動畫(3D版)</div>
<br />
## 2D 版動畫
以下的程式中,數學式子的部分主要是參考維基百科上的圖片 [[1](https://upload.wikimedia.org/wikipedia/commons/2/2c/Two_sources_interference.gif)],動畫的部分主要是參考 Python Matplotlib Tips 網站 [[2](https://pythonmatplotlibtips.blogspot.com/2018/11/animation-3d-surface-plot-funcanimation-matplotlib.html)],為了使 color bar 的高度與圖片相同,則參考了 StackOverflow 上的文章 [[3](https://stackoverflow.com/questions/18195758/set-matplotlib-colorbar-size-to-match-graph)]。在我一開始寫的版本當中,圖形的顏色一直沒有對應到Z軸的量值,後來改用 plt.cm.ScalarMappable 對應到陣列 np.arange(-cut, cut, 0.1) 才解決這個問題。[[4](https://stackoverflow.com/questions/56112328/configure-matplotlib-colorbar-to-match-3d-surface-values)]
<br />
```python=
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
import matplotlib.animation as animation
"""
1.參數設定
"""
L, sep, N, k, cut = 6, 2, 500, 5, 0.8 # 0.5倍寬度, 波源與原點距離, 每邊分割數量, 角波數, z軸範圍
fps, frn = 24, 50 # 每秒影格數量, 影格總數
x = np.linspace(-L, L, N) # x軸
y = np.linspace(-L, L, N) # y軸
X, Y = np.meshgrid(x, y) # 組成2維陣列
j = np.complex(0, 1) # 根號 -1
"""
2.設定計算振幅的函數, 計算每個位置的振幅並存入陣列
"""
# 自訂函式, 計算每個位置對應的振幅
def func(x, y, t):
r1 = np.sqrt((x-sep)**2 + y**2) # 點波源1
r2 = np.sqrt((x+sep)**2 + y**2) # 點波源2
z = np.exp(j*k*r1)/r1 + np.exp(j*k*r2)/r2 # 兩個點波源的 Green's function 總合
return np.real(z*np.exp(-j*t)) # 回傳實部
Z = np.zeros((N, N, frn)) # 儲存振幅用的2維陣列
T = np.linspace(0, 2*np.pi, frn) # 儲存時間用的1維陣列
# 計算每個時刻每個位置對應的振幅, 有加cut效果較佳
for i in range(frn):
Z[:, :, i] = func(X, Y, T[i]).clip(-cut, cut)
"""
3.繪圖
"""
fig = plt.figure(figsize=(7, 6), dpi=100) # 開啟繪圖視窗
ax = fig.gca()
ax.set_aspect(1.0) # 使圖片長寬變成1:1
# 以某個預設的colormap為基底, 修改成對應到 -cut ~ +cut 的colormap
mappable = plt.cm.ScalarMappable(cmap=plt.cm.jet)
mappable.set_array(np.arange(-cut, cut, 0.1))
# 在圖片右側加上color bar, 高度與圖片相同
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
plt.colorbar(mappable, cax=cax)
# 自訂函式, 先移除前一張圖, 再畫出下一張圖
def update(frame_number):
plot[0] = ax.contourf(X, Y, Z[:, :, frame_number], cmap=mappable.cmap, norm=mappable.norm)
# t = 0 的圖片
plot = [ax.contourf(X, Y, Z[:, :, 0], cmap=mappable.cmap, norm=mappable.norm)]
# 產生動畫, 目標為繪圖物件fig, 使用自訂函式update更新圖片, 圖片總數為frn, 時間間隔為interal, 單位為ms
ani = animation.FuncAnimation(fig, update, frn, interval=1000/fps)
plt.show() # 顯示圖片
ani.save('TwoSourcesInterference2D.gif', writer='imagemagick', fps=fps) # 儲存圖片
```
<br />
<img style="display: block; margin-left: auto; margin-right: auto" height="80%" width="80%" src="https://imgur.com/uJmokGl.gif">
<div style="text-align:center">使用 Matplotlib 自行繪製的水波干涉動畫(2D版)</div>
<br />
## 3D 版動畫
以下的程式與 2D 版本相當接近,但是要將繪圖物件改為 3D,並改用 plot_surface 繪圖。
<br />
```python=
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import LinearLocator, FormatStrFormatter
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.animation as animation
"""
1.參數設定
"""
L, sep, N, k, cut = 6, 2, 500, 5, 5 # 0.5倍寬度, 波源與原點距離, 每邊分割數量, 角波數, z軸範圍
fps, frn = 24, 50 # 每秒影格數量, 影格總數
x = np.linspace(-L, L, N) # x軸
y = np.linspace(-L, L, N) # y軸
X, Y = np.meshgrid(x, y) # 組成2維陣列
j = np.complex(0, 1) # 根號 -1
"""
2.設定計算振幅的函數, 計算每個位置的振幅並存入陣列
"""
# 自訂函式, 計算每個位置對應的振幅
def func(x, y, t):
r1 = np.sqrt((x-sep)**2 + y**2) # 點波源1
r2 = np.sqrt((x+sep)**2 + y**2) # 點波源2
z = np.exp(j*k*r1)/r1 + np.exp(j*k*r2)/r2 # 兩個點波源的 Green's function 總合
return np.real(z*np.exp(-j*t)) # 回傳實部
Z = np.zeros((N, N, frn)) # 儲存振幅用的2維陣列
T = np.linspace(0, 2*np.pi, frn) # 儲存時間用的1維陣列
# 計算每個時刻每個位置對應的振幅, 有加cut效果較佳
for i in range(frn):
Z[:, :, i] = func(X, Y, T[i]).clip(-cut, cut)
"""
3.繪圖
"""
fig = plt.figure(figsize=(8, 6), dpi=100) # 開啟繪圖視窗
ax = fig.gca(projection='3d') # 設定為 3D 繪圖
ax.set_zlim(-cut, cut) # 設定z軸範圍
ax.zaxis.set_major_locator(LinearLocator(10)) # 將z軸分為10格
ax.zaxis.set_major_formatter(FormatStrFormatter('%.1f')) # 設定z軸數值格式
# 以某個預設的colormap為基底, 修改成對應到 -cut ~ +cut 的colormap
mappable = plt.cm.ScalarMappable(cmap=plt.cm.jet)
mappable.set_array(np.arange(-cut, cut, 0.1))
# 自訂函式, 先移除前一張圖, 再畫出下一張圖
def update(frame_number):
plot[0].remove()
plot[0] = ax.plot_surface(X, Y, Z[:, :, frame_number], cmap=mappable.cmap,
norm=mappable.norm, linewidth=0.5, antialiased=False, alpha=0.7)
# t = 0 的圖片
plot = [ax.plot_surface(X, Y, Z[:, :, 0], cmap=mappable.cmap, norm=mappable.norm,
linewidth=0.5, antialiased=False, alpha=0.7)]
# 顯示數值及色階對應方式
fig.colorbar(mappable, shrink=0.5, aspect=5)
# 產生動畫, 目標為繪圖物件fig, 使用自訂函式update更新圖片, 圖片總數為frn, 繪圖資料為Z, 更新圖片plot的Z軸數值, 時間間隔為interal, 單位為ms
ani = animation.FuncAnimation(fig, update, frn, interval=1000/fps)
plt.show() # 顯示圖片
ani.save('TwoSourcesInterference3D.gif', writer='imagemagick', fps=fps) # 儲存圖片
```
<br />
<img style="display: block; margin-left: auto; margin-right: auto" height="80%" width="80%" src="https://imgur.com/eeWfbC2.gif">
<div style="text-align:center">使用 Matplotlib 自行繪製的水波干涉動畫(3D版)</div>
<br />
## 安裝 imagemagick 套件
為了將產生的圖片存成 gif 檔,需要事先安裝 imagemagick 套件,存檔之後可以再用這個套件將檔案縮小。
### Windows 版安裝方法
Windows 版的安裝方法很簡單,先到[下載頁面](https://imagemagick.org/script/download.php#windows)找到對應的版本,下載安裝檔之後執行檔案,一直按下一步即可。我安裝的版本是 ImageMagick-7.0.9-8-Q16-x64-dll.exe,可以[按此下載](ftp://ftp.imagemagick.org/pub/ImageMagick/binaries/ImageMagick-7.0.9-8-Q16-x64-dll.exe)。
### Linux 版安裝方法
以下的安裝方法主要是參考 TechMint 網站上的文章[[6](https://www.tecmint.com/install-imagemagick-on-debian-ubuntu/)],測試環境為 Linux Mint 19.1 Cinnamon,採用編譯安裝,先安裝編譯需要用的套件
```shell
$ sudo apt update
$ sudo apt-get install build-essential
```
下載原始碼並解壓縮
```shell
$ wget https://www.imagemagick.org/download/ImageMagick.tar.gz
$ tar xvzf ImageMagick.tar.gz
```
進到解壓縮後的資料夾中,檢查環境設定、編譯並安裝
```shell
$ cd ImageMagick-7.0.9-8/
$ ./configure
$ make
$ sudo make install
```
設定指令的動態連結
```shell
$ sudo ldconfig /usr/local/lib
```
確認安裝的版本
```shell
$ magick -version
```
<br />
### 縮小檔案
假設檔名為 TwoSourcesInterference3D.gif,可以使用以下的指令縮小檔案,縮小後的檔案命名為 TwoSourcesInterference3D_r.gif。
```shell
magick convert TwoSourcesInterference3D.gif -fuzz 5%% -layers Optimize TwoSourcesInterference3D_r.gif
```
<br />
## 參考資料
1. 維基百科兩個點波源水波干涉動畫 https://upload.wikimedia.org/wikipedia/commons/2/2c/Two_sources_interference.gif
2. Generate animation of 3D surface plot using plot_surface and animation.FuncAnimation in Python and matplotlib.pyplot https://pythonmatplotlibtips.blogspot.com/2018/11/animation-3d-surface-plot-funcanimation-matplotlib.html
3. StackOverflow Set Matplotlib colorbar size to match graph https://stackoverflow.com/questions/18195758/set-matplotlib-colorbar-size-to-match-graph
4. StackOverflow Configure matplotlib colorbar to match 3D surface values https://stackoverflow.com/questions/56112328/configure-matplotlib-colorbar-to-match-3d-surface-values
5. imagemagick https://imagemagick.org/index.php
6. How to Install ImageMagick 7 on Debian and Ubuntu https://www.tecmint.com/install-imagemagick-on-debian-ubuntu/
---
###### tags:`Python`、`Physics`