Rabbit_p
@Rabbit_p

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

ipywidjetsによるインタラクティブな各凡例の更新

解決したいこと

グラフの描画の際にプログラム中ではなく、ipywidjetsを用いてインタラクティブに各凡例の文字列を更新できるようにしたいです。

データフレームのlabel列の要素の種類や数が変わる前提でプログラムを作りたく困っている次第です。

以下のプログラムを利用すると、Jupyterのログには凡例が更新されたグラフが表示されているのですが、Notebook上では更新されない状況です。

よろしくお願いいたします。

import pandas as pd
import matplotlib.pyplot as plt
import ipywidgets as widgets
from IPython.display import display, clear_output

# 仮のデータフレームを作成
df = pd.DataFrame({
    'label': ['A', 'B', 'C', 'A', 'B', 'C'],
    'value': [1, 2, 3, 4, 5, 6]
})

# ラベルごとに値を合計
grouped_df = df.groupby('label')['value'].sum()

# 初期の凡例名を設定
initial_legend_names = grouped_df.index.tolist()

# テキストボックスを作成し、初期の凡例名を設定
text_boxes = [widgets.Text(value=name) for name in initial_legend_names]

# 描画用の関数を定義
def plot_bars(legend_names):
    fig, ax = plt.subplots()
    bars = ax.bar(grouped_df.index, grouped_df.values)
    for bar, legend_name in zip(bars, legend_names):
        bar.set_label(legend_name)
    ax.legend()
    plt.show()

# テキストボックスの値が変更されたときにグラフを再描画する関数
def on_text_change(change):
    new_legend_names = [tb.value for tb in text_boxes]
    clear_output(wait=True)
    display(widgets.VBox(text_boxes))
    plot_bars(new_legend_names)


for tb in text_boxes:
    tb.observe(on_text_change, names='value')

# 初期のグラフを描画
display(widgets.VBox(text_boxes))
plot_bars(initial_legend_names)
0

1Answer

気になる点をコメントしますね。

コードはほぼ正しいですが、clear_output()display()を使用すると、描画を更新するたびに新しいmatplotlibFigureインスタンスが作成されます。したがって、古いFigureインスタンスへの参照が失われ、Jupyter notebook上で描画が更新されません。

この問題を解決するためには、同じFigureインスタンスで描画を更新するようにする必要があります。このためには、matplotlib.pyplotではなく、matplotlib.figure.Figurematplotlib.axes.Axesを直接使用します。

まず、matplotlib.figure.Figurematplotlib.backends.backend_agg.FigureCanvasをインポートします。

from matplotlib.figure import Figure
from matplotlib.backends.backend_agg import FigureCanvas

matplotlib.pyplot.subplots()の代わりにmatplotlib.figure.Figureとmatplotlib.axes.Axesを直接使用します。

# FigureインスタンスとそのCanvasを作成
fig = Figure(figsize=(6, 4), dpi=100)
canvas = FigureCanvas(fig)
ax = fig.add_subplot(111)

グラフを描画する際、同じFigureAxesインスタンスを使用し、描画を更新します。

def plot_bars(legend_names):
    ax.clear()  # 前の描画をクリア
    bars = ax.bar(grouped_df.index, grouped_df.values)
    for bar, legend_name in zip(bars, legend_names):
        bar.set_label(legend_name)
    ax.legend()
    canvas.draw()  # 描画を更新
    display(fig)

この変更により、各凡例の名前がインタラクティブに更新され、その更新が即座に反映されるようになるかと思います。

0Like

Your answer might help someone💌