はじめに
Matplotlib の 設定を直接書き散らすといたるところに分散して再利用することが難しくコードの管理が大変になります。
今回、私は Matplotlib の設定を オブジェクト指向でカプセル化し、再利用可能で、保守しやすい構成にまとめました。
本記事は第3弾で、ホバーの設定方法をまとめます。
なお、前回は、ベースの設定や軸の設定を行ったのでそちらも興味ある方はご覧ください
本記事のゴール
- Matplotlibのホバー設定を1ヶ所に集約して管理する
🎨 実装コード(完全版)
from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import Callable, Optional
from matplotlib.axes import Axes
from matplotlib.backend_bases import MouseEvent
from matplotlib.figure import Figure
from src.domain.station_params import Period
class HoverConfigurator:
"""
Plot 側で用いるためのラッパークラス
"""
def __init__(self, fig: Figure, ax: Axes, period: Period):
self.fig = fig
self.ax = ax
self.period = period
def apply(self):
# カーソル変換ロジック
x_conv = lambda x: self.period.start + timedelta(minutes=int(x))
y_conv = float
cursor = CursorConverter(x_conv, y_conv)
hover_text = HoverText(self.fig, self.ax, cursor)
self.fig.canvas.mpl_connect("motion_notify_event", hover_text.on_hover)
@dataclass(frozen=True)
class CursorData:
dt: datetime
value: float
class CursorConverter:
"""
Matplotlib のカーソルイベント(xdata, ydata)を
ドメインデータ(CursorData)に変換する責務を持つ。
"""
def __init__(
self,
x_converter: Callable[[float], datetime],
y_converter: Callable[[float], float],
):
self.x_converter = x_converter
self.y_converter = y_converter
def convert(self, event: MouseEvent) -> Optional[CursorData]:
"""イベントから CursorData を生成する。無効なイベントは None。"""
if event.inaxes is None or event.xdata is None or event.ydata is None:
return None
return CursorData(
dt=self.x_converter(event.xdata),
value=self.y_converter(event.ydata),
)
class HoverText:
INFO_TEXT_X = 0.01
INFO_TEXT_Y = 0.98
LABEL_FONT_SIZE = 12
DATE_FORMAT = "%Y/%m/%d %H:%M"
TEXT_TEMPLATE = "Date: {ts} | Value: {value:.2f} nT"
def __init__(self, fig: Figure, ax: Axes, cursor_converter: CursorConverter):
self.fig = fig
self.ax = ax
self._cursor_converter = cursor_converter
self._info_text = self.ax.text(
self.INFO_TEXT_X,
self.INFO_TEXT_Y,
"",
transform=self.ax.transAxes,
va="top",
fontsize=self.LABEL_FONT_SIZE,
)
def on_hover(self, event) -> None:
"""ホバーイベント時に呼び出され、情報を更新する."""
cursor = self._cursor_converter.convert(event)
if cursor is not None:
self._update_info(cursor)
def _update_info(self, cursor: CursorData) -> None:
ts = cursor.dt.strftime(self.DATE_FORMAT)
text = self.TEXT_TEMPLATE.format(ts=ts, value=cursor.value)
self._info_text.set_text(text)
self.fig.canvas.draw_idle()
なお、筆者は横軸に期間のデータクラスを定義して管理しています。ここは任意の横軸を用いてください。
呼び出し方法
プロット前に以下を 1 行書くだけで設定が適用されます。
self.fig, self.ax = plt.subplots()
#ホバーの適応
HoverConfigurator(self.fig, self.ax, self.lt_period).apply()
さいごに
本記事ではオブジェクト指向でホバーの設定方法を紹介しました。
次回はこれまで作成した機能を用いて全体のコードを実装していきます。
興味があったら教えて下さい。