HOWTO · Matplotlib

Matplotlib 中如何為所有子圖建立一個圖例

在 Matplotlib 中,`fig.legend()` 可建立套用於整個 Figure 的圖例;若要為單一 Axes 建立圖例,則可使用 `ax.legend()`。當所有子圖的資料系列與標籤相同時,可重用其中一個 Axes 的圖例項目。

本頁內容

使用 fig.legend() 將一個圖例附加到整個 Matplotlib Figure。若每個子圖顯示相同且有標籤的資料系列,可重用其中一個 Axes 的 handles 和 labels。若子圖顯示不同的資料系列,則從每個 Axes 收集 handles 和 labels,並將兩個清單都傳給 Figure 圖例。這與 ax.legend() 不同;ax.legend() 會為單一 Axes 建立圖例。

以下範例使用 Matplotlib 建立圖形。第一個範例也會列印為共用圖例收集到的標籤。

子圖使用相同標籤時,建立共用的 Figure 層級圖例

如果每個子圖繪製的都是相同資料系列,且使用相同標籤,只從其中一個子圖取得圖例項目就足以建立共用圖例。以下範例使用兩組縮放方式不同的測量值,兩者都標示為 measurement。由於標籤與代表的資料意義相同,從第一個 Axes 取得 handles 即可。將圖例加到 fig 上,而不是對每個 Axes 呼叫 legend(),就能讓整個 Figure 只顯示一個圖例。

執行此範例:

本節中的程式碼以及顯示的標籤或輸出仍為英文。

import matplotlib

matplotlib.use("Agg")
import logging

# Ignore only Matplotlib's benign cold-start cache notice; other diagnostics
# remain visible on stderr.
class _FontCacheNoticeFilter(logging.Filter):
    def filter(self, record):
        return "Matplotlib is building the font cache; this may take a moment." not in record.getMessage()


logging.getLogger("matplotlib.font_manager").addFilter(_FontCacheNoticeFilter())
import matplotlib.pyplot as plt

fig, axes = plt.subplots(2, 1, figsize=(6, 4))
x = [0, 1, 2]

for ax, scale in zip(axes, [1, 2]):
    ax.plot(x, [scale * value for value in x], label="measurement")

handles, labels = axes[0].get_legend_handles_labels()
fig.legend(handles, labels, loc="upper center")
fig.tight_layout(rect=(0, 0, 1, 0.9))
print("Legend entries:", labels)
plt.show()
Legend entries: ['measurement']

get_legend_handles_labels() 會傳回兩個配對清單,包含該 Axes 上具有標籤的繪圖物件。共用標籤的方法簡潔,但只會呈現所選 Axes 上的資料系列。如果其他子圖有不同的標籤或資料系列,請使用下一節介紹的全 Axes 收集方法。

子圖內容不同時,從每個 Axes 收集 handles 和 labels

如果不同子圖包含不同且有標籤的線條,請在同一個迴圈中收集每個 Axes 的 handles 和 labels。這兩個清單會依位置保持配對:索引 i 的 handle 對應索引 i 的 label。下方程式碼會產生兩個圖例項目,每條線各一個。

執行此範例:

本節中的程式碼以及顯示的標籤或輸出仍為英文。

import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt

fig, axes = plt.subplots(1, 2, figsize=(7, 3))
axes[0].plot([0, 1, 2], [0, 1, 4], label="quadratic")
axes[1].plot([0, 1, 2], [0, 1, 2], label="linear")

handles, labels = [], []
for ax in axes:
    ax_handles, ax_labels = ax.get_legend_handles_labels()
    handles.extend(ax_handles)
    labels.extend(ax_labels)

fig.legend(handles, labels, loc="upper center", ncol=2)
fig.tight_layout(rect=(0, 0, 1, 0.88))
plt.show()

此方法會收集每個 Axes 上有標籤的繪圖物件,因此重複的標籤可能出現不只一次。如果重複項目代表相同概念,呼叫 fig.legend() 前,可為每個想保留的標籤選取一個 handle。相反地,若讀者需要區分不同資料系列,就保留各自的標籤。若只要顯示部分項目,傳入對應的 handles 和 labels 即可;圖例不必包含每一條繪製的線。

設定 Figure 層級圖例的位置與格式

loc 引數會決定圖例的哪個位置對齊錨點。例如,fig.legend(..., loc="upper center") 會將圖例置於 Figure 頂端中央。若要明確控制錨點,請使用 bbox_to_anchor;對 Figure 圖例而言,其座標是相對於 Figure 設定的。例如,fig.legend(handles, labels, loc="upper center", bbox_to_anchor=(0.5, 1.02)) 會將圖例放在頂端邊界稍上方。調整錨點數值即可移動圖例;也可以使用 fig.tight_layout(rect=(0, 0, 1, 0.9)) 或 fig.subplots_adjust(top=0.82) 預留空間,避免圖例遮住子圖內容。

圖例樣式可使用與其他 Matplotlib 圖例相同的常用選項。將 fontsize=9、frameon=False 或 facecolor="white" 等值傳給 fig.legend(),即可調整文字大小、邊框或背景。儲存圖形時,如果圖例位於 Figure 範圍之外,fig.savefig("plot.png", bbox_inches="tight") 可以將外部圖例一併納入儲存的圖片。

Figure 層級圖例常見問題

ax.legend() 和 fig.legend() 有什麼差別? ax.legend() 屬於特定 Axes。fig.legend() 則屬於 Figure,可合併來自一個或多個 Axes 的圖例項目。

所有子圖使用相同標籤時,如何建立一個圖例? 從一個具有代表性的 Axes 取得 handles 和 labels,再傳給 fig.legend()。前提是該 Axes 的標籤資料系列與其他子圖相同。

如何只加入指定的線條? 將想加入的 handles 和 labels 保存在配對清單中,再把清單傳給 fig.legend()。若有重複標籤,移除重複項目前,先確認這些項目是否代表相同資料系列。

如何避免圖例遮住圖表? 使用 loc 和 bbox_to_anchor 將圖例放在 Figure 邊緣的留白處,並在子圖版面配置中預留空間。如果圖例位於畫布之外,儲存時使用緊密邊界框,避免圖例遭到裁切。