使用Python製作基本動畫和交互式繪圖
有時,您想創建一個動態圖形,該圖形可以隨時間變化,例如視頻,或者根據交互式用戶輸入進行調整。這些可視化在真正顯示輸出如何隨輸入變化方面做了大量工作。在本文中,我將提供與靜態圖,動畫和交互式圖相同的數據。我要繪製的數據將來自固態物理學中使用最廣泛的方程式之一:費米-狄拉克分佈,它描述了固體中電子的佔有率。該方程如下所示,該方程將在能量E下佔據的狀態分數與費米能量和溫度的函數聯繫起來。
靜態圖
我們的第一個圖將是一個靜態圖,其中在不同溫度下將具有f(E)曲線。首先,我們導入所需的庫:
<code># Import packages %matplotlib inline import matplotlib as mpl import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation from matplotlib.widgets import Slider import numpy as np/<code>
由於我們將多次計算費米-狄拉克分佈,因此我們應該編寫一個函數為我們進行此計算:
<code># Fermi-Dirac Distribution def fermi(E: float, E_f: float, T: float) -> float: k_b = 8.617 * (10**-5) # eV/K return 1/(np.exp((E - E_f)/(k_b * T)) + 1)/<code>
現在我們可以開始繪製數據了!首先,我將編輯一些常規繪圖參數:
<code># General plot parameters mpl.rcParams['font.family'] = 'Avenir' mpl.rcParams['font.size'] = 18 mpl.rcParams['axes.linewidth'] = 2 mpl.rcParams['axes.spines.top'] = False mpl.rcParams['axes.spines.right'] = False mpl.rcParams['xtick.major.size'] = 10 mpl.rcParams['xtick.major.width'] = 2 mpl.rcParams['ytick.major.size'] = 10 mpl.rcParams['ytick.major.width'] = 2/<code>
我們創建圖並向其中添加axes對象:
<code># Create figure and add axes fig = plt.figure(figsize=(6, 4)) ax = fig.add_subplot(111)/<code>
為了將變化的溫度數據提供給我們的Fermi-Dirac函數,我們使用以下命令生成了一個介於100 K和1000 K之間的值的數組numpy.linspace:
<code># Temperature values T = np.linspace(100, 1000, 10)/<code>
對於每個溫度值,我們需要將不同的顏色映射到其結果曲線。我們將從coolwarm顏色版生成顏色,因為我們實際上是在處理溫度的變化,而且我們已經生成了上面的10個溫度值,所以我們將從coolwarm 顏色版中提取10種顏色。
<code># Get colors from coolwarm colormap colors = plt.get_cmap('coolwarm', 10)/<code>
繪製數據的最簡單方法是遍歷所有溫度值並每次繪製對應的曲線。為了生成x軸值,我們再次使用numpy.linspace創建一個數組,該數組包含一個介於0和1之間的100個均勻間隔的值。此外,我們對所有計算都使用0.5 eV的固定費米能量值。
<code># Plot F-D data for i in range(len(T)): x = np.linspace(0, 1, 100) y = fermi(x, 0.5, T[i]) ax.plot(x, y, color=colors(i), linewidth=2.5)/<code>
我們的繪圖需要的最後一個元素是一種區分不同色溫曲線的方法。為此,我們將通過首先創建標籤列表,然後將其傳遞給axes.legend方法來創建圖例。
<code># Add legend labels = ['100 K', '200 K', '300 K', '400 K', '500 K', '600 K', '700 K', '800 K', '900 K', '1000 K'] ax.legend(labels, bbox_to_anchor=(1.05, -0.1), loc='lower left', frameon=False, labelspacing=0.2)/<code>
labelspacing—圖例條目之間的垂直間距(默認為0.5)
最後,在添加軸標籤之後,我們將看到以下圖:
生成動圖
現在說我們想提供與上述相同的數據,但作為視頻呈現—我們將如何做?事實證明,我們可以很容易地做到這一點matplotlib!我們必須導入以下內容以使我們可以使用此功能:
<code>from matplotlib.animation import FuncAnimation/<code>
如果我們使用的是Jupyter Notebook,我們還應該更改matplotlib用於渲染其圖形的後端,以便進行交互式繪圖。
<code># Change matplotlib backend %matplotlib notebook/<code>
對於我們的動畫,我們需要執行以下操作:
(1)將對繪製曲線的參考存儲為變量
(2)使用帶有此變量的函數調用來不斷更新繪圖數據
我們將首先繪製空數組並將其存儲為名為f_d的變量。另外,我們將添加一個文本註釋以顯示當前圖顯示的溫度,因此我們還將存儲對此的變量引用。們將文本註釋的右上角對齊到axis對象的右上角。
<code># Create variable reference to plot f_d, = ax.plot([], [], linewidth=2.5) # Add text annotation and create variable reference temp = ax.text(1, 1, '', ha='right', va='top', fontsize=24)/<code>
現在,我們的動畫的主力—動畫函數,該函數將獲取索引的輸入,i並在每次調用時更新圖。它還將使用當前溫度更新文本註釋,並且基於顏色圖中的相同顏色來更改繪圖和文本的coolwarm顏色。
<code># Animation function def animate(i): x = np.linspace(0, 1, 100) y = fermi(x, 0.5, T[i]) f_d.set_data(x, y) f_d.set_color(colors(i)) temp.set_text(str(int(T[i])) + ' K') temp.set_color(colors(i))/<code>
set_data(x, y)—為繪圖設置新的x和y數據。
我們使用以下代碼行:
<code># Create animation ani = FuncAnimation(fig, animate, frames=range(len(T)), interval=500, repeat=True)/<code>
fig—將圖形傳遞給動畫函數
func —繪圖的動畫函數
frames—一個從0開始的數組,代表動畫的幀。在這種情況下,我們傳遞的長度等於要設置動畫的溫度的數量(這也是傳遞給func的索引i))。
interval —幀之間的延遲(以毫秒為單位)
repeat—是否在結束時重複播放動畫
現在,如果您的軸標籤或部分繪圖被切除,則可以嘗試添加以下代碼行,以確保所有元素都在圖中。
<code># Ensure the entire plot is visible fig.tight_layout()/<code>
現在,要保存動畫,我們使用以下內容:
<code># Save and show animation ani.save('AnimatedPlot.gif', writer='ImageMagick', fps=2)/<code>
writer—動畫編寫器程序—生成.gif文件,我使用ImageMagick
fps —動畫的每秒幀數(由於我們只有10幀,因此我使用fps值為2來模擬500 ms之前的間隔延遲)
互動圖
最後,如果我們想讓用戶使用輸入參數來觀察它們在輸出上的變化,我們可以製作一個交互式圖。我們首先導入所需的庫。
<code>from matplotlib.widgets import Slider/<code>
我們再次從創建圖形和軸對象開始以保存繪圖。但是,這一次,我們調整圖的大小以為要添加的滑塊騰出空間。
<code># Create main axis ax = fig.add_subplot(111) fig.subplots_adjust(bottom=0.2, top=0.75)/<code>
figure.subplots_adjust()接受頂部、底部、左側和右側的輸入,以指示在何處繪製軸邊框的四個角。在本例中,我們要確保頂部不超過0.75,這樣才能將滑塊放置在繪圖區的頂部。
滑塊就像其他任何軸對象一樣開始。在這裡,我們把兩個都加到圖上(一個用來改變費米能量,一個用來改變溫度)。此外,由於我們更改了全局圖設置以刪除右側和頂部,所以我們將把它們添加回這裡作為滑塊。
<code># Create axes for sliders ax_Ef = fig.add_axes([0.3, 0.85, 0.4, 0.05]) ax_Ef.spines['top'].set_visible(True) ax_Ef.spines['right'].set_visible(True) ax_T = fig.add_axes([0.3, 0.92, 0.4, 0.05]) ax_T.spines['top'].set_visible(True) ax_T.spines['right'].set_visible(True)/<code>
現在,我們必須將這些軸對象變成滑塊:
<code># Create sliders s_Ef = Slider(ax=ax_Ef, label='Fermi Energy ', valmin=0, valmax=1.0, valinit=0.5, valfmt=' %1.1f eV', facecolor='#cc7000') s_T = Slider(ax=ax_T, label='Temperature ', valmin=100, valmax=1000, valinit=300, valfmt=' %i K', facecolor='#cc7000')/<code>
ax —將軸對象轉換為滑塊
label —滑塊左側的滑塊標籤
valmin —滑塊的最小值
valmax —滑塊的最大值
valfmt—要顯示為滑塊值的字符串,位於右側。%1.1f是具有1個小數點的浮點數,並且%i是整數
facecolor —填充滑塊的顏色
現在,我們已經創建了滑塊,讓我們繪製“默認”數據集,該數據集將在首次加載圖形時顯示(0.5 eV的費米能量和300 K的溫度):
<code># Plot default data x = np.linspace(-0, 1, 100) Ef_0 = 0.5 T_0 = 300 y = fermi(x, Ef_0, T_0) f_d, = ax.plot(x, y, linewidth=2.5)/<code>
就像在動圖中一樣,我們現在將定義update函數,該函數將在更新滑塊時更改數據。此update函數獲取滑塊的當前值,更改繪圖中的數據,然後重新繪製圖形。
<code># Update values def update(val): Ef = s_Ef.val T = s_T.val f_d.set_data(x, fermi(x, Ef, T)) fig.canvas.draw_idle() s_Ef.on_changed(update) s_T.on_changed(update)/<code>
Slider.on_changed(func)func更改滑塊值時調用更新。
結論
希望本文能夠展示如何利用動圖和交互式滑塊使數據可視化動態化。