tklib.tkgraphic.tkplotevent のソースコード

"""
概要: 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 button_click(self, e): """ 概要: ボタンクリックイベントのデフォルトハンドラ。 詳細説明: stop_flagをTrueに設定し、外部のループなどの処理を停止させることを意図します。 引数: :param e: matplotlib.backend_bases.MouseEvent, イベントオブジェクト。 :type e: matplotlib.backend_bases.MouseEvent """ self.stop_flag = True
[ドキュメント] def finalize_button(self, text = 'finished'): """ 概要: 停止ボタンのテキストを更新する。 詳細説明: 主に処理完了後にボタンの表示を変更するために使用されます。 このメソッドが呼び出される前に、add_buttonまたはadd_stop_buttonでボタンが追加されている必要があります。 引数: :param text: str, ボタンに表示する新しいテキスト。 :type text: str """ self.button.label.set_text(text)
[ドキュメント] def add_button(self, button_region = [0.15, 0.95, 0.10, 0.03], plot_region = [0.92, 0.15], text = 'stop', color = '#f8e58c', hovercolor = '#38b48b', callback = None): """ 概要: MatplotlibのFigureにボタンを追加する。 詳細説明: 指定された領域にボタンを作成し、クリックされたときのコールバック関数を設定します。 プロット領域を調整し、ボタンが重ならないように配置します。 引数: :param button_region: list, ボタンを配置するAxesの領域を[left, bottom, width, height]の形式で指定。単位はFigureの割合。 :type button_region: list :param plot_region: list, ボタン追加後のプロット領域調整のための領域を[top, bottom]の形式で指定。単位はFigureの割合。 :type plot_region: list :param text: str, ボタンに表示するテキスト。 :type text: str :param color: str, ボタンの背景色 (CSSカラー名またはHEXコード)。 :type color: str :param hovercolor: str, マウスオーバー時のボタンの背景色。 :type hovercolor: str :param callback: callable, ボタンがクリックされたときに呼び出される関数。指定しない場合はbutton_clickが使われる。 :type callback: callable 戻り値: :returns: matplotlib.widgets.Button, 作成されたMatplotlibのButtonオブジェクト。 :rtype: matplotlib.widgets.Button """ if callback is None: callback = self.button_click self.button_region = button_region self.plot_region = plot_region self.color = color self.hovercolor = hovercolor self.callback = callback self.ax_button = plt.axes(button_region) self.button = wg.Button(self.ax_button, text, color = color, hovercolor = hovercolor) self.button.on_clicked(callback) return self.button
[ドキュメント] 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_stop_button(self, button_region = [0.15, 0.95, 0.10, 0.03], plot_region = [0.92, 0.15], label = 'stop', color = '#f8e58c', hovercolor = '#38b48b', on_stop_clicked = None): """ 概要: プロットに停止ボタンを追加する。 詳細説明: ユーザーが処理を中断できるように、指定された領域にボタンを配置します。 ボタンは自身の状態を持ち、テキストを変更する機能も持ちます。 tkStopButtonクラスのインスタンスがself.stop_buttonに格納されます。 引数: :param button_region: list, ボタンを配置するAxesの領域を[left, bottom, width, height]の形式で指定。単位はFigureの割合。 :type button_region: list :param plot_region: list, ボタン追加後のプロット領域調整のための領域を[top, bottom]の形式で指定。単位はFigureの割合。 :type plot_region: list :param label: str, ボタンに表示するテキスト。 :type label: str :param color: str, ボタンの背景色。 :type color: str :param hovercolor: str, マウスオーバー時のボタンの背景色。 :type hovercolor: str :param on_stop_clicked: callable, ボタンがクリックされたときに呼び出されるカスタムコールバック関数。指定しない場合はデフォルトのon_clickが使われる。 :type on_stop_clicked: callable """ def on_click(e): self.stop_button.status = 'stop' self.stop_button.set_text("terminating") print("\ntkplotevent.add_stop_button.on_click(): Stop button was pressed. Terminating...\n") def set_text(label = None): if label is not None: self.stop_button.button.label.set_text(label) def adjust_plot(plot_region = None): if plot_region is None: plot_region = self.stop_button.plot_region plt.subplots_adjust(top = plot_region[0], bottom = plot_region[1]) class tkStopButton(): """ 概要: 停止ボタンの内部管理クラス。 詳細説明: 停止ボタンの状態管理とテキスト更新機能を提供します。 """ def __init__(self, plt, button_region, plot_region, on_stop_clicked): self.plt = plt self.__status = 'running' self.button_region = button_region self.plot_region = plot_region self.on_click = on_click self.set_text = set_text self.adjust_plot = adjust_plot self.ax = self.plt.axes(button_region) self.button = wg.Button(self.ax, label, color = color, hovercolor = hovercolor) if on_stop_clicked is None: self.button.on_clicked(self.on_click) else: self.button.on_clicked(on_stop_clicked) def set_text(self, str): """ 概要: ボタンのテキストを設定する。 引数: :param str: str, 設定するテキスト。 :type str: str """ self.button.label.set_text(str) @property def status(self): """ 概要: ボタンの現在の状態を取得するプロパティ。 戻り値: :returns: str, ボタンの状態 ('running' または 'stop')。 :rtype: str """ return self.__status @status.setter def status(self, f): """ 概要: ボタンの現在の状態を設定するプロパティ。 引数: :param f: str, 設定する状態。 :type f: str """ self.__status = f self.stop_button = tkStopButton(self.plt, button_region, plot_region, on_stop_clicked)
[ドキュメント] 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_popup_menu(self, fig, parent = None): """ 概要: ポップアップメニューの準備を行う。 詳細説明: TkinterのMenuウィジェットを作成し、これをself.popup_menu.menuに格納します。 メニュー項目を追加するためのヘルパー関数add_menuも提供されます。 parentが指定されない場合、非表示のルートTkinterウィンドウが作成されます。 引数: :param fig: matplotlib.figure.Figure, メニューを関連付けるMatplotlibのFigureオブジェクト。 :type fig: matplotlib.figure.Figure :param parent: tkinter.Tk or tkinter.Toplevel, Tkinterの親ウィジェット(オプション)。 :type parent: tkinter.Tk or tkinter.Toplevel or None """ self.popup_menu = tkParams() self.popup_menu.fig = fig if parent is None: self.popup_menu.parent = Tk() # Tkinterのルートウィンドウを作成(非表示) self.popup_menu.parent.withdraw() # ウィンドウを表示しない else: self.popup_menu.parent = parent self.popup_menu.menu = Menu(self.popup_menu.parent, tearoff = 0) def add_menu(label = "run", command = lambda: print("runが選択されました")): """ 概要: ポップアップメニューに項目を追加する。 引数: :param label: str, メニュー項目に表示するテキスト。 :type label: str :param command: callable, メニュー項目が選択されたときに実行される関数。 :type command: callable """ self.popup_menu.menu.add_command(label = label, command = command) self.popup_menu.add_menu = add_menu
[ドキュメント] def register_popup_menu_event(self, on_click = None): """ 概要: 右クリックイベントにポップアップメニューを表示する機能を登録する。 詳細説明: Matplotlibの'button_press_event'を監視し、イベントが右クリック(ボタン3)の場合に、 tk_popupメソッドを使ってTkinterのポップアップメニューをマウスカーソルの位置に表示します。 カスタムのon_clickハンドラを指定することも可能です。 引数: :param on_click: callable, カスタムのクリックイベントハンドラ(オプション)。デフォルトは右クリックでメニュー表示。 :type on_click: callable or None """ def on_click_default(event): if event.button == 3: # 右クリック(button==3) self.popup_menu.menu.tk_popup(event.guiEvent.x_root, event.guiEvent.y_root) if on_click is None: on_click = on_click_default self.popup_menu.fig.canvas.mpl_connect('button_press_event', on_click)
[ドキュメント] 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} ")