matplotlibで描画する関数を自分で定義する際、すでになにかがプロットされている場合は新しいAxesにプロットし、Axesは存在するけれど何もプロットされていない場合はそのAxesにプロットする、のような関数を定義したかったのだが、少しハマったのでメモ。

最終的に最善と思われる関数を定義した。


実装

# モジュールのインポート
import sys

import matplotlib
import matplotlib.patches

# Pythonのバージョンを表示
print(sys.version)

3.11.6 | packaged by conda-forge | (main, Oct  3 2023, 10:37:07) [Clang 15.0.7 ]
# matplotlibのバージョンを表示
matplotlib.__version__

'3.8.0'

is_plotted関数

matplotlibでCurrent Axesに何かプロットされているかを判定する関数を定義した。

なにかプロットされているときはTrue、なにもプロットされていないときはFalseが返る (はず)。

from typing import Optional
import matplotlib.pyplot as plt
import matplotlib.axes


def is_plotted(ax: Optional[matplotlib.axes.Axes] = None) -> bool:
    """Check if an axes has been plotted on.

    Parameters
    ----------
    ax : matplotlib.axes.Axes | None
        The axes to check. If None, the current axes will be used.
        optional, by default None

    Returns
    -------
    bool
        True if the axes has been plotted on, False otherwise.
    """
    ax = plt.gca() if ax is None else ax
    return any(
        len(getattr(ax, _key))
        for _key in dir(ax)
        if isinstance(getattr(ax, _key), matplotlib.axes.Axes.ArtistList)
    )

仕様確認

何もプロットしてないとき

is_plotted()

False

png

fig, ax = plt.subplots(facecolor="w")
is_plotted(ax)

False

png

scatter

ax.scatter([1, 2, 3], [1, 2, 3])
ax.scatter([3, 2, 1], [2, 1, 3])
display(fig)
is_plotted(ax)

png

True
# プロットを消去
plt.close()

plot

plt.plot([1, 2, 3], [1, 2, 3])
is_plotted()

True

png

text系

fig, ax = plt.subplots(facecolor="w")
ax.annotate("test", xy=(0.5, 0.5))
is_plotted(ax)

True

png

# プロットを消去
plt.close()

fig, ax = plt.subplots(facecolor="w")
ax.text(0.5, 0.5, "test")
is_plotted(ax)

True

png

# プロットを消去
plt.close()

図形

fig, ax = plt.subplots(facecolor="w")
ax.add_artist(matplotlib.patches.Circle((0.5, 0.5), 0.5))
is_plotted()

True

png

# プロットを消去
plt.close()

画像

# fmt: off
img = [
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,11,130,166,209,253,84,18,18,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,8,171,252,253,252,166,21,106,199,21,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,171,252,252,223,91,2,0,9,200,202,11,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,179,252,226,35,0,0,0,0,148,252,21,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,4,42,24,0,0,0,0,0,192,252,21,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,124,255,204,9,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,57,225,253,89,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,31,162,232,246,252,253,202,30,7,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,71,218,252,252,252,155,102,189,247,170,37,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,62,253,252,199,77,7,0,0,47,217,235,45,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,18,106,9,0,0,0,0,0,27,167,237,55,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,27,218,196,7,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,96,252,29,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43,252,126,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,43,252,126,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,61,253,127,0,0,0,0,0],
    [0,0,0,0,0,0,0,8,28,0,0,0,0,0,0,0,0,0,0,36,183,252,91,0,0,0,0,0],
    [0,0,0,0,0,0,0,215,142,13,0,0,0,0,0,0,0,20,92,179,253,201,11,0,0,0,0,0],
    [0,0,0,0,0,0,0,135,252,217,152,64,64,64,64,126,169,246,252,252,199,21,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,4,113,147,209,252,252,252,252,253,252,155,147,59,18,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],
]
# fmt: on

fig, ax = plt.subplots(facecolor="w")
ax.imshow(img, cmap="gray")
ax.axis("off")
is_plotted(ax)

True

png

なぜ判定できるのか

matplotlib.axes.Axes.ArtisListオブジェクトは、Axesに紐づくArtistのリストで、ここに描画されたものが保存されているため。

ArtistListAxesオブジェクトのAttributeの中から全て取り出して、1つでも何かArtistが保存されていれば何かが描画されていると判定している。

最初に提示したis_plotted関数は内包表記で書いているが、これを書き下すと以下のように書ける。

from typing import Optional
import matplotlib.pyplot as plt
import matplotlib.axes


def is_plotted(ax: Optional[matplotlib.axes.Axes] = None) -> bool:
    """Check if an axes has been plotted on.

    Parameters
    ----------
    ax : matplotlib.axes.Axes | None
        The axes to check. If None, the current axes will be used.
        optional, by default None

    Returns
    -------
    bool
        True if the axes has been plotted on, False otherwise.
    """
    ax = plt.gca() if ax is None else ax
    # ax: matplotlib.axes.Axesオブジェクトの属性の名称を列挙
    for _key in dir(ax):
        # axオブジェクトの属性の値を取得
        _attribute = getattr(ax, _key)
        # axオブジェクトの属性の値がmatplotlib.axes.Axes.ArtistList型の場合
        if isinstance(_attribute, matplotlib.axes.Axes.ArtistList):
            # axオブジェクトの属性の値が空でない場合
            if len(_attribute) > 0:
                # なにか入っているのでTrueを返す
                return True
    # for文が最後まで実行された場合
    # = すべてのArtistListが空の場合
    else:
        # 何も描画されていないのでFalseを返す
        return False

コメント

もっと適切な関数などありましたらぜひ教えてください。

どうでもいいけれど、blackで整形してほしくないところを無視するマジックコマンド (# fmt: off# fmt: onで囲う) の存在を初めて知った。

flake8でいうところの# noqaのような存在で、重宝しそう(厳密にはそれらは使わない方が理想なのだろうが。。。)。