matplotlibの凡例（legend）レイアウト関連メモ

# 凡例の表示、曲線の選択

`ax.legend()`にオプションを何も含めない場合。

```py:
import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(-3, 3, 101)
y1 = np.sin(x * np.pi)
y2 = np.cos(x * 2 * np.pi) * 0.5

fig = plt.figure()
ax = fig.add_subplot(111)

# labelオプションで凡例に用いる曲線名を指定
ax.plot(x, y1, c="r", label="$\mathrm{sin}(x)$")
ax.plot(x, y2, c="b", label="$0.5 \mathrm{cos}(2x)$")
ax.grid(axis='both')

# 凡例の表示
ax.legend()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187118/9195589f-d3b3-374e-48f3-166602ed7cd2.png)

上と全く同じ図を、`ax.get_legend_handles_labels()`で獲得した曲線情報、ラベル情報を用いて描画すると下記のようになる。

```py:
ax.plot(x, y1, c="r", label="$\mathrm{sin}(x)$")
ax.plot(x, y2, c="b", label="$0.5 \mathrm{cos}(2x)$")
ax.grid(axis='both')

# handsにはlabelが指定された曲線オブジェクトのリスト、
# labsには対応するlabelのリストが入る
hans, labs = ax.get_legend_handles_labels()

# 凡例の表示
ax.legend(handles=hans, labels=labs)
```

`ax.get_legend_handles_labels()`で獲得した曲線情報、ラベル情報は下記のようにも指定できる。
`handles`の中で、`ax.plot()`によるl1やl2はリスト型なので、+演算子で結合する。`ax.scatter()`だとリスト型ではないので、`[s1, s2]`のように結合する。

```py:
l1 = ax.plot(x, y1, c="r")
l2 = ax.plot(x, y2, c="b")
ax.grid(axis='both')

# 凡例の表示
# 今回の場合、handlesは省略可。
ax.legend(handles=l1+l2, labels=["$\mathrm{sin}(x)$", "$0.5 \mathrm{cos}(2x)$"])
```

例えば、凡例の順序を逆転させたい場合は下記のようにする。

```py:
x = np.linspace(-3,3,101)

fig = plt.figure(figsize=(10,5))
# 左図
ax1 = fig.add_subplot(121)
# 右図
ax2 = fig.add_subplot(122)
for i in np.linspace(-1,1,5):
    ax1.plot(x, np.sin(x)+i, label=str(i))
    ax2.plot(x, np.sin(x)+i, label=str(i))

# 凡例情報の取得
hans, labs = ax1.get_legend_handles_labels()
# 左図凡例の表示
ax1.legend(handles=hans, labels=labs, fontsize=8)
# 右図凡例の表示（順序反転）
ax2.legend(handles=hans[::-1], labels=labs[::-1], fontsize=8)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187118/8ef6050d-9be4-0a7e-a93c-1957e4591463.png)

# 凡例のレイアウト

## locオプション

1から9までの数字に対してaxis内の位置が定義される。該当する文字列でも指定可能（"upper right", "upper left"など）。

```py:
fig = plt.figure(figsize=(10,10))
for i in range(1,9+1):
    ax = fig.add_subplot(330+i)

    # 描画に必要なリストを空にしたとしても、
    # 曲線のスタイルは定義され、凡例にも反映される。
    ax.plot([], [], c="r", label="sin")
    ax.plot([], [], c="b", label="cos")
    # 緑文字の追加（axisの中央に配置）
    ax.text(0.5, 0.5, "loc={}".format(i),
            c="g", fontsize=30,
            horizontalalignment="center",
            verticalalignment="center", )
    # locを指定した凡例の描画
    ax.legend(loc=i)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187118/d329e4c0-0c77-093a-6c13-3de3397e1113.png)


## axis外への描画（`bbox_to_anchor`）、列数の変更（`ncol`）

わかりやすいように、`bbox_to_anchor`で指定した座標に緑色のマーカーを追加した。

```py:
fig = plt.figure(figsize=(10,10))

# 左上
ax = fig.add_subplot(321)
ax.plot([], [], c="r", label="sin")
ax.plot([], [], c="b", label="cos")
# axisの上部に凡例を描画
# 凡例ウィンドウの中央下に基準点
# 基準点の座標は、水平方向はaxis中央、上下方向はaxisの上辺より少し上
# ncolで2列表示を適用
ax.legend(loc="lower center", bbox_to_anchor=(0.5, 1.02,), borderaxespad=0, ncol=2)
# bbox_to_anchorの位置に緑色のマーカーの追加
# clip_on=Falseでaxis外への描画を許容
# transform=ax.transAxesで、axisの左下右上座標が(0,0)(1,1)になる系を適用。
circle = plt.Circle((0.5, 1.02,), 0.01, color='g', clip_on=False, transform=ax.transAxes)
ax.add_artist(circle)

# 右上
ax = fig.add_subplot(322)
ax.plot([], [], c="r", label="sin")
ax.plot([], [], c="b", label="cos")
# axisの右上に凡例を描画
# 凡例ウィンドウの左上に基準点
# 基準点の座標は、水平方向はaxisの右辺より少し右、上下方向はaxisの上辺
ax.legend(loc="upper left", bbox_to_anchor=(1.02, 1.0,), borderaxespad=0)
circle = plt.Circle((1.02, 1.0,), 0.01, color='g', clip_on=False, transform=ax.transAxes)
ax.add_artist(circle)

# 右中
ax = fig.add_subplot(324)
ax.plot([], [], c="r", label="sin")
ax.plot([], [], c="b", label="cos")
# axisの右中に凡例を描画
# 凡例ウィンドウの左中に基準点
# 基準点の座標は、水平方向はaxisの右辺より少し右、上下方向はaxisの中央
ax.legend(loc="center left", bbox_to_anchor=(1.02, 0.5,), borderaxespad=0)
circle = plt.Circle((1.02, 0.5,), 0.01, color='g', clip_on=False, transform=ax.transAxes)
ax.add_artist(circle)

# 左下
ax = fig.add_subplot(325)
ax.plot([], [], c="r", label="sin")
ax.plot([], [], c="b", label="cos")
# axisの下部に凡例を描画
# 凡例ウィンドウの中央上に基準点
# 基準点の座標は、水平方向はaxisの中央、上下方向はaxisの下辺より多めに下
ax.legend(loc="upper center", bbox_to_anchor=(0.5, -0.1,), borderaxespad=0, ncol=2)
circle = plt.Circle((0.5, -0.1,), 0.01, color='g', clip_on=False, transform=ax.transAxes)
ax.add_artist(circle)

# 右下
ax = fig.add_subplot(326)
ax.plot([], [], c="r", label="sin")
ax.plot([], [], c="b", label="cos")
# axisの右下に凡例を描画
# 凡例ウィンドウの左下に基準点
# 基準点の座標は、水平方向はaxisの右辺より少し右、上下方向はaxisの下辺
# ncolで2列表示を適用
ax.legend(loc="lower left", bbox_to_anchor=(1.02, 0.0,), borderaxespad=0)
circle = plt.Circle((1.02, 0.0,), 0.01, color='g', clip_on=False, transform=ax.transAxes)
ax.add_artist(circle)
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187118/e4a8c570-e609-7ad2-f423-65063ceebcfd.png)

上のコードで`borderaxespad=0`としていたのを`borderaxespad=1`とすると下の図のようになる。この数字は、font-sizeで指定されたポイント数を1とした相対値で定義される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187118/a321c882-07a8-a335-b7fd-ba71bea6a565.png)

# 2軸グラフの凡例

上記2つを組み合わせる。

```py:
x = np.linspace(-3,3,101)
y1 = np.sin(x * np.pi)
y2 = np.cos(x * 2 * np.pi) * 0.5
z  = np.tan(x * np.pi)

fig = plt.figure(figsize=(10,5))

# 左図
# それぞれの軸の凡例を独立して描画
ax = fig.add_subplot(131)
ax.plot(x, y1, c="r", label="$\mathrm{sin}(x)$")
ax.plot(x, y2, c="b", label="$0.5 \mathrm{cos}(2x)$")
ax.grid(axis='both')
# 第1軸のグラフについて凡例の表示
ax.legend(loc="upper left", bbox_to_anchor=(1.2, 1.0))
# 第2軸
ax2 = ax.twinx()
ax2.plot(x, z, "g", marker="o", lw=0, ms=2, label = '$\mathrm{tan}(x)$')
ax2.set_ylim(-3,3)
# 第2軸のグラフについて凡例の表示
ax2.legend(loc="lower left", bbox_to_anchor=(1.2, 0.0))

# 右図
# 2つの軸の凡例を統合する
ax = fig.add_subplot(133)
ax.plot(x, y1, c="r", label="$\mathrm{sin}(x)$")
ax.plot(x, y2, c="b", label="$0.5 \mathrm{cos}(2x)$")
ax.grid(axis='both')
# 第1軸の凡例情報保持
hans1, labs1 = ax.get_legend_handles_labels()
# 第2軸
ax2 = ax.twinx()
ax2.plot(x, z, "g", marker="o", lw=0, ms=2, label = '$\mathrm{tan}(x)$')
ax2.set_ylim(-3,3)
# 第2軸の凡例情報保持
hans2, labs2 = ax2.get_legend_handles_labels()
# 凡例の表示
ax.legend(hans1+hans2, labs1+labs2, loc="upper left", bbox_to_anchor=(1.2, 1.0))
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/187118/9499ab57-5c2e-121a-c7b9-c55116b65094.png)