0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

オブジェクト指向で整理する Matplotlib ― ホバー設定編

0
Posted at

はじめに

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()

さいごに

本記事ではオブジェクト指向でホバーの設定方法を紹介しました。
次回はこれまで作成した機能を用いて全体のコードを実装していきます。
興味があったら教えて下さい。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?