matplotlib(PyPlot)でグラフのラベルに画像を使いたい

Julia

はじめに

PythonやJuliaでグラフの作図をしているときに、凡例に画像を使いたいときがでてきた。
一方で、ネットには以外にこれらの記載がなくLLMに頼っても回答がでてこなかった。そこで頑張ってプログラムを書いたので今後も使っていけるように備忘録がてら記事に残しておく。(最後のコードの整形はclaude監修です)

また、matplotlibについては過去いくつか記事を残しているので参考にしてほしい。

卒論グラフをPythonで作る#Matplotlib

matplotlibの小技備忘録

Matplotlibの色一覧とコピー用ボタン

イメージ的にこんな感じのグラフになります↓

コード

基本的に自分の場合はPythonでもJuliaでもmatplotlib(JuliaではPyPlot)を利用している。Pythonではseabornはちょくちょく使うが他の可視化ライブラリは結局使わなくなったし、JuliaでもPythonの最大エコシステムに乗ることができるmatplotlibが一番使いやすいという結論だ。

基本的にPyPlot環境はmatplotlib.pyplotならしい(以下,PyPlotから引用)のでJuliaでmatplotlibライブラリを使いたい場合はPythonを呼び出して関数を定義してあげる必要がある。

ここでPython関数内でmatplotlibの設定を変えた後はJuliaのPyPlotからいつも通り描画すれば問題ないということがわかった。

This module provides a Julia interface to the Matplotlib plotting library from Python, and specifically to the matplotlib.pyplot module

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
from matplotlib.legend_handler import HandlerLine2D
import matplotlib.image as mpimg

class HandlerLineImage(HandlerLine2D):
    def __init__(self, image_path, img_size=20, spacing=10):
        super().__init__()
        self.image_path = image_path
        self.img_size = img_size
        self.spacing = spacing
    
    def create_artists(self, legend, orig_handle, xdescent, ydescent,
                       width, height, fontsize, trans):
        # 通常の線のlegendを作成
        line_artists = super().create_artists(legend, orig_handle, xdescent, 
                                             ydescent, width, height, fontsize, trans)
        
        # 画像を読み込む
        img = mpimg.imread(self.image_path)
        
        # OffsetImageを作成
        imagebox = OffsetImage(img, zoom=self.img_size/100)
        imagebox.image.axes = legend.axes
        
        # テキストの右側に画像を配置
        # widthはlegend内のテキスト幅、spacingで画像の位置を調整
        ab = AnnotationBbox(imagebox, 
                           (width + self.spacing + self.img_size/2, ydescent + height/2),
                           frameon=False, xycoords=trans, boxcoords="offset points")
        
        # 線と画像の両方を返す
        return line_artists + [ab]

# サンプルプロット
fig, ax = plt.subplots(figsize=(10, 6))

# データをプロット
x = np.linspace(0, 10, 100)
line1, = ax.plot(x, 4*x + 10, label='niwatori1')
line2, = ax.plot(x, 3*x + 10, label='niwatori2')

# 画像付きlegendを追加
img_size = 10
space = 20
fontsize = 12
handler_map = {
    line1: HandlerLineImage('niwatori1.png', img_size=img_size, spacing=space),
    line2: HandlerLineImage('niwatori2.png', img_size=img_size, spacing=space)
}

ax.legend(handler_map=handler_map, handlelength = 2, loc= "upper center", frameon=False, fontsize=fontsize)
plt.show()
using PyCall
using PyPlot
plt = PyPlot

# Pythonのクラスを定義
py"""
from matplotlib.legend_handler import HandlerLine2D
from matplotlib.offsetbox import OffsetImage, AnnotationBbox
import matplotlib.image as mpimg

class HandlerLineImage(HandlerLine2D):
    def __init__(self, image_path, img_size=20, spacing=10):
        super().__init__()
        self.image_path = image_path
        self.img_size = img_size
        self.spacing = spacing
    
    def create_artists(self, legend, orig_handle, xdescent, ydescent,
                       width, height, fontsize, trans):
        # 通常の線のlegendを作成
        line_artists = super().create_artists(legend, orig_handle, xdescent, 
                                             ydescent, width, height, fontsize, trans)
        
        # 画像を読み込む
        img = mpimg.imread(self.image_path)
        
        # OffsetImageを作成
        imagebox = OffsetImage(img, zoom=self.img_size/100)
        imagebox.image.axes = legend.axes
        
        # テキストの右側に画像を配置
        ab = AnnotationBbox(imagebox, 
                           (width + self.spacing + self.img_size/2, ydescent + 2),
                           frameon=False, xycoords=trans, boxcoords="offset points")
        
        # 線と画像の両方を返す
        return line_artists + [ab]
"""

# Pythonクラスを取得
HandlerLineImage = py"HandlerLineImage"

# サンプルプロット
fig, ax = plt.subplots(figsize=(10, 6))

# データをプロット
x = range(0, 10, step=1)
line1 = ax.plot(x, 4*x .+ 10, label="Sin wave")[1]
line2 = ax.plot(x, 3*x .+ 10, label="Cos wave")[1]

# 画像付きlegendを追加
img_size = 10
space = 20
fontsize = 12
handler_map = Dict(
    line1 => HandlerLineImage("niwatori1.png", img_size=img_size, spacing=space),
    line2 => HandlerLineImage("niwatori2.png", img_size=img_size, spacing=space)
)

ax.legend(handler_map=handler_map, handlelength=2, fontsize=fontsize, loc="upper center", frameon=false)
display(fig)

コメント

タイトルとURLをコピーしました