CUBE SUGAR CONTAINER

技術系のこと書きます。

Python: Kivy で Matplotlib のグラフをプロットする

Kivy は最近人気のある Python のクロスプラットフォームな GUI のフレームワーク。 今回はそんな Kivy で作った GUI 上に Matplotlib のグラフをプロットしてみる。

使った環境は次の通り。

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.14.5
BuildVersion:   18F132
$ python -V                    
Python 3.7.3

下準備

まずは Kivy と Matplotlib をインストールしておく。

$ pip install kivy matplotlib numpy

続いて Kivy Garden を使って Matplotlib 用のプラグイン (garden.matplotlib) をインストールする。

$ garden install matplotlib

これで Kivy で Matplotlib を使う準備ができた。

Kivy で Matplotlib のグラフをプロットする

以下の Kivy で Matplotlib のグラフをプロットするサンプルコードを示す。 garden.matplotlib を使うと Figure#canvas のインスタンスをウィジェットとして追加できるようになる。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
# Kivy 上で Matplotlib を使うために必要な準備
matplotlib.use('module://kivy.garden.matplotlib.backend_kivy')


class GraphApp(App):
    """Matplotlib のグラフを表示するアプリケーション"""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.title = 'Matplotlib graph on Kivy'

    def build(self):
        # メインの画面
        main_screen = BoxLayout()
        main_screen.orientation = 'vertical'

        # 上部にラベルを追加しておく
        label_text = 'The following is a graph of Matplotlib'
        label = Label(text=label_text)
        label.size_hint_y = 0.2
        main_screen.add_widget(label)

        # サイン波のデータを用意する
        x = np.linspace(-np.pi, np.pi, 100)
        y = np.sin(x)
        # 描画する領域を用意する
        fig, ax = plt.subplots()
        # プロットする
        ax.plot(x, y)
        # Figure#canvas をウィジェットとして追加する
        main_screen.add_widget(fig.canvas)

        return main_screen


def main():
    # アプリケーションを開始する
    app = GraphApp()
    app.run()


if __name__ == '__main__':
    main()

上記を実行する。

$ python kvplot.py

すると、次のような結果が得られる。

f:id:momijiame:20190710212812p:plain

ちゃんと描画できてるね。

FigureCanvasKivyAgg を使う場合

別のやり方として Figure オブジェクトを FigureCanvasKivyAgg でラップするやり方もある。

サンプルコードは次の通り。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.garden.matplotlib.backend_kivyagg import FigureCanvasKivyAgg
from kivy.uix.label import Label
import numpy as np
import matplotlib.pyplot as plt


class GraphApp(App):
    """Matplotlib のグラフを表示するアプリケーション"""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.title = 'Matplotlib graph on Kivy'

    def build(self):
        main_screen = BoxLayout()
        main_screen.orientation = 'vertical'

        label_text = 'The following is a graph of Matplotlib'
        label = Label(text=label_text)
        label.size_hint_y = 0.2
        main_screen.add_widget(label)

        x = np.linspace(-np.pi, np.pi, 100)
        y = np.sin(x)
        fig, ax = plt.subplots()
        ax.plot(x, y)

        # Figure をラップする
        widget = FigureCanvasKivyAgg(fig)
        # ウィジェットとして追加する
        main_screen.add_widget(widget)

        return main_screen


def main():
    app = GraphApp()
    app.run()


if __name__ == '__main__':
    main()

表示される内容は変わらない。

ウィジェットのクラスとして定義する

さらに、ウィジェットをクラスとして定義した上で、その中にグラフを埋め込む場合には、次のようにする。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.garden.matplotlib.backend_kivyagg import FigureCanvasKivyAgg
import numpy as np
import matplotlib.pyplot as plt


class GraphView(BoxLayout):
    """Matplotlib のグラフを表示するためのウィジェット"""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        x = np.linspace(-np.pi, np.pi, 100)
        y = np.sin(x)
        fig, ax = plt.subplots()
        ax.plot(x, y)

        widget = FigureCanvasKivyAgg(fig)
        self.add_widget(widget)


class GraphApp(App):
    """Matplotlib のグラフを表示するアプリケーション"""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.title = 'Matplotlib graph on Kivy'

    def build(self):
        main_screen = BoxLayout()
        main_screen.orientation = 'vertical'

        label_text = 'The following is a graph of Matplotlib'
        label = Label(text=label_text)
        label.size_hint_y = 0.2
        main_screen.add_widget(label)

        # ウィジェットを生成して追加する
        graph = GraphView()
        main_screen.add_widget(graph)

        return main_screen


def main():
    # アプリケーションを開始する
    app = GraphApp()
    app.run()


if __name__ == '__main__':
    main()

こちらも表示される内容は変わらない。

KV 言語を使う

Kivy は KV 言語という DSL でレイアウトを制御できる。 もし、KV 言語も併用したいという場合であれば次のようにする。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.lang import Builder
from kivy.garden.matplotlib.backend_kivyagg import FigureCanvasKivyAgg
import numpy as np
import matplotlib.pyplot as plt


kv_def = '''
<RootWidget>:
    orientation: 'vertical'

    Label:
        text: 'The following is a graph of Matplotlib'
        size_hint_y: 0.2

    GraphView:

<GraphView>:
'''
Builder.load_string(kv_def)


class GraphView(BoxLayout):
    """Matplotlib のグラフを表示するためのウィジェット"""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        x = np.linspace(-np.pi, np.pi, 100)
        y = np.sin(x)
        fig, ax = plt.subplots()
        ax.plot(x, y)

        widget = FigureCanvasKivyAgg(fig)
        self.add_widget(widget)


class RootWidget(BoxLayout):
    """子を追加していくためのウィジェットを用意しておく"""


class GraphApp(App):
    """Matplotlib のグラフを表示するアプリケーション"""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.title = 'Matplotlib graph on Kivy'

    def build(self):
        return RootWidget()


def main():
    # アプリケーションを開始する
    app = GraphApp()
    app.run()


if __name__ == '__main__':
    main()

こちらも表示される内容は変わらない。

グラフで表示される内容を動的に更新したい

グラフに表示される内容を動的に更新したい場合のサンプルも以下に示す。 基本的には普通に Matplotlib を使って動的なグラフを描くのと変わらない。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
from kivy.lang import Builder
from kivy.garden.matplotlib.backend_kivyagg import FigureCanvasKivyAgg
import numpy as np
import matplotlib.pyplot as plt


kv_def = '''
<RootWidget>:
    orientation: 'vertical'

    Label:
        text: 'The following is a graph of Matplotlib'
        size_hint_y: 0.2

    GraphView:

<GraphView>:
'''
Builder.load_string(kv_def)


class GraphView(BoxLayout):
    """Matplotlib のグラフを表示するためのウィジェット"""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        # 初期化に用いるデータ
        x = np.linspace(-np.pi, np.pi, 100)
        y = np.sin(x)
        # 描画状態を保存するためのカウンタ
        self.counter = 0

        # Figure, Axis を保存しておく
        self.fig, self.ax = plt.subplots()
        # 最初に描画したときの Line も保存しておく
        self.line, = self.ax.plot(x, y)

        # ウィジェットとしてグラフを追加する
        widget = FigureCanvasKivyAgg(self.fig)
        self.add_widget(widget)

        # 1 秒ごとに表示を更新するタイマーを仕掛ける
        Clock.schedule_interval(self.update_view, 0.01)

    def update_view(self, *args, **kwargs):
        # データを更新する
        self.counter += np.pi / 100  # 10 分の pi ずらす
        # ずらした値を使ってデータを作る
        x = np.linspace(-np.pi + self.counter,
                        np.pi + self.counter,
                        100)
        y = np.sin(x)
        # Line にデータを設定する
        self.line.set_data(x, y)
        # グラフの見栄えを調整する
        self.ax.relim()
        self.ax.autoscale_view()
        # 再描画する
        self.fig.canvas.draw()
        self.fig.canvas.flush_events()


class RootWidget(BoxLayout):
    """子を追加していくためのウィジェットを用意しておく"""


class GraphApp(App):
    """Matplotlib のグラフを表示するアプリケーション"""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.title = 'Matplotlib graph on Kivy'

    def build(self):
        return RootWidget()


def main():
    # アプリケーションを開始する
    app = GraphApp()
    app.run()


if __name__ == '__main__':
    main()

実行すると、こんな表示が得られる。 うにょうにょ。

f:id:momijiame:20190714123106g:plain

いじょう。