いろいろ倉庫

KNIME、EXCEL、R、Pythonなどの備忘録

【Python】散布図でプロットにカーソルを合わせると画像がホバーするようにしたい

・お題:先日、画像を散布図上に表示したが、プロットが多いとどうしても画像とプロットが被ってしまい、とても見づらい。プロットにカーソルを合わせると画像がホバーする仕様にしたい。

 

・以下のサイトを参考にさせて頂いた。正しいことは以下のサイトをご確認いただきたい。

stackoverflow.com

qiita.com

 

・画像は以下のサイトから拝借した。pngで落としてワーキングディレクトリに保存した。

icooon-mono.com

・画像を読み込み、リストに格納する。

%matplotlib widget

import matplotlib.pyplot as plt

imgname=["マンボウ","マグロ","エビ","ピザ","さくらんぼ"]
img=
for n in imgname:    
    img.append(plt.imread(f"{n}.png", format="png"))

・格納した画像は以下のように確認できる。

fig, ax = plt.subplots()
ax.imshow(img[0])
plt.show()

・今回のプロットは以下の通り。これに機能を付け足す感じ。

import matplotlib.pyplot as plt
fig, ax = plt.subplots()

x=[1,2,3,4,5]
y=[1,2,3,4,5]
sc = plt.scatter(x, y)

ax.set_xlim(0, 6)
ax.set_ylim(0, 6)
ax.set_xlabel("X")
ax.set_ylabel("Y")

plt.show()

・矢印がくっついた画像を格納した箱を準備する。xyboxでプロットと箱の位置関係を、OffsetImageのimg[0]で先頭の画像を例として取り出して画像の設定をしているみたい。

from matplotlib.offsetbox import OffsetImage, AnnotationBbox

imagebox = OffsetImage(img[0], zoom=0.1)
imagebox.image.axes = ax

annot = AnnotationBbox(imagebox,
                       xy=(0,0),
                       xybox=(30,-30),
                       xycoords="data",
                       boxcoords="offset points",
                       pad=0.5,
                       arrowprops=dict( arrowstyle="->"))
annot.set_visible(False)
ax.add_artist(annot)

・カーソルの場所から情報を拾って、何の画像を表示するか決める関数を定義するらしい。

def update_annot(ind):
    i = ind["ind"][0]
    pos = sc.get_offsets()[i]
    annot.xy = (pos[0], pos[1])
    imagebox.set_data(img[i])

・矢印つきの箱を出したり消したりする関数を定義しているみたい。

def hover(event):
    vis = annot.get_visible()
    if event.inaxes == ax:
        cont, ind = sc.contains(event)
        if cont:
            update_annot(ind)
            annot.set_visible(True)
            fig.canvas.draw_idle()
        else:
            if vis:
                annot.set_visible(False)
                fig.canvas.draw_idle()

・最後に、カーソルを動かしたときに矢印つきの箱を出したり消したりしてほしいというコマンドを追加して、表示する。確かにカーソルを合わせると画像が出てくる。

fig.canvas.mpl_connect("motion_notify_event", hover)

plt.show()

・上から下まで一気に行くと以下の感じ。

import matplotlib.pyplot as plt

%matplotlib widget

imgname=["マンボウ","マグロ","エビ","ピザ","さくらんぼ"]
img=
for n in imgname:    
    img.append(plt.imread(f"{n}.png", format="png"))

fig, ax = plt.subplots()

x=[1,2,3,4,5]
y=[1,2,3,4,5]
sc = plt.scatter(x, y)

ax.set_xlim(0, 6)
ax.set_ylim(0, 6)
ax.set_xlabel("X")
ax.set_ylabel("Y")


from matplotlib.offsetbox import OffsetImage, AnnotationBbox

imagebox = OffsetImage(img[0], zoom=0.1)
imagebox.image.axes = ax

annot = AnnotationBbox(imagebox,
                       xy=(0,0),
                       xybox=(30,-30),
                       xycoords="data",
                       boxcoords="offset points",
                       pad=0.5,
                       arrowprops=dict( arrowstyle="->"))
annot.set_visible(False)
ax.add_artist(annot)


def update_annot(ind):
    i = ind["ind"][0]
    pos = sc.get_offsets()[i]
    annot.xy = (pos[0], pos[1])
    imagebox.set_data(img[i])

def hover(event):
    vis = annot.get_visible()
    if event.inaxes == ax:
        cont, ind = sc.contains(event)
        if cont:
            update_annot(ind)
            annot.set_visible(True)
            fig.canvas.draw_idle()
        else:
            if vis:
                annot.set_visible(False)
                fig.canvas.draw_idle()

fig.canvas.mpl_connect("motion_notify_event", hover)

plt.show()

・ちなみに、motion_notify_eventをbutton_press_eventに変えると、クリックした際に画像がでてくるようになる。

 

・ちなみに、以下のようにすれば画像ではなくテキストをホバーできる。

import matplotlib.pyplot as plt

plt.rcParams['font.family'] = "MS Gothic"#これがないと文字化けする。
%matplotlib widget

imgname=["マンボウ","マグロ","エビ","ピザ","さくらんぼ"]

img=[]
for n in imgname:    
    img.append(plt.imread(f"{n}.png", format="png"))

fig, ax = plt.subplots()

x=[1,2,3,4,5]
y=[1,2,3,4,5]
sc = plt.scatter(x, y)

ax.set_xlim(0, 6)
ax.set_ylim(0, 6)
ax.set_xlabel("X")
ax.set_ylabel("Y")


from matplotlib.offsetbox import OffsetImage, AnnotationBbox

annot = ax.annotate("",
                    xy=(0,0),
                    xytext=(20,-20),
                    textcoords="offset points",
                    arrowprops=dict(arrowstyle="->"))
annot.set_visible(False)

def update_annot(ind):
    i = ind["ind"][0]
    pos = sc.get_offsets()[i]
    annot.xy = (pos[0], pos[1])
    annot.set_text(imgname[i])

def hover(event):
    vis = annot.get_visible()
    if event.inaxes == ax:
        cont, ind = sc.contains(event)
        if cont:
            update_annot(ind)
            annot.set_visible(True)
            fig.canvas.draw_idle()
        else:
            if vis:
                annot.set_visible(False)
                fig.canvas.draw_idle()

fig.canvas.mpl_connect("motion_notify_event", hover)

plt.show()

おわり。