<style> .markdown-body table{ display: unset; } </style> # Matplotlib 繪圖技巧:繪製選擇題用的函數圖形選項 > 作者:王一哲 > 日期:2019/11/9 ## 前言 有時候選擇題會以五張不同的函數圖形作為選項,之前我是用 [LibreOffice Draw](https://zh-tw.libreoffice.org/discover/draw/) 的繪圖工具畫好圖形之後再加上選項標籤,最後將五張函數圖形以及選項標籤匯出成一張圖片。下圖是我用 LibreOffice Draw 繪製的選項,題目是要選出自由落下的小球觸地時發生彈性碰撞的*v-t*圖,但是選項中的曲線畢竟是手動繪製的,只能算是示意圖。如果對於圖形的精準度要求不高,這樣的作法應該可以符合需求,但如果需要畫出精準的函數圖形,LibreOffice Draw 就不太適合了。 <img height="100%" width="100%" src="https://imgur.com/qOAEM3D.png" style="display: block; margin-left: auto; margin-right: auto;"/> <div style="text-align:center">使用 LibreOffice Draw 繪製的函數圖形選項</div> <br /> 於是我試著改用 NumPy 計算函數值,再用 Matplotlib 中的 subplots 將圖形及標籤一口氣畫好,以下是實際出題的例子。 <br /> ## 使用 NumPy + Matplotlib 繪製都卜勒效應題目的選項 ### 試題 假設聲源發出的聲波頻率為 1000 Hz,空氣中的聲速為 340 m/s,下表中是兩種不同狀況下觀察者觀測到的聲波頻率,單位為 Hz。狀況 I:聲源移動速度為 *v* 接近靜止的觀察者;狀況 II:觀察者移動速度為 *v* 接近靜止的聲源。下列的 5 張圖形中,請問何者最符合表格中數據? <div style="text-align:center"> | *v* (m/s) | 0 | 5 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | | ------- | - | - | -- | -- | -- | -- | -- | -- | -- | | 狀況 I 測到的聲波頻率 (Hz) | 1000.0 | 1014.9 | 1030.3 | 1046.2 | 1062.5 | 1079.4 | 1096.8 | 1114.8 | 1133.3 | | 狀況 II 測到的聲波頻率 (Hz) | 1000.0 | 1014.7 | 1029.4 | 1044.1 | 1058.8 | 1073.5 | 1088.2 | 1102.9 | 1117.6 | </div> <br /> <img height="100%" width="100%" src="https://imgur.com/h74d6QI.png" style="display: block; margin-left: auto; margin-right: auto;"/> <br /> ### 圖形對應的函數 假設波源移動時觀測到的聲波頻率為$f_s$,觀察者移動時觀測到的聲波頻率為$f_o$,空氣中的聲速為$v_0$,波源發出的聲波頻率為$f_0$,五張圖形對應的函數如下: $$f_{s,1} = \frac{v_0}{v_0 - v} f_0 ~~~~~~~~~~ f_{o,1} = \frac{v_0 + v}{v_0} f_0$$ $$f_{s,2} = \frac{v_0 + v}{v_0} f_0 ~~~~~~~~~~ f_{o,2} = \frac{v_0}{v_0 - v} f_0$$ $$f_{s,3} = \frac{v_0 + v}{v_0 - v} f_0 ~~~~~~~~~~ f_{o,3} = \frac{v_0 + v}{v_0} f_0$$ $$f_{s,4} = \frac{v_0}{v_0 + v} f_0 ~~~~~~~~~~ f_{o,4} = \frac{v_0 - v}{v_0} f_0$$ $$f_{s,5} = \left( \frac{v_0}{v_0 - v} \right)^2 f_0 ~~~~~~~~~~ f_{o,5} = \left( \frac{v_0 + v}{v_0} \right)^2 f_0$$ <br /> ### 程式碼 ```python= import matplotlib as mpl import matplotlib.pyplot as plt import numpy as np plt.rc('font', **{'family' : 'sans-serif'}) plt.rc('legend', fontsize=16) f0, v0 = 1000, 340 # 原來的聲波頻率, 空氣中的聲速 # 產生陣列 v, fs, fo 共 5 組 vmin, vmax, num = 0, 40, 100 v = np.linspace(vmin, vmax, num) fs1 = v0 / (v0 - v) * f0 # 正確選項 fo1 = (v0 + v) / v0 * f0 fs2 = (v0 + v) / v0 * f0 # 錯誤選項 fo2 = v0 / (v0 - v) * f0 fs3 = (v0 + v) / (v0 - v)* f0 # 錯誤選項 fo3 = (v0 + v) / v0 * f0 fs4 = v0 / (v0 + v) * f0 # 錯誤選項 fo4 = (v0 - v) / v0 * f0 fs5 = (v0 / (v0 - v))**2 * f0 # 錯誤選項 fo5 = ((v0 + v) / v0)**2 * f0 # 用 plt.subplots fig, ((ax1, ax2, ax3), (ax4, ax5, ax6)) = plt.subplots(2, 3, figsize=(12, 8), dpi=72) fig.subplots_adjust(left=0.1, bottom=0.1, right=0.9, top=0.9, wspace=0.4, hspace=0.3) # 繪圖並設定線條顏色、寬度、圖例 line1_1, = ax1.plot(v, fs1, color='black', linestyle='-', linewidth=3, label='I') line1_2, = ax1.plot(v, fo1, color='black', linestyle='--', linewidth=3, label='II') ax1.legend(handles = [line1_1, line1_2], loc='upper left') ax1.set_xlabel(r'$v~\mathrm{(m/s)}$', fontsize=16) ax1.set_ylabel(r'$f~\mathrm{(Hz)}$', fontsize=16) ax1.tick_params(axis='both', labelsize=14) ax1.set_title('(A)', loc='left', fontsize=20) line2_1, = ax2.plot(v, fs2, color='black', linestyle='-', linewidth=3, label='I') line2_2, = ax2.plot(v, fo2, color='black', linestyle='--', linewidth=3, label='II') ax2.legend(handles = [line2_1, line2_2], loc='upper left') ax2.set_xlabel(r'$v~\mathrm{(m/s)}$', fontsize=16) ax2.set_ylabel(r'$f~\mathrm{(Hz)}$', fontsize=16) ax2.tick_params(axis='both', labelsize=14) ax2.set_title('(B)', loc='left', fontsize=20) line3_1, = ax3.plot(v, fs3, color='black', linestyle='-', linewidth=3, label='I') line3_2, = ax3.plot(v, fo3, color='black', linestyle='--', linewidth=3, label='II') ax3.legend(handles = [line3_1, line3_2], loc='upper left') ax3.set_xlabel(r'$v~\mathrm{(m/s)}$', fontsize=16) ax3.set_ylabel(r'$f~\mathrm{(Hz)}$', fontsize=16) ax3.tick_params(axis='both', labelsize=14) ax3.set_title('(C)', loc='left', fontsize=20) line4_1, = ax4.plot(v, fs4, color='black', linestyle='-', linewidth=3, label='I') line4_2, = ax4.plot(v, fo4, color='black', linestyle='--', linewidth=3, label='II') ax4.legend(handles = [line4_1, line4_2], loc='upper right') ax4.set_xlabel(r'$v~\mathrm{(m/s)}$', fontsize=16) ax4.set_ylabel(r'$f~\mathrm{(Hz)}$', fontsize=16) ax4.tick_params(axis='both', labelsize=14) ax4.set_title('(D)', loc='left', fontsize=20) line5_1, = ax5.plot(v, fs5, color='black', linestyle='-', linewidth=3, label='I') line5_2, = ax5.plot(v, fo5, color='black', linestyle='--', linewidth=3, label='II') ax5.legend(handles = [line5_1, line5_2], loc='upper left') ax5.set_xlabel(r'$v~\mathrm{(m/s)}$', fontsize=16) ax5.set_ylabel(r'$f~\mathrm{(Hz)}$', fontsize=16) ax5.tick_params(axis='both', labelsize=14) ax5.set_title('(E)', loc='left', fontsize=20) ax6.axis('off') # 隱藏右下角小圖 fig.savefig('DopplerEffectPlot.svg') fig.savefig('DopplerEffectPlot.png') fig.show() ``` <br /> 整個程式可以分為兩大部分: 1. 第 8 - 21 行:定義圖形對應的函數並產生資料。 2. 第 24 - 72 行:繪製圖形並儲存成圖檔,雖然看起來行數很多,但其實只是同樣的事做了5次,由於我是用 numpy.subplots 指令產生 2 列、3 欄共 6 張小圖,但是只需要 5 張圖作為選項,因此最後用 ax6.axis('off') 隱藏右下角的小圖。 <br /> ## 結語 雖然這樣的作法看起來很麻煩,但是以後如果要出其它的題目,只要修改函數以及 x 軸範圍再執行一次即可,畫出來的函數圖形絕對正確,而且圖形的風格能夠保持一致,以後出題會更方便。 <br /> ## 參考資料 StackOverFlow https://stackoverflow.com/questions/10035446/how-can-i-make-a-blank-subplot-in-matplotlib --- ###### tags:`Python`