"""
概要: Matplotlibのプロットイベントを処理し、インタラクティブな機能を提供するユーティリティモジュール。
詳細説明:
本モジュールは、TkinterとMatplotlibを連携させ、プロット上で様々なインタラクティブ操作を可能にするための
クラスとメソッドを提供します。具体的には、以下の機能を含みます。
- プロット上での範囲選択 (RangeSelector)
- データ点のクリックイベント処理と情報表示
- ポップアップメニューの表示
- プロット上のテキストやマーカーのドラッグ移動
- マウスカーソルに追従する補助線やマーカー
- mplcursorsによるデータアノテーション
これにより、ユーザーはより直感的にMatplotlibのグラフと対話できるようになります。
関連リンク: tkplotevent_usage
"""
import tklib.tkimport as imp
np = imp.import_lib("numpy", stop_by_error = False)
pd = imp.import_lib("pandas", stop_by_error = False)
mpl = imp.import_lib("matplotlib", stop_by_error = False)
mplcursors = imp.import_lib("mplcursors", stop_by_error = False)
tk = imp.import_lib("tkinter", stop_by_error = False)
from tkinter import Tk, Menu
from tkinter import ttk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
imp.messages(stop_by_error = False)
import sys
import random
from matplotlib import pyplot as plt
import matplotlib.widgets as wg
from tklib.tkobject import tkObject
from tklib.tkparams import tkParams
[ドキュメント]
class RangeSelector():
"""
概要: Matplotlibのプロット上で範囲選択を可能にするクラス。
詳細説明:
マウスドラッグによりX軸またはY軸、あるいは両方の範囲を視覚的に選択し、その選択範囲をコンソールに出力します。
選択された範囲は、指定されたスタイル(色、線種、線幅)の補助線で表示されます。
"""
def __init__(self, mode = 'x', axis = None, color = 'red', linestyle = 'dashed', linewidth = 0.5, print_level = 1):
"""
概要: RangeSelectorのインスタンスを初期化する。
詳細説明:
選択モード、Axesオブジェクト、線のスタイルなどを設定し、マウスイベントに接続します。
初期状態では補助線は非表示です。
引数:
:param mode: str, 選択する軸を指定 ('x', 'y', 'xy' のいずれか)。
:type mode: str
:param axis: matplotlib.axes.Axes, 範囲選択を行うAxesオブジェクト。
:type axis: matplotlib.axes.Axes
:param color: str, 選択範囲を示す線の色。
:type color: str
:param linestyle: str, 選択範囲を示す線のスタイル (例: 'solid', 'dashed', 'dotted')。
:type linestyle: str
:param linewidth: float, 選択範囲を示す線の幅。
:type linewidth: float
:param print_level: int, 選択範囲をコンソールに出力するレベル。0で出力しない。
:type print_level: int
"""
self.ax = axis
self.color = color
self.linestyle = linestyle
self.linewidth = linewidth
self.print_level = print_level
self.x0 = 0.0
self.x1 = 0.0
self.y0 = 0.0
self.y1 = 0.0
self.vline0 = None
self.vline1 = None
self.hline0 = None
self.hline1 = None
self.set_mode(mode)
self.button_press = self.ax.figure.canvas.mpl_connect('button_press_event', self.on_press)
self.button_release = self.ax.figure.canvas.mpl_connect('button_release_event', self.on_release)
self.motion = self.ax.figure.canvas.mpl_connect('motion_notify_event', self.on_mouse_move)
[ドキュメント]
def set_mode(self, mode):
"""
概要: 選択モード(X軸、Y軸、または両方)を設定する。
詳細説明:
内部フラグ (mode_x, mode_y) を更新し、表示される補助線 (vline0, vline1, hline0, hline1) の可視性を制御します。
補助線が存在しない場合は、ここで初期化されます。
引数:
:param mode: str, 選択する軸を指定 ('x', 'y', 'xy' のいずれか)。
:type mode: str
"""
self.moving = False
self.mode_x = 'x' in mode
self.mode_y = 'y' in mode
if self.vline0 is None:
self.vline0 = self.ax.axvline(self.x0, color = self.color, linestyle = self.linestyle, linewidth = self.linewidth)
if self.vline1 is None:
self.vline1 = self.ax.axvline(self.x1, color = self.color, linestyle = self.linestyle, linewidth = self.linewidth)
if self.hline0 is None:
self.hline0 = self.ax.axhline(self.y0, color = self.color, linestyle = self.linestyle, linewidth = self.linewidth)
if self.hline1 is None:
self.hline1 = self.ax.axhline(self.y1, color = self.color, linestyle = self.linestyle, linewidth = self.linewidth)
self.vline0.set_visible(False)
self.vline1.set_visible(False)
self.hline0.set_visible(False)
self.hline1.set_visible(False)
[ドキュメント]
def on_press(self, event):
"""
概要: マウスのボタンが押されたときのイベントハンドラ。
詳細説明:
イベントが左クリックで、かつ現在のAxes内で発生した場合に、選択開始点を記録します。
その後、対応する補助線(X軸用またはY軸用)を表示し、キャンバスを再描画します。
引数:
:param event: matplotlib.backend_bases.MouseEvent, Matplotlibのマウスイベントオブジェクト。
:type event: matplotlib.backend_bases.MouseEvent
"""
if event.button != 1: # 左クリック以外
return
if event.inaxes != self.ax:
return
self.moving = True
self.x0 = event.xdata
self.y0 = event.ydata
if self.mode_x:
self.vline0.set_xdata([self.x0])
self.vline0.set_visible(True)
if self.mode_y:
self.hline0.set_ydata([self.y0])
self.hline0.set_visible(True)
self.ax.figure.canvas.draw()
[ドキュメント]
def on_mouse_move(self, event):
"""
概要: マウスが移動したときのイベントハンドラ。
詳細説明:
範囲選択が進行中の場合、マウスの位置に応じて選択範囲の終点を更新します。
対応する補助線(X軸用またはY軸用)をリアルタイムで描画し、キャンバスを再描画します。
引数:
:param event: matplotlib.backend_bases.MouseEvent, Matplotlibのマウスイベントオブジェクト。
:type event: matplotlib.backend_bases.MouseEvent
"""
if not self.moving:
return
if event.inaxes != self.ax:
return
self.x1 = event.xdata
self.y1 = event.ydata
if self.mode_x:
self.vline1.set_xdata([self.x1])
self.vline1.set_visible(True)
if self.mode_y:
self.hline1.set_ydata([self.y1])
self.hline1.set_visible(True)
self.ax.figure.canvas.draw()
[ドキュメント]
def on_release(self, event):
"""
概要: マウスのボタンが離されたときのイベントハンドラ。
詳細説明:
範囲選択が進行中の場合、マウスが離された位置を最終的な選択範囲の終点として記録します。
その後、finalizeメソッドを呼び出して選択操作を確定します。
引数:
:param event: matplotlib.backend_bases.MouseEvent, Matplotlibのマウスイベントオブジェクト。
:type event: matplotlib.backend_bases.MouseEvent
"""
if not self.moving:
return
self.x1 = event.xdata
self.y1 = event.ydata
self.finalize()
[ドキュメント]
def finalize(self):
"""
概要: 範囲選択操作を確定する。
詳細説明:
最終的な選択範囲の終点を補助線に設定し、print_levelが0より大きい場合は、
選択されたX軸またはY軸の範囲をコンソールに出力します。
その後、キャンバスを再描画し、movingフラグをFalseにリセットします。
"""
if self.mode_x:
self.vline1.set_xdata([self.x1])
if self.print_level:
print(f"Selected x range: {self.x0} - {self.x1}")
if self.mode_y:
self.hline1.set_ydata([self.y1])
if self.print_level:
print(f"Selected y range: {self.y0} - {self.y1}")
self.ax.figure.canvas.draw()
self.moving = False
[ドキュメント]
class tkPlotEvent(tkObject):
"""
概要: Matplotlibのプロットイベントを処理し、インタラクティブな機能を提供するユーティリティクラス。
詳細説明:
本クラスは、データ点のクリックイベント処理、ポップアップメニュー、移動可能なテキスト/点の管理、
マウス追従カーソル、データアノテーションなど、Matplotlibのプロットをよりインタラクティブにするための
多岐にわたる機能を提供します。tklib.tkobject.tkObjectを継承し、パラメータ管理も行います。
"""
def __init__(self, plt, distance = 'r', **args):
"""
概要: tkPlotEventのインスタンスを初期化する。
詳細説明:
MatplotlibのpyplotモジュールまたはFigureオブジェクトと、データ点との距離計算モードを設定し、
イベント処理のための各種パラメータを初期化します。
各種機能(アノテーション、マウスカーソル、データ移動、停止ボタン)のコンテナも準備されます。
引数:
:param plt: matplotlib.pyplotモジュール、またはmatplotlib.figure.Figureオブジェクト。
:type plt: module or matplotlib.figure.Figure
:param distance: str, データ点との距離を計算する際の基準 ('r': 半径, 'x': X軸方向, 'y': Y軸方向)。
:type distance: str
:param args: dict, tkObjectの初期化に渡される追加のキーワード引数。
:type args: dict
"""
self.plt = plt
# データ点との距離の測り方: 'r', 'x', 'y'
self.distance = distance
# マウスクリック時に最近接データの情報をコンソールに表示
self.hit_itarget = None
self.hit_axisinf = None
self.stop_flag = False
# mplcursors/annotate用変数
self.annotation = tkParams()
# マウスカーソル用変数
self.follow_mouse = tkParams()
# データ移動変数
self.move_points = tkParams()
# stop buttun用コンテナ
self.stop_button = None
super(tkObject, self).__init__(**args)
# super(tkParams, self).__init__(parameter_file, app, args)
# self.update(**args)
self.axis_inf = []
[ドキュメント]
def layout(self, show = False):
"""
概要: プロットのレイアウトを調整する。
詳細説明:
plt.subplots_adjustを使用して、add_buttonなどで設定されたplot_regionに基づきプロット領域を調整します。
showがTrueの場合、プロットを表示または一時停止します。
引数:
:param show: bool, Trueの場合、プロットを表示または一時停止する。
:type show: bool
"""
# self.plt.tight_layout()
self.plt.subplots_adjust(top = self.plot_region[0], bottom = self.plot_region[1])
if show:
if use_pause is not None:
self.plt.pause(use_pause)
else:
self.plt.show()
[ドキュメント]
def remove(self, index):
"""
概要: axis_infリストからデータを削除する。
詳細説明:
引数indexが整数の場合はリストのインデックスとして、文字列の場合は'label'キーの値として解釈し、
対応するデータ情報をself.axis_infリストから削除します。
引数:
:param index: int or str, 削除するデータのインデックス(整数)またはラベル(文字列)。
:type index: int or str
"""
if type(index) is int:
self.axis_inf.pop(index)
return
for i in range(len(self.axis_inf)):
if self.axis_inf[i]['label'] == index:
self.axis_inf.pop(i)
return
[ドキュメント]
def add_data(self, axis_inf = {}):
"""
概要: プロットデータをイベント処理用に登録する。
詳細説明:
与えられたデータ情報(axis_inf辞書)を内部リストself.axis_infに追加します。
プロットタイプ('2D', 'plot', 'scatter')に応じてデータ構造を統一し、
X軸/Y軸のラベル情報も Axes オブジェクトから取得または設定します。
無効なplot_typeが指定された場合はエラーメッセージを出力し終了します。
引数:
:param axis_inf: dict, プロットに関する情報を含む辞書。必須キー: 'plot_type', 'axis', 'data'。
オプションキー: 'xlabel', 'ylabel', 'x', 'y' (plot_type='scatter'の場合)。
:type axis_inf: dict
戻り値:
:returns: dict, 登録されたaxis_inf辞書。
:rtype: dict
"""
#{"label": "test predict", "plot_type": "2D", "axis": axis, "data": test_predict_data,
# "xlist": [index_test_list, *x_test_list], "xlabels": ["index", *x_labels]})
plot_type = axis_inf['plot_type']
# プロットの仕方によってデータ構造が違うので、ここで統一しておく
axis = axis_inf['axis']
data = axis_inf['data']
if axis_inf.get('xlabel', None):
axis.xlabel = axis_inf['xlabel']
else:
axis.xlabel = axis.get_xlabel()
if axis_inf.get('ylabel', None):
axis.ylabel = axis_inf['ylabel']
else:
axis.ylabel = axis.get_ylabel()
if plot_type == '2D' or plot_type == 'plot':
for i in range(len(data)):
data[i].xdata = data[i].get_xdata()
data[i].ydata = data[i].get_ydata()
elif plot_type == 'scatter':
data = [data]
axis_inf['data'] = data
data[0].xdata = axis_inf['x']
data[0].ydata = axis_inf['y']
else:
print("")
print(f"Error in tkplotevent.add_data(): Invalid plot_type [{plot_type}]")
input(">>")
exit()
self.axis_inf.append(axis_inf)
return axis_inf
# マウスクリック時にクリックされたグラフ軸を特定
# axesが与えられたときはmplcursors、与えられない場合はplot/scatter用
[ドキュメント]
def find_target_axes(self, event, axes = None):
"""
概要: マウスクリックイベントが発生したAxesオブジェクトを特定する。
詳細説明:
イベントのinaxesプロパティと登録されたAxesオブジェクトを比較し、
イベントが発生したAxesとそのインデックス(またはaxis_inf辞書)を返します。
axes引数が指定された場合はそのリストから検索し、指定されない場合は内部のself.axis_infから検索します。
引数:
:param event: matplotlib.backend_bases.MouseEvent, Matplotlibのマウスイベントオブジェクト。
:type event: matplotlib.backend_bases.MouseEvent
:param axes: list, 検索対象となるAxesオブジェクトのリスト(オプション)。指定しない場合は内部のself.axis_infを使用。
:type axes: list or None
戻り値:
:returns: tuple: (itargets, hit_axisinfs) - itargets: list or int, ターゲットAxesのインデックスのリスト、
または単一のインデックス。hit_axisinfs: list or matplotlib.axes.Axes, ターゲットAxes情報
(axis_inf辞書)のリスト、または単一のAxesオブジェクト。tuple: (None, None), ターゲットAxesが見つからない場合。
:rtype: tuple
"""
if axes:
naxis = len(axes)
else:
naxis = len(self.axis_inf)
itargets = []
hit_axisinfs = []
for i in range(naxis):
# print("a=", event.inaxes, self.axis_inf[i]["axis"])
if axes:
if event.inaxes == axes[i]:
return i, axes[i]
else:
if event.inaxes == self.axis_inf[i]["axis"]:
itargets.append(i)
hit_axisinfs.append(self.axis_inf[i])
if axes:
return None, None
if len(itargets) == 0:
return None, None
return itargets, hit_axisinfs
[ドキュメント]
def convert_coord(self, x0, y0, axis_s, axis_t):
"""
概要: あるAxes座標から別のAxes座標へ点を変換する。
詳細説明:
異なるスケールや範囲を持つAxes間で点の位置を相対的に変換する際に使用されます。
例えば、twinx()やtwiny()で作成されたAxes間で座標を変換する場合に有用です。
引数:
:param x0: float, 変換元のX座標。
:type x0: float
:param y0: float, 変換元のY座標。
:type y0: float
:param axis_s: matplotlib.axes.Axes, 変換元のAxesオブジェクト。
:type axis_s: matplotlib.axes.Axes
:param axis_t: matplotlib.axes.Axes, 変換先のAxesオブジェクト。
:type axis_t: matplotlib.axes.Axes
戻り値:
:returns: tuple: (float, float), 変換後の(x1, y1)座標。
tuple: (None, None), 入力が無効な場合。
:rtype: tuple
"""
if axis_s is None or axis_t is None or x0 is None or y0 is None:
return None, None
xlim0 = axis_s.get_xlim()
ylim0 = axis_s.get_ylim()
xlim1 = axis_t.get_xlim()
ylim1 = axis_t.get_ylim()
x1 = xlim1[0] + (xlim1[1] - xlim1[0]) / (xlim0[1] - xlim0[0]) * (x0 - xlim0[0])
y1 = ylim1[0] + (ylim1[1] - ylim1[0]) / (ylim0[1] - ylim0[0]) * (y0 - ylim0[0])
return x1, y1
[ドキュメント]
def prepare_move_points(self):
"""
概要: プロット上の点を移動させる機能の準備を行う。
詳細説明:
移動対象の点を管理するためのgco (Graphics Object) を初期化し、
プロットに移動可能な点を追加するためのヘルパー関数addを提供します。
"""
self.move_points.gco = None
def add(ax, x, y, marker = 'o', picker = 15, color = 'red'):
"""
概要: 指定されたAxesに移動可能な点を追加する。
引数:
:param ax: matplotlib.axes.Axes, 点を追加するAxesオブジェクト。
:type ax: matplotlib.axes.Axes
:param x: float, 点のX座標。
:type x: float
:param y: float, 点のY座標。
:type y: float
:param marker: str, 点のマーカースタイル。
:type marker: str
:param picker: float or bool, 点のピッキング感度。
:type picker: float or bool
:param color: str, 点の色。
:type color: str
"""
ax.plot(x, y, marker, markerfacecolor = color, markeredgecolor = color, picker = picker)
self.move_points.add = add
[ドキュメント]
def prepare_move_text(self, fig = None):
"""
概要: プロット上のテキストやアノテーションを移動させる機能の準備を行う。
詳細説明:
移動可能なテキストやアノテーションの情報を管理するためのリストtext_infを初期化し、
これらをプロットに追加するためのヘルパー関数add_annotationとadd_textを提供します。
座標がDataFrame形式の場合の処理や、自動でY軸範囲内に配置するロジックも含まれます。
引数:
:param fig: matplotlib.figure.Figure, イベントを登録するMatplotlibのFigureオブジェクト(オプション)。
:type fig: matplotlib.figure.Figure or None
"""
self.move_text = tkParams()
self.move_text.fig = fig
self.move_text.text_inf = []
def get_xy(ax, x, y, x_list, y_list, x_target, frac, xlim, ylim):
"""
概要: テキスト配置のためのX, Y座標を取得する。
詳細説明:
与えられたx_listとy_listから、x_targetまたはfracに基づいて適切なX, Y座標を決定します。
xlimやylimが指定されている場合、その範囲内に収まるように調整を試みます。
引数:
:param ax: matplotlib.axes.Axes, テキストを配置するAxesオブジェクト。
:type ax: matplotlib.axes.Axes
:param x: float, 初期X座標。
:type x: float
:param y: float, 初期Y座標。
:type y: float
:param x_list: list or numpy.ndarray or pandas.DataFrame, X座標のリスト。
:type x_list: list or numpy.ndarray or pandas.DataFrame
:param y_list: list or numpy.ndarray or pandas.DataFrame, Y座標のリスト。
:type y_list: list or numpy.ndarray or pandas.DataFrame
:param x_target: float, ターゲットX座標。
:type x_target: float
:param frac: float, X軸範囲の割合(0.0〜1.0)でX座標を指定。
:type frac: float
:param xlim: tuple, X軸の範囲 (min, max)。
:type xlim: tuple
:param ylim: tuple, Y軸の範囲 (min, max)。
:type ylim: tuple
戻り値:
:returns: tuple: (float, float), 決定された(x, y)座標。
tuple: (None, None), 入力データが不十分な場合。
:rtype: tuple
"""
if x is None or y is None or x_list is None or y_list is None: return None, None
if x_list is not None:
if isinstance(x_list, pd.DataFrame):
_x_list = x_list.to_numpy().T
else:
_x_list = x_list
if isinstance(y_list, pd.DataFrame):
_y_list = y_list.to_numpy().T
else:
_y_list = y_list
if not isinstance(_x_list[0], np.ndarray):
_x_list = np.array([_x_list])
if not isinstance(_y_list[0], np.ndarray):
_y_list = np.array([_y_list])
_x_list = _x_list.flatten()
_y_list = _y_list.flatten()
auto_determine = False
if x_target is None:
if frac is None:
auto_determine = True
frac = random.random()
_min = min(_x_list)
_max = max(_x_list)
if xlim:
_min = max([_min, xlim[0]])
_max = min([_max, xlim[1]])
x_target = _min + (_max - _min) * frac
# print("x_target=", frac, _min, _max, x_target)
x = x_target
y = np.interp(x, x_list, y_list)
if auto_determine and ylim and (y < ylim[0] or ylim[1] < y):
for f in np.arange(0.0, 1.0, 0.025):
x = _min + (_max - _min) * f
y = np.interp(x, x_list, y_list)
if ylim[0] <= y and y <= ylim[1]:
break
else:
print("no data found")
x = _min + (_max - _min) * 0.5
y = np.interp(x, x_list, y_list)
'''
idx_min = 0
dx_min = 1.0e300
for i, _x in enumerate(_x_list):
absdx = abs(_x - x_target)
if absdx < dx_min:
idx_min = i
dx_min = absdx
x = _x_list[idx_min]
y = _y_list[idx_min]
'''
return x, y
def add_annotation(ax, ax_ref, x = None, y = None, x_list = None, y_list = None,
x_offset = 0, x_target = None, frac = None, xlim = None, ylim = None,
text = "no name", fontsize = 10,
ha = 'center', va = 'center', color = 'black', fc = "w", ec = "none", alpha = 0.5):
"""
概要: 指定されたAxesに移動可能なアノテーションを追加する。
詳細説明:
アノテーションの表示位置を決定し、Axesに追加します。
アノテーションは内部リストself.move_text.text_infに登録され、後で移動可能になります。
引数:
:param ax: matplotlib.axes.Axes, アノテーションを追加するAxesオブジェクト。
:type ax: matplotlib.axes.Axes
:param ax_ref: matplotlib.axes.Axes, 座標変換の基準となるAxesオブジェクト。axと同じでも良い。
:type ax_ref: matplotlib.axes.Axes
:param x: float, アノテーションのX座標。
:type x: float or None
:param y: float, アノテーションのY座標。
:type y: float or None
:param x_list: list or numpy.ndarray or pandas.DataFrame, X座標のリスト(データから位置を決定する場合)。
:type x_list: list or numpy.ndarray or pandas.DataFrame or None
:param y_list: list or numpy.ndarray or pandas.DataFrame, Y座標のリスト(データから位置を決定する場合)。
:type y_list: list or numpy.ndarray or pandas.DataFrame or None
:param x_offset: float, X座標に対するオフセット。
:type x_offset: float
:param x_target: float, ターゲットX座標(x_listから位置を決定する場合)。
:type x_target: float or None
:param frac: float, X軸範囲の割合(x_listから位置を決定する場合)。
:type frac: float or None
:param xlim: tuple, X軸の範囲 (min, max)。
:type xlim: tuple or None
:param ylim: tuple, Y軸の範囲 (min, max)。
:type ylim: tuple or None
:param text: str, 表示するテキスト。
:type text: str
:param fontsize: int, テキストのフォントサイズ。
:type fontsize: int
:param ha: str, 水平アライメント。
:type ha: str
:param va: str, 垂直アライメント。
:type va: str
:param color: str, テキストの色。
:type color: str
:param fc: str, テキストボックスの面の色。
:type fc: str
:param ec: str, テキストボックスの縁の色。
:type ec: str
:param alpha: float, テキストボックスの透明度。
:type alpha: float
"""
x, y = get_xy(ax, x, y, x_list, y_list, x_target, frac, xlim, ylim)
if x is None: return
# print("annotate:", x, x_offset, x + x_offset, y, text)
hannotate = ax.annotate(text, xy = (x + x_offset, y), xytext = (0, 0), color = color,
# hannotate = ax.annotate(text, xy = (x, y), xytext = (x, y),
textcoords = 'offset points', fontsize = fontsize,
bbox={'facecolor': fc, 'alpha': alpha, 'pad': 0.5, 'edgecolor': ec, 'boxstyle': 'round'})
# bbox = dict(boxstyle = "round,pad=0.3", fc = fc, ec = ec))
self.move_text.text_inf.append([ax, ax_ref, hannotate, False, "annotate"])
def add_text(ax, ax_ref, x = None, y = None, x_list = None, y_list = None,
x_offset = 0, x_target = None, frac = None, xlim = None, ylim = None,
text = "no name", fontsize = 10, ha = 'center', va = 'center', color = 'black', fc = "w", ec = "none", alpha = 0.5):
"""
概要: 指定されたAxesに移動可能なテキストを追加する。
詳細説明:
テキストの表示位置を決定し、Axesに追加します。
テキストは内部リストself.move_text.text_infに登録され、後で移動可能になります。
引数:
:param ax: matplotlib.axes.Axes, テキストを追加するAxesオブジェクト。
:type ax: matplotlib.axes.Axes
:param ax_ref: matplotlib.axes.Axes, 座標変換の基準となるAxesオブジェクト。axと同じでも良い。
:type ax_ref: matplotlib.axes.Axes
:param x: float, テキストのX座標。
:type x: float or None
:param y: float, テキストのY座標。
:type y: float or None
:param x_list: list or numpy.ndarray or pandas.DataFrame, X座標のリスト(データから位置を決定する場合)。
:type x_list: list or numpy.ndarray or pandas.DataFrame or None
:param y_list: list or numpy.ndarray or pandas.DataFrame, Y座標のリスト(データから位置を決定する場合)。
:type y_list: list or numpy.ndarray or pandas.DataFrame or None
:param x_offset: float, X座標に対するオフセット。
:type x_offset: float
:param x_target: float, ターゲットX座標(x_listから位置を決定する場合)。
:type x_target: float or None
:param frac: float, X軸範囲の割合(x_listから位置を決定する場合)。
:type frac: float or None
:param xlim: tuple, X軸の範囲 (min, max)。
:type xlim: tuple or None
:param ylim: tuple, Y軸の範囲 (min, max)。
:type ylim: tuple or None
:param text: str, 表示するテキスト。
:type text: str
:param fontsize: int, テキストのフォントサイズ。
:type fontsize: int
:param ha: str, 水平アライメント。
:type ha: str
:param va: str, 垂直アライメント。
:type va: str
:param color: str, テキストの色。
:type color: str
:param fc: str, テキストボックスの面の色。
:type fc: str
:param ec: str, テキストボックスの縁の色。
:type ec: str
:param alpha: float, テキストボックスの透明度。
:type alpha: float
"""
x, y = get_xy(ax, x, y, x_list, y_list, x_target, frac, xlim, ylim)
htext = ax.text(x + x_offset, y, text, fontsize = fontsize, ha = ha, va = va, color = color,
bbox={'facecolor': fc, 'alpha': alpha, 'pad': 0.5, 'edgecolor': ec, 'boxstyle': 'round'})
self.move_text.text_inf.append([ax, ax_ref, htext, False, "text"])
self.move_text.add_text = add_text
self.move_text.add_annotation = add_annotation
[ドキュメント]
def register_move_text_event(self, fig = None, activate = False):
"""
概要: プロット上のテキストやアノテーションをドラッグして移動させるイベントを登録する。
詳細説明:
マウスのボタンプレス、リリース、移動イベントを監視し、選択されたテキスト/アノテーションの位置を
リアルタイムで更新します。activateメソッドで移動機能のアクティブ/非アクティブを切り替えられます。
twinx/yなどで異なるAxesを使用している場合も座標変換して移動を処理します。
引数:
:param fig: matplotlib.figure.Figure, イベントを登録するMatplotlibのFigureオブジェクト(オプション)。
:type fig: matplotlib.figure.Figure or None
:param activate: bool, 初期状態で移動機能をアクティブにするかどうか。
:type activate: bool
"""
if fig is not None:
self.move_text.fig = fig
self.move_text.is_active = activate
def activate_func(flag = True):
"""
概要: テキスト移動機能をアクティブ/非アクティブにする。
引数:
:param flag: bool, Trueでアクティブ、Falseで非アクティブ。
:type flag: bool
"""
self.move_text.is_active = flag
self.move_text.activate = activate_func
def on_click(event):
"""
概要: マウスプレスイベントハンドラ。テキストがクリックされたかチェックし、ドラッグ開始。
"""
for inf in self.move_text.text_inf:
annot = inf[2]
contains, _ = annot.contains(event)
if contains:
inf[3] = True
return
def on_release(event):
"""
概要: マウスリリースイベントハンドラ。ドラッグ終了。
"""
for inf in self.move_text.text_inf:
inf[3] = False
def on_move(event):
"""
概要: マウス移動イベントハンドラ。ドラッグ中のテキスト位置を更新。
"""
if not self.move_text.is_active: return
if event.button == 1:
for inf in self.move_text.text_inf:
ax = inf[0]
ax_ref = inf[1]
annot = inf[2]
is_dragging = inf[3]
_type = inf[4]
# ref_axisでの値
x0 = event.xdata
y0 = event.ydata
# axisでの値に戻す
x1, y1 = self.convert_coord(x0, y0, ax_ref, ax)
if x1 is None:
return
if is_dragging:
if event.xdata is not None and event.ydata is not None:
if _type == 'text':
annot.set_x(x1)
annot.set_y(y1)
annot.xy = (x1, y1) # xyを直接設定する必要がある場合も考慮
elif _type == 'annotate':
# Annotateの場合、xyはデータ座標、xytextはオフセットなのでxytextを(0,0)に保ちxyを更新
annot.set_x(0)
annot.set_y(0)
annot.xy = (x1, y1)
self.move_text.fig.canvas.draw()
return
self.move_text.fig.canvas.mpl_connect('button_press_event', on_click)
self.move_text.fig.canvas.mpl_connect('button_release_event', on_release)
self.move_text.fig.canvas.mpl_connect('motion_notify_event', on_move)
[ドキュメント]
def register_move_points_event(self, fig):
"""
概要: プロット上のマーカーをドラッグして移動させるイベントを登録する。
詳細説明:
マウスイベントを監視し、ピックされたマーカーをドラッグ中に移動させることができます。
これにより、プロット上の特定のデータ点をインタラクティブに操作することが可能になります。
引数:
:param fig: matplotlib.figure.Figure, イベントを登録するMatplotlibのFigureオブジェクト。
:type fig: matplotlib.figure.Figure
"""
self.move_points.fig = fig
def motion(event):
"""
概要: マウス移動イベントハンドラ。ドラッグ中のマーカー位置を更新。
"""
if self.move_points.gco is None:
return
x = event.xdata
y = event.ydata
self.move_points.gco.set_data([x], [y])
plt.draw()
def onpick(event):
"""
概要: ピックイベントハンドラ。ピックされたマーカーをgcoに設定。
"""
self.move_points. gco = event.artist
# plt.title(self.move_points.gco)
def release(event):
"""
概要: マウスリリースイベントハンドラ。ドラッグ終了。
"""
self.move_points.gco = None
fig.canvas.mpl_connect('motion_notify_event', motion)
fig.canvas.mpl_connect('pick_event', onpick)
fig.canvas.mpl_connect('button_release_event', release)
[ドキュメント]
def prepare_annotation(self):
"""
概要: mplcursorsを用いたアノテーション表示機能の準備を行う。
詳細説明:
mplcursorsに必要な内部変数を初期化し、アノテーション表示対象となるラインを登録するための
ヘルパー関数add_lineを提供します。これにより、複数のラインに対するアノテーションを管理できます。
"""
self.annotation.sel = None
self.annotation.axes = []
self.annotation.ref_axes = []
self.annotation.lines = []
self.annotation.labels = []
self.annotation.x_lists = []
self.annotation.y_lists = []
self.annotation.inf_list = []
self.annotation.annotation_formats = []
self.annotation.inf_formats = []
self.annotation.x = None
self.annotation.y = None
self.annotation.refresh_cursors = None
self.cursor = None
self.annotation.id_add_connection = False
def add_line(label, axis, ref_axis, x_list, y_list, line, inf_list = None, annotation_format = None, inf_format = None):
"""
概要: アノテーション表示対象のライン情報を追加する。
引数:
:param label: str, ラインのラベル。
:type label: str
:param axis: matplotlib.axes.Axes, ラインが属するAxesオブジェクト。
:type axis: matplotlib.axes.Axes
:param ref_axis: matplotlib.axes.Axes, 座標変換の基準となるAxesオブジェクト。
:type ref_axis: matplotlib.axes.Axes
:param x_list: list, ラインのXデータリスト。
:type x_list: list
:param y_list: list, ラインのYデータリスト。
:type y_list: list
:param line: matplotlib.lines.Line2D, 対象のラインオブジェクト。
:type line: matplotlib.lines.Line2D
:param inf_list: list or dict, 各データ点に関連する追加情報。
:type inf_list: list or dict or None
:param annotation_format: str or callable, ホバー時のアノテーション表示フォーマット文字列または関数。
:type annotation_format: str or callable or None
:param inf_format: str or callable, クリック時の情報出力フォーマット文字列または関数。
:type inf_format: str or callable or None
"""
self.annotation.axes.append(axis)
self.annotation.ref_axes.append(ref_axis)
self.annotation.lines.append(line)
self.annotation.labels.append(label)
self.annotation.x_lists.append(x_list)
self.annotation.y_lists.append(y_list)
self.annotation.inf_list.append(inf_list)
self.annotation.annotation_formats.append(annotation_format)
self.annotation.inf_formats.append(inf_format)
self.annotation.add_line = add_line
[ドキュメント]
def register_annotation_event(self, fig, activate = True, on_mouse_move = None, on_click = None, print_level = 0):
"""
概要: mplcursorsを使用してプロット上のデータ点にアノテーションを表示するイベントを登録する。
詳細説明:
マウスのホバーやクリックに応じてデータ点に関する情報を表示する機能を提供します。
twinx/yを使用したAxesにも対応しており、座標変換を考慮して正しい情報が表示されます。
アノテーションの表示/非表示やフォーマットを細かく制御できます。
引数:
:param fig: matplotlib.figure.Figure, イベントを登録するMatplotlibのFigureオブジェクト。
:type fig: matplotlib.figure.Figure
:param activate: bool, 初期状態でアノテーション機能をアクティブにするかどうか。
:type activate: bool
:param on_mouse_move: callable, カスタムのマウス移動イベントハンドラ(オプション)。
:type on_mouse_move: callable or None
:param on_click: callable, カスタムのクリックイベントハンドラ(オプション)。
:type on_click: callable or None
:param print_level: int, クリック時に情報をコンソールに出力するレベル。
:type print_level: int
"""
self.annotation.fig = fig
self.annotation.is_active = activate
def activate_func(flag = True):
"""
概要: アノテーション機能をアクティブ/非アクティブにする。
引数:
:param flag: bool, Trueでアクティブ、Falseで非アクティブ。
:type flag: bool
"""
self.annotation.is_active = flag
self.annotation.activate = activate_func
def on_mouse_move_annotate(event):
"""
概要: マウス移動イベントハンドラ。アノテーションの可視性を制御し、位置情報を更新。
"""
if not self.annotation.is_active: return
cur_sel = self.annotation
if cur_sel.sel is None:
return
eps = 1.0e-2
#twinx/y()を使った場合、event.xdata/ydataは、最後のaxis (ref_axis) における数値 => 変換する必要あり
#selection objectのtarget[0]/[1]は、選択されたグラフ線 (axis) の数値
iaxis, ref_axis = self.find_target_axes(event, axes = cur_sel.ref_axes)
axis = cur_sel.axis
# ref_axisでの値
x0 = event.xdata
y0 = event.ydata
# axisでの値
prev_x = cur_sel.sel.target[0]
prev_y = cur_sel.sel.target[1]
# axisでの値に戻す
x1, y1 = self.convert_coord(x0, y0, ref_axis, axis)
if x1 is None:
return
self.annotation.curpos = {"sel": self.annotation.sel, "event": event, "iaxis": iaxis, "axis": axis, "x": x1, "y": y1}
#前の座標からの移動量がepsより大きかったら更新
r2 = pow((prev_x - x1) / cur_sel.x, 2.0) + pow((prev_y - y1) / cur_sel.y, 2.0)
if r2 > eps:
cur_sel.sel.annotation.set_visible(False)
self.annotation.fig.canvas.draw_idle()
self.annotation.sel = None
#クリックしたときconsoleに表示するフォーマット
def inf_format(plotevent, sel, iline, idata, label, inf, x = 0, y = 0, opt = None, format = None):
"""
概要: クリック時にコンソールに情報を出力するフォーマット関数。
詳細説明:
与えられたformat文字列とデータ情報を使用して、整形されたテキストをコンソールに出力します。
引数:
:param plotevent: tkPlotEvent, tkPlotEventのインスタンス。
:type plotevent: tkPlotEvent
:param sel: mplcursors.cursor.Selection, mplcursorsのSelectionオブジェクト。
:type sel: mplcursors.cursor.Selection
:param iline: int, 選択されたラインのインデックス。
:type iline: int
:param idata: int, 選択されたデータ点のインデックス。
:type idata: int
:param label: str, 選択されたラインのラベル。
:type label: str
:param inf: dict, 選択されたデータ点に関連する追加情報。
:type inf: dict
:param x: float, 選択されたデータ点のX座標。
:type x: float
:param y: float, 選択されたデータ点のY座標。
:type y: float
:param opt: tkParams, オプションパラメータ(イベント情報など)。
:type opt: tkParams or None
:param format: str, 出力フォーマット文字列。
:type format: str or None
"""
if inf is None:
return
args = {"label": label, "iline": iline, "idata": idata}
for key, val in inf.items():
if type(val) is str:
args[key] = val
else:
try:
args[key] = val[idata]
except:
args[key] = val
text = format.format(**args)
print(text)
#マウスがhoverしたときannotationに表示するフォーマット
def annotation_format(plotevent, sel, iline, idata, label, inf, x, y, opt = None, format = None):
"""
概要: マウスホバー時にアノテーションに情報を表示するフォーマット関数。
詳細説明:
与えられたformat文字列とデータ情報を使用して、整形されたテキストをアノテーションに設定します。
formatがNoneの場合はデフォルトのフォーマットが使用されます。
引数:
:param plotevent: tkPlotEvent, tkPlotEventのインスタンス。
:type plotevent: tkPlotEvent
:param sel: mplcursors.cursor.Selection, mplcursorsのSelectionオブジェクト。
:type sel: mplcursors.cursor.Selection
:param iline: int, 選択されたラインのインデックス。
:type iline: int
:param idata: int, 選択されたデータ点のインデックス。
:type idata: int
:param label: str, 選択されたラインのラベル。
:type label: str
:param inf: dict, 選択されたデータ点に関連する追加情報。
:type inf: dict
:param x: float, 選択されたデータ点のX座標。
:type x: float
:param y: float, 選択されたデータ点のY座標。
:type y: float
:param opt: tkParams, オプションパラメータ(イベント情報など)。
:type opt: tkParams or None
:param format: str, 出力フォーマット文字列。
:type format: str or None
"""
args = {"label": label, "iline": iline, "idata": idata, "x": x, "y": y}
for key, val in inf.items():
if type(val) is str:
args[key] = val
else:
try:
args[key] = val[idata]
except:
args[key] = val
if format is not None:
text = format.format(**args)
else:
text = f"{label}".strip() + ": (" + f"{x:10.4g}".strip() + ", " + f"{y:10.4g}".strip() + ")"
sel.annotation.set(text = text)
# sel.annotation.set(text = text, position = (x0, y0))
# print(text)
def on_click_default(event):
"""
概要: マウスクリックイベントのデフォルトハンドラ。
詳細説明:
クリックされたデータ点に関連する情報をコンソールに出力します。
inf_formatで定義されたフォーマットまたはカスタム関数が使用されます。
"""
if not self.annotation.is_active: return
if event.button == 1 or event.button == 3: # 左クリックか右クリック
if event.xdata is None or event.ydata is None: return
# curpos = self.annotation.curpos
iline = None
label = None
format = None
sel = self.annotation.sel
if sel is None:
idata = None
# return
else:
if sel.artist in self.annotation.lines:
iline = self.annotation.lines.index(sel.artist)
if iline is None:
pass
# return
idata = int(sel.index + 0.499999)
label = self.annotation.labels[iline]
format = self.annotation.inf_formats[iline]
inf = None
if iline is not None and len(self.annotation.inf_list) > iline:
if event.inaxes != self.annotation.axes[iline] and event.inaxes != self.annotation.ref_axes[iline]:
print(f"Warning in tkplotevent.on_click_default(): axis is not matched with line#{iline}")
return
inf = self.annotation.inf_list[iline]
if inf is None:
print("no inf")
# return
if format is None:
format = self.annotation.inf_formats[0]
opt = tkParams()
opt.event = event
opt.sel = None
if type(format) is str:
inf_format(self, sel, iline, idata, label, inf, x = event.xdata, y = event.ydata, opt = opt, format = format)
elif format is None and inf is not None:
for key, val in inf.items():
print(f"{key}: {val[idata]}")
elif format is not None:
format(self, sel, iline, idata, label, inf, x = event.xdata, y = event.ydata, opt = opt)
else:
print(f"Warning intkplotevent.on_click_default(): No data is selected (format is None and inf is None)")
if on_click is None:
on_click = on_click_default
if on_mouse_move is None:
on_mouse_move = on_mouse_move_annotate
self.annotation.fig.canvas.mpl_connect('button_press_event', on_click)
fig.canvas.mpl_connect('motion_notify_event', on_mouse_move)
# @self.cursor.connect("add")
def on_add(sel, print_level = print_level):
"""
概要: mplcursorsのアノテーションが追加されたときに呼び出されるハンドラ。
詳細説明:
アノテーションの可視性をis_activeフラグに基づいて制御し、
選択されたデータ点の情報をannotationオブジェクトに格納します。
その後、annotation_formatで定義されたフォーマットを用いてアノテーションテキストを設定します。
引数:
:param sel: mplcursors.cursor.Selection, mplcursorsのSelectionオブジェクト。
:type sel: mplcursors.cursor.Selection
:param print_level: int, デバッグ用の出力レベル(未使用)。
:type print_level: int
"""
if self.annotation.is_active:
sel.annotation.set_visible(True)
else:
sel.annotation.set_visible(False)
return
self.annotation.sel = sel
cur_sel = self.annotation
# iaxis, axis = self.find_target_axes(sel, axes = self.annotation.axes)
iline = cur_sel.lines.index(sel.artist)
idata = int(sel.index + 0.4999)
cur_sel.axis = cur_sel.axes[iline]
x0 = cur_sel.sel.target[0]
y0 = cur_sel.sel.target[1]
sel.annotation.arrow_patch.set_alpha(0)
xlist = self.annotation.x_lists[iline]
if type(xlist) is str:
self.annotation.x = xlist
else:
try:
self.annotation.x = xlist[idata]
except:
self.annotation.x = xlist
ylist = self.annotation.x_lists[iline]
if type(ylist) is str:
self.annotation.y = ylist
else:
try:
self.annotation.y = ylist[idata]
except:
self.annotation.y = ylist
if self.annotation.y <= 0.0:
self.annotation.y = 1.0
label = self.annotation.labels[iline]
format = self.annotation.annotation_formats[iline]
inf = self.annotation.inf_list[iline]
opt = tkParams()
opt.event = None
opt.sel = sel
if format is None:
return
if type(format) is str:
text = annotation_format(self, sel, iline, idata, label, inf, x0, y0, opt = opt, format = format)
else:
text = format(self, sel, iline, idata, label, inf, x0, y0, opt = opt)
# self.cursor = mplcursors.cursor(self.annotation.lines, hover = True)
def refresh_cursors(lines = None):
"""
概要: mplcursorsのカーソルを再初期化または更新する。
詳細説明:
既存のカーソルを削除し、新たにmplcursors.cursorを生成してイベント接続を確立します。
これにより、動的に追加されたラインにもアノテーション機能が適用されるようになります。
引数:
:param lines: list, アノテーション対象のラインオブジェクトのリスト。Falseの場合、既存のラインを使用。
:type lines: list or bool or None
"""
if lines is not False:
lines = self.annotation.lines
if self.cursor:
self.cursor.remove()
self.cursor = mplcursors.cursor(lines, hover = True)
self.annotation.id_add_connection = \
self.cursor.connect("add", lambda sel: on_add(sel, print_level = print_level))
if self.annotation.refresh_cursors is None:
self.annotation.refresh_cursors = refresh_cursors
refresh_cursors()
# self.annotation.id_add_connection = \
# self.cursor.connect("add", lambda sel: on_add(sel, print_level = print_level))
[ドキュメント]
def register_follow_mouse_event(self, fig, activate = True, on_mouse_move = None, print_level = 0):
"""
概要: マウスカーソルに追従する補助線やマーカーを表示するイベントを登録する。
詳細説明:
マウスの動きに合わせて、指定されたAxes上にカーソルのX/Y座標を示す補助線またはマーカーを
リアルタイムで描画します。複数のAxesに同時に表示することも可能です。
activateメソッドで追従機能のアクティブ/非アクティブを切り替えられます。
引数:
:param fig: matplotlib.figure.Figure, イベントを登録するMatplotlibのFigureオブジェクト。
:type fig: matplotlib.figure.Figure
:param activate: bool, 初期状態で追従機能をアクティブにするかどうか。
:type activate: bool
:param on_mouse_move: callable, カスタムのマウス移動イベントハンドラ(オプション)。
:type on_mouse_move: callable or None
:param print_level: int, 未使用。
:type print_level: int
"""
self.follow_mouse.fig = fig
self.follow_mouse.gco = None
self.follow_mouse.is_active = activate
def activate_func(flag = True):
"""
概要: マウス追従機能をアクティブ/非アクティブにする。
引数:
:param flag: bool, Trueでアクティブ、Falseで非アクティブ。
:type flag: bool
"""
self.follow_mouse.is_active = flag
self.follow_mouse.activate = activate_func
def on_mouse_move_default(event):
"""
概要: マウス移動イベントハンドラ。追従マーカー/線の位置を更新。
"""
if not self.follow_mouse.is_active:
return
x = event.xdata
y = event.ydata
for ia, ax in enumerate(self.follow_mouse.axes):
if event.inaxes == ax:
# ln_1.set_data([x], [y])
if self.follow_mouse.marker[ia] is None:
self.follow_mouse.vlines[ia].set_xdata([x])
self.follow_mouse.hlines[ia].set_ydata([y])
else:
# markerがある場合はplotオブジェクトなのでset_dataはx, yのリストを受け取る
self.follow_mouse.vlines[ia].set_data([x], [y])
if self.follow_mouse.gco is not None:
self.follow_mouse.gco.set_data(x, y)
plt.draw()
self.follow_mouse.axes = []
self.follow_mouse.marker = []
self.follow_mouse.vlines = []
self.follow_mouse.hlines = []
def add(ax, line = 'dashed', width = 0.5, marker = None, size = 3.0, color = 'red'):
"""
概要: マウス追従機能のための補助線またはマーカーをAxesに追加する。
引数:
:param ax: matplotlib.axes.Axes, 補助線/マーカーを追加するAxesオブジェクト。
:type ax: matplotlib.axes.Axes
:param line: str, 補助線の線種(markerがNoneの場合)。
:type line: str
:param width: float, 補助線の線幅(markerがNoneの場合)。
:type width: float
:param marker: str, マーカーのスタイル(指定するとマーカー表示になる)。
:type marker: str or None
:param size: float, マーカーのサイズ。
:type size: float
:param color: str, 補助線/マーカーの色。
:type color: str
"""
self.follow_mouse.axes.append(ax)
self.follow_mouse.marker.append(marker)
if marker is None:
vline = ax.axvline(0, linestyle = line, linewidth = width, color = color)
hline = ax.axhline(0, linestyle = line, linewidth = width, color = color)
self.follow_mouse.vlines.append(vline)
self.follow_mouse.hlines.append(hline)
else:
ln, = ax.plot([], [], marker, markersize = size, markeredgecolor = color, markerfacecolor = color)
self.follow_mouse.vlines.append(ln) # vlinesにマーカーオブジェクトを格納
self.follow_mouse.hlines.append(None) # hlinesは使わない
self.follow_mouse.add = add
if on_mouse_move is None:
on_mouse_move = on_mouse_move_default
fig.canvas.mpl_connect('motion_notify_event', on_mouse_move)
[ドキュメント]
def register_event(self, fig, event = "button_press_event", callback = None):
"""
概要: 指定されたMatplotlibイベントにコールバック関数を登録する汎用メソッド。
詳細説明:
MatplotlibのFigureCanvasから特定のイベントを捕捉し、ユーザー定義の関数を呼び出します。
これにより、様々な種類のインタラクティブなイベントハンドリングを柔軟に設定できます。
引数:
:param fig: matplotlib.figure.Figure, イベントを登録するMatplotlibのFigureオブジェクト。
:type fig: matplotlib.figure.Figure
:param event: str, 登録するイベントの種類(例: "button_press_event", "key_press_event")。
:type event: str
:param callback: callable, イベント発生時に呼び出される関数。指定しない場合はデフォルトのハンドラが使用される。
:type callback: callable or None
"""
self.fig = fig
if callback is None:
callback = lambda event: self.onclick(event)
fig.canvas.mpl_connect(event, callback)
# fig_contour.canvas.mpl_connect("button_press_event", onclick)
[ドキュメント]
def register_click(self, fig, event = "button_press_event", callback = None):
"""
概要: マウスクリックイベントにコールバック関数を登録する。
詳細説明:
register_eventを使用して、デフォルトでonclickメソッドを呼び出します。
カスタムのコールバック関数を指定することも可能です。
引数:
:param fig: matplotlib.figure.Figure, イベントを登録するMatplotlibのFigureオブジェクト。
:type fig: matplotlib.figure.Figure
:param event: str, 登録するイベントの種類(デフォルトは"button_press_event")。
:type event: str
:param callback: callable, イベント発生時に呼び出されるカスタム関数(オプション)。
:type callback: callable or None
"""
if callback is None:
callback = lambda event: self.onclick(event)
self.register_event(fig, event = event, callback = callback)
[ドキュメント]
def register_pick(self, fig, event = "pick_event", callback = None):
"""
概要: ピックイベントにコールバック関数を登録する。
詳細説明:
register_eventを使用して、デフォルトでonpickメソッドを呼び出します。
カスタムのコールバック関数を指定することも可能です。
引数:
:param fig: matplotlib.figure.Figure, イベントを登録するMatplotlibのFigureオブジェクト。
:type fig: matplotlib.figure.Figure
:param event: str, 登録するイベントの種類(デフォルトは"pick_event")。
:type event: str
:param callback: callable, イベント発生時に呼び出されるカスタム関数(オプション)。
:type callback: callable or None
"""
if callback is None:
callback = lambda event: self.onpick(event)
self.register_event(fig, event = event, callback = callback)
[ドキュメント]
def register_redraw(self, fig, event = "draw_event", callback = None):
"""
概要: 再描画イベントにコールバック関数を登録する。
詳細説明:
register_eventを使用して、デフォルトでondrawメソッドを呼び出します。
カスタムのコールバック関数を指定することも可能です。
引数:
:param fig: matplotlib.figure.Figure, イベントを登録するMatplotlibのFigureオブジェクト。
:type fig: matplotlib.figure.Figure
:param event: str, 登録するイベントの種類(デフォルトは"draw_event")。
:type event: str
:param callback: callable, イベント発生時に呼び出されるカスタム関数(オプション)。
:type callback: callable or None
"""
if callback is None:
callback = lambda event: self.ondraw(event)
self.register_event(fig, event = event, callback = callback)
[ドキュメント]
def register_key(self, fig, event = "key_press_event", callback = None):
"""
概要: キーボードプレスイベントにコールバック関数を登録する。
詳細説明:
register_eventを使用して、デフォルトでonkeyメソッドを呼び出します。
カスタムのコールバック関数を指定することも可能です。
引数:
:param fig: matplotlib.figure.Figure, イベントを登録するMatplotlibのFigureオブジェクト。
:type fig: matplotlib.figure.Figure
:param event: str, 登録するイベントの種類(デフォルトは"key_press_event")。
:type event: str
:param callback: callable, イベント発生時に呼び出されるカスタム関数(オプション)。
:type callback: callable or None
"""
if callback is None:
callback = lambda event: self.onkey(event)
self.register_event(fig, event = event, callback = callback)
[ドキュメント]
def onkey(self, event):
"""
概要: キーボードプレスイベントのデフォルトハンドラ。
詳細説明:
'q'キーが押されたときに、関連するFigureを閉じます。
引数:
:param event: matplotlib.backend_bases.KeyEvent, Matplotlibのキーイベントオブジェクト。
:type event: matplotlib.backend_bases.KeyEvent
"""
# print('you pressed', event.key, event.xdata, event.ydata)
if event.key == 'q':
plt.close(event.canvas.figure)
sys.stdout.flush()
[ドキュメント]
def onclick(self, event):
"""
概要: マウスクリックイベントのデフォルトハンドラ。
詳細説明:
クリックされた位置に最も近いデータ点を見つけ、その情報をコンソールに表示します。
find_target_axesとfind_nearest_dataを使用してデータ点を特定し、
display_dataで情報を整形して出力します。
引数:
:param event: matplotlib.backend_bases.MouseEvent, Matplotlibのマウスイベントオブジェクト。
:type event: matplotlib.backend_bases.MouseEvent
"""
# print("click", event.inaxes)
itargets, hit_axisinfs = self.find_target_axes(event)
if itargets is None:
print("Warning in tkplotevent.onclick(): Can not find axes")
return
iinf, idata, idx, r2 = self.find_nearest_data(event.xdata, event.ydata, itargets, hit_axisinfs)
self.display_data(iinf, idata, idx, self.axis_inf[iinf])
"""
#selected_point = None
def onclick2(self, event):
global selected_point # クリックイベント内で変数を変更するために必要です
recover = False
if event.inaxes == ax:
x_click, y_click = event.xdata, event.ydata
# print("clicked")
for i in range(len(x)):
if abs(x[i] - x_click) < 0.1 and abs(y[i] - y_click) < 0.1:
print("hit")
# クリックしたデータポイントを太い線に変更します
line.set_marker('o')
line.set_linestyle('-')
line.set_markersize(8)
line.set_linewidth(1)
line.set_color('red')
selected_point = line # 選択されたデータポイントを記憶します
fig.canvas.draw()
return
else:
recover = True
# 新しい場所をクリックした場合、以前の選択を元に戻す
if recover and selected_point:
selected_point.set_marker('o')
selected_point.set_linestyle('-')
selected_point.set_markersize(8)
selected_point.set_linewidth(1)
selected_point.set_color('blue')
selected_point = None
fig.canvas.draw()
"""
[ドキュメント]
def onpick(self, event):
"""
概要: ピックイベントのデフォルトハンドラ。
詳細説明:
クリックされたアーティスト(線、テキストなど)のラベルをコンソールに表示します。
event.artistがget_textまたはget_labelメソッドを持つ場合にのみ処理を行います。
引数:
:param event: matplotlib.backend_bases.PickEvent, Matplotlibのピックイベントオブジェクト。
:type event: matplotlib.backend_bases.PickEvent
"""
if hasattr(event.artist, 'get_text'):
clicked_label = event.artist.get_text()
elif hasattr(event.artist, 'get_label'):
clicked_label = event.artist.get_label()
else:
return
print(f"Data [{clicked_label}] is clicked")
[ドキュメント]
def ondraw(self, event):
"""
概要: 再描画イベントのデフォルトハンドラ。
詳細説明:
プロットが再描画されたときにメッセージを出力します。
引数:
:param event: matplotlib.backend_bases.DrawEvent, Matplotlibの描画イベントオブジェクト。
:type event: matplotlib.backend_bases.DrawEvent
"""
print("draw")
"""
# legend.set_picker(True)
for text in legend.get_texts():
text.set_picker(True)
# fig.canvas.draw()
# plt.pause(1.0e-5)
# fig.canvas.mpl_connect('pick_event', on_legend_label_click)
for text in legend.get_texts():
text.set_picker(10)
for line in legend.get_lines():
line.set_picker(10)
line.set_pickradius(5) # クリックを検出する半径を設定
"""
# マウスクリック時に最近接データを検索
[ドキュメント]
def find_nearest_data(self, x, y, itarget_list, hit_axisinf_list):
"""
概要: 指定された座標に最も近いデータ点を見つける。
詳細説明:
複数のAxesとデータセットの中から、マウスクリック位置に最も近いデータ点を特定します。
距離計算は、self.distance ('r': 半径, 'x': X軸方向, 'y': Y軸方向) に基づいて行われます。
twinx/yなどで異なるスケールのAxesがある場合でも、座標変換を考慮して正しい距離を計算します。
引数:
:param x: float, マウスクリックのX座標 (イベント発生Axesの座標系)。
:type x: float
:param y: float, マウスクリックのY座標 (イベント発生Axesの座標系)。
:type y: float
:param itarget_list: list, 検索対象のself.axis_inf内のAxesのインデックスリスト。
:type itarget_list: list
:param hit_axisinf_list: list, 検索対象のAxes情報辞書 (axis_inf) のリスト。
:type hit_axisinf_list: list
戻り値:
:returns: tuple: (iinf_hit, idata_hit, ihit, minr2) - iinf_hit: int, 検出されたデータが属する
self.axis_infリストのインデックス。idata_hit: int, 検出されたデータが属するdataリストの
インデックス。ihit: int, 検出されたデータ点そのもののインデックス。minr2: float, 最小距離の二乗。
tuple: (None, None, None, None), 検出可能なデータ点が見つからない場合。
:rtype: tuple
"""
minr2 = 1.0e300
iinf_hit = None
idata_hit = None
ihit = None
for iaxis in range(len(hit_axisinf_list)):
hit_axisinf = hit_axisinf_list[iaxis]
axis = hit_axisinf['axis']
axis_scale = hit_axisinf.get('axis_scale', None)
data = hit_axisinf['data']
plot_type = hit_axisinf['plot_type']
xlim0 = axis.get_xlim()
ylim0 = axis.get_ylim()
xrange0 = xlim0[1] - xlim0[0]
yrange0 = ylim0[1] - ylim0[0]
if axis_scale is not None:
xlim = axis_scale.get_xlim()
ylim = axis_scale.get_ylim()
else:
xlim = axis.get_xlim()
ylim = axis.get_ylim()
if type(data) is not list:
data = [data]
xrange = xlim[1] - xlim[0]
yrange = ylim[1] - ylim[0]
if type(data) is not list:
data = [data]
# x,yは検出axisの座標で与えられる。検出axisとaxis_scaleが異なる場合、変換する必要がある
# print("axis=", iaxis, axis, axis_scale, axis is not axis_scale)
if axis_scale and axis is not axis_scale:
# print("x, y converted from ", xi, yi, " to ", end = '')
x0 = xlim[0] + xrange / xrange0 * (x - xlim0[0])
y0 = ylim[0] + yrange / yrange0 * (y - ylim0[0])
# print(x0, y0)
else:
x0 = x
y0 = y
for idata in range(len(data)):
xdata = data[idata].xdata
ydata = data[idata].ydata
# xdata = data[idata].get_xdata()
# ydata = data[idata].get_ydata()
for i in range(len(xdata)):
xi = xdata[i]
yi = ydata[i]
# print(f"i={i:4} xi={xi:10.3g} yi={yi:10.3g} x0={x0:10.3g} y0={y0:10.3g}")
if xi is None or yi is None:
continue
if self.distance == 'r':
dx = (x0 - xi) / xrange
dy = (y0 - yi) / yrange
r2 = dx * dx + dy * dy
elif self.distance == 'x':
dx = (x0 - xi) / xrange
r2 = dx * dx
elif self.distance == 'y':
dy = (y0 - yi) / yrange
r2 = dy * dy
if minr2 > r2:
minr2 = r2
# print("\n**iaxis is updated:", iaxis, itarget_list[iaxis])
iinf_hit = itarget_list[iaxis]
idata_hit = idata
ihit = i
# print(" => ", minr2, ihit)
if iinf_hit is None:
print("")
print(f"Error: Invalid type [{plot_type}] in find_nearest_data()")
return None, None, None, None
return iinf_hit, idata_hit, ihit, minr2
# 最近接データの情報をコンソールに表示
[ドキュメント]
def display_data(self, iinf, idata, idx, hit_axisinf = None):
"""
概要: 指定されたデータ点の情報をコンソールに表示する。
詳細説明:
find_nearest_dataによって見つかったデータ点の詳細(ラベル、座標、関連情報など)を整形し、
コンソールに出力します。xlistやxlabelsが存在する場合、追加のデータ属性も表示します。
引数:
:param iinf: int, 検出されたデータが属するself.axis_infリストのインデックス。
:type iinf: int
:param idata: int, 検出されたデータが属するdataリストのインデックス。
:type idata: int
:param idx: int, 検出されたデータ点そのもののインデックス。
:type idx: int
:param hit_axisinf: dict, 検出されたデータ点を含むaxis_inf辞書(オプション)。指定しない場合はself.axis_inf[iinf]が使用される。
:type hit_axisinf: dict or None
"""
if hit_axisinf is None:
hit_axisinf = self.axis_inf[iinf]
plot_type = hit_axisinf['plot_type']
label = hit_axisinf['label']
axis = hit_axisinf['axis']
xlabel = axis.xlabel
ylabel = axis.ylabel
data = hit_axisinf['data']
if type(data) is list:
data = data[0]
print("")
if plot_type == '2D' or plot_type == 'plot' or plot_type == 'scatter':
xdata = data.xdata
ydata = data.ydata
xlist = hit_axisinf.get('xlist', None)
xlabels = hit_axisinf.get('xlabels', None)
# print("xlist=", xlist, xlabels)
if xlist is None or xlabels is None:
print(f"clicked for the data [{label}] in the {xlabel} - {ylabel} plot (inf #{iinf} idata #{idata} data index #{idx})")
print(f" #{idx:4}: {xlabel:>12}: {xdata[idx]:<12.4g} {ylabel:>12}: {ydata[idx]:<12.4g}")
else:
print(f"clicked for the data [{label}] in the {xlabel} - {ylabel} plot (inf #{iinf} data index #{idx})")
print(f" #{idx:4}: {xlabel:>5}: {xdata[idx]:5} {ylabel:>12}: {ydata[idx]:<12.4g}")
nx = len(xlabels)
for i in range(nx):
print(f" {i}: ", end = '')
if type(xlist[i][idx]) is int:
print(f"{xlabels[i]:>10}: {xlist[i][idx]:<10d} ")
elif type(xlist[i][idx]) is float or isinstance(xlist[i][idx], np.float64):
print(f"{xlabels[i]:>10}: {xlist[i][idx]:<10.4g} ")
else:
print(f"{xlabels[i]:>10}: {xlist[i][idx]:<10} ")