kivyはモバイルにも対応しているクロスプラットフォームGUIアプリ開発ライブラリです。科学技術関係のアプリを作る際、matplotlibによるプロットを利用したくなりますが、今回は筆者がインストールの時点でハマってしまったので解決方法を備忘録として残しておきます。
こんにちは。wat(@watlablog)です。ここではkivyでmatplotlibを使う時に必要なgarden関連の困りごと解決例とkivy上でmatplotlibを使うコード例を紹介します!
問題の背景と詳細
筆者が直面した問題は、結果的にそんなに難しい問題ではなかったことがわかりました。
しかし同じような問題によりkivyでmatplotlibを使うのを諦めてしまう人が少なくなるように、環境や問題発生の詳細、試行錯誤したけど無意味だった内容も含めて記載しようと思います。
環境
今回問題が発生した時の筆者のPC環境やライブラリバージョン状況を以下に示します。
(kivy_garden.graphは今回の件とはあまり関係がありませんが、試行錯誤中に「pip3 install kivy_garden.graph --extra-index-url https://kivy-garden.github.io/simple/」コマンドで入れました。→このコマンドを知ったきっかけ:https://www.reddit.com/r/kivy/comments/ssotuv/no_module_named_garden_main_garden_is_a_package/)
Mac | OS | macOS Catalina 10.15.7 |
---|---|---|
CPU | 1.4[GHz] | |
メモリ | 8[GB] |
Python | Python 3.9.6 |
---|---|
PyCharm (IDE) | PyCharm CE 2020.1 |
matplotlib | 3.4.3 |
kivy | 2.1.0 |
Kivy-Garden | 0.1.5 |
kivy-garden.graph | 0.4.0 |
kivyでmatplotlibを使いたい
matplotlibはこのWATLABブログで切っても切り離せないほど活用しているPythonの外部ライブラリです。
測定やシミュレーション結果を必ずmatplotlibで出力していたので、「Python/kivyでGUIアプリ開発!基本の書き方を学ぶ」の記事でkivyに入門した次のステップでmatplotlib on kivyを実装しようとしていました。
チュートリアル動画を視聴
kivyでmatplotlibを使うためにネットを検索した結果、以下のYouTube動画が最もシンプルでわかりやすいと感じましたので、この動画をベースに進めようと考えました。
動画における手順は以下のとおりです。
- ①pip install kivy_garden
- ②garden install matplotlib
- ③matplotlibのバージョンを指定してインストール
- ④pyコードとkvコードを書く
- ⑤イベントを設定する
上記手順の①は先ほどの記事(kivy入門の記事)でfullバージョンをインストールしていたため、既に筆者環境にはkivy_gardenがインストールされていました。問題は②で発生しました。
ちなみにmatplotlibのバージョンは関係なく、動画のバージョン以上でも正常動作しています。
garden install matplotlibができない
問題:permission denied: gardenが出る
手順②で「garden install matplotlib」を行うと、「zsh: permission denied: garden」という文章が表示されて先に進まないというのが今回の問題です。
zshというのはZ Shell(ズィー・シェルと読む)を意味しており、筆者のMac環境で駆動しているシェルのことです。シェルはコマンドを介してコンピュータに作業をさせるツールなので、今回の問題(エラーではない)はネット関係でなく最初の指示段階で発生したものと推測しました。
permission deniedは直訳すると権限が拒否されたということなので、どうやらgardenコマンドを実行しようとしたら権限がなくて進めなかったというのが問題の本質のようです。
この辺、本職のITエンジニアならワケない問題なのでしょうが、ITとはほとんど無縁の機械エンジニアである筆者にとってはわけわかめ状態でした。そのため一つずつ論理的に攻めるしかありません…。
試行:sudoコマンドでもダメ
permission deniedの解決方法を調べると、sudoで無理やり権限を高めてインストールする方法が紹介されていました(そんなノリで良いの??と思いましたが)。
しかし、sudoコマンドを使っても、メッセージが「command not found」と変わり別問題で進みませんでした。
試行:GitHubからインストールしようとしてもダメ
今回インストールしたいgarden.matplotlibはどうやら以下のURLにあるようです。
https://github.com/kivy-garden/garden.matplotlib
「【Python】GitHubから直接パッケージをインストールする方法」を参考にpipやgardenを試してみましたが、これも今回は不発。ただこの方法は今後役立ちそうと思ったのでリンクをメモしておきます。
解決方法の例
ヒントとなったQA
それではようやく筆者の解決方法を解説します。もしかしたらもっと良い方法もあると思いますが、今回ヒントとなったのは上で紹介もしたこのリンク先QAでした。
ページの中腹をよく見ると、gardenまでのパス(path to garden)を指定すればうまくいくとの記述がありました。
Here's what you'll need to do (assuming you are running Windows):
https://www.reddit.com/r/kivy/comments/ssotuv/no_module_named_garden_main_garden_is_a_package/
Run your console command (cmd
, the linux shell, whichever)
Find the path to garden / garden.bat. garden.bat just runsgarden
with whichever Python interpreter it finds, so let's just usegarden
.
Runpython3.9 <path to garden> install matplotlib
また、以下のリンクはWindows版の解決策です。garden.batの拡張子を.pyに変更してPythonとして実行するというものです。筆者の環境はWindowsでなくMacなのでこの方法は不発でしたが、この辺でようやくgardenコマンドがgardenもしくはgarden.batで実行されるということがわかりました(筆者のレベルはそこから…!)。
If I rename the
https://github.com/kivy-garden/garden/issues/35garden
file inscript
to garden.py and run afterC:\Program Files\Python38>python.exe Scripts\garden.py install graph
it works.
【Solved】garden install matplotlibを通す手順
①garden.batまでのパスを調査する
英語の質問サイトを読み漁っていた時に、gardenまでのパスというキーワードは何回も見ていましたが、ずっとkivy直下にあるgardenフォルダだと思っていました。
しかし、実際にFinderでkivyフォルダの中にあるgardenフォルダを確認したところ、筆者の環境でこの場所にgarden.batはありませんでした。
Finderで「garden.bat」を検索すると筆者の環境では「/usr/local/bin/garden.bat」にあることがわかりました。つまりgardenまでのパス<path to garden>は以下です。
1 |
/usr/local/bin/garden |
※Pythonやkivy_gardenのインストール環境によって変わると思います。
②gardenまでのパスを指定してmatplotlibをインストールする
先ほど調査したgardenまでのパスを使って、以下のコマンドをターミナルで実行します。
1 |
python3.9 /usr/local/bin/garden install matplotlib |
ここまででようやく当初問題であったpermission deniedの警告文が出ず、以下の文章が表示され正常にインストールできました。
「Done! garden.matplotlib is installed at: /Users/wat/.kivy/garden/garden.matplotlib」
③フォルダ移動と名称の変更
上記メッセージにはインストール先が書かれています。よく見ると筆者が普段PyCharm環境で外部ライブラリを参照している「site-package」の中にはありません。そのため先ほどインストールしたフォルダを丸ごと移動しなければなりません。
先ほどの操作でインストール先を指定したりできればもっとスマートかも知れませんが、今回は無理やりやりました。
ちなみに、普段pipでインストールしているフォルダは「pip show <package name>」で調べることができます。
筆者の環境では/Users/wat/.kivy/garden/にgarden.matplotlibというフォルダがインストールされたので、/usr/local/lib/python3.9/site-packages/kivy/garden/に移動し、さらにgarden.matplotlibをmatplotlibにフォルダ名称変更します。
import文からどこに置けば良いかの推測はつきますが、あまり詳しくなくてもIDE上でimport文を書いておき、フォルダの置き場や名称を試行錯誤してimportが通るようになることを確認するという方法もあります。
このようにしてチュートリアル動画に記載されているimport文が通ることを確認し、無事問題は解決されました。
ここで紹介した方法は初心者が無理やり解決した感じです。Pythonのインストールからかも知れませんが、もっとスマートなやり方をご存知であれば記事のコメント等で教えていただけるとありがたいです。
kivyでmatplotlibのプロットを表示するサンプルコード
コードの目標
問題も解決されたので、ようやく本来やりたかったmatplotlib on kivyを実装してみましょう。
といっても、先ほどのYouTube動画で紹介されていること+αの内容です。とは言え、WATLABブログでこだわっているmatplotlibの書式(論文でも使えるフォーマット)を踏襲した完全オリジナル版です。
この記事では以下の内容を目標とします。
プロットの見栄えをカッコよくする
主観は人それぞれですが、プロットの外観が論文に使えるようなカッコいい見た目をしているとテンションが上がります。
「Pythonのmatplotlibで論文に使えるグラフを描く!」や、最近では「Pythonで簡単にwavファイルのノイズキャンセルを行う方法」で描いたようなプロットをkivyで実現してみましょう。
↓こんな感じ。
今後も使える雛形のkvファイルとPythonクラスを作る
kivyで保守性良くコーディングするためには、kvファイルをうまく使う必要があります。matplotlibウィジェットの部分はPythonで書くのが良いと思いますが、その他のButtonやBoxLayoutといったレイアウト要素の使い方を学びます。
その中で、今後も小変更で色々なアプリに使えるように雛形のような書き方を目指します。具体的には以下のコンセプトで実装します。
- Pythonのクラスで初期プロットを定義する
- kvファイルでイベントのトリガーを検知する
- プロットに用いる計算結果はPythonで定義する
- プロットの更新部分を作成する
チュートリアル動画のようにPythonクラスの欄外で計算させるのではなく、クラスのメソッド内に書くスタイルを目指します。
ボタンで波形が更新されるコード
基本的な書き方は「Python/kivyでGUIアプリ開発!基本の書き方を学ぶ」と同じです。この記事の内容を理解したという前提で以下の解説をします。
.kvファイル
以下にkvファイルを示します。ファイル名は「my.kv」です。このファイル名はPythonファイル内のクラス名と関連していますので、名前は変更しないようにします。
BoxLayoutの中に、プロット専用のウィジェットとしてPlotWidgetを作成しています。今回はPythonファイル上で後からプロット用のウィジェットをadd_widget()する仕様であるため、先頭にグラフを入れたい時にレイアウトウィジェットが1つだと困るからです。
on_releaseイベントにはchildren[0]children[1](=PlotWidget)を使ってPythonクラスのPlotWidget内のメソッドを使えるようにしています(ここはウィジェットの数が変わると数値が変わるので、もっと良い方法がありそう…)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
#:kivy 2.1.0 Root: <Root> BoxLayout: orientation:'vertical' PlotWidget: size_hint_y:0.8 Button: id:button size_hint_y:0.2 text:'Button' on_release:root.children[0].children[1].on_button() <PlotWidget> |
.pyファイル
次にPythonコード(main.py)を示します。こちらのファイル名は任意です。
class PlotWidget内の「def __init__(self, **kwargs):」というメソッドはコンストラクタ(最初に実行されるメソッド)です。
matplotlibによるプロットは何回も使うので、コンストラクタ上ではなく関数として別途plot()を定義してあります。コンストラクタではこのplot()を呼び出して初期値を描画します。
ボタンがクリックされた時のイベントはon_button()で定義しています。ここでNumpyによるランダム計算をした後、波形を更新しています。
更新は若干乱暴ですが、remove_widget()を使ってウィジェットそのものを削除した後に再度add_widget()をしているというものです。
remove_widget()はウィジェットを引数としますが、今回はRoot側のイベントからPlotWidgetを削除したいのでchildren[0](=BoxLayoutの0番目のウィジェット…という表現で良いのかな?)を使っています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
from kivy.app import App from kivy.uix.boxlayout import BoxLayout from kivy.garden.matplotlib.backend_kivyagg import FigureCanvasKivyAgg import numpy as np from matplotlib import pyplot as plt class Root(BoxLayout): ''' matplotlibのfigウィジェットを追加するためのダミークラス ''' pass class PlotWidget(BoxLayout): ''' Pythonで計算をした結果をmatplotlibでプロットするクラス ''' def __init__(self, **kwargs): super().__init__(**kwargs) ''' 初期化時 ''' # 初期値 x = np.arange(0, 10, 1) y = np.zeros(len(x)) self.fig = self.plot(x, y) # ウィジェットとしてfigをラップする self.add_widget(FigureCanvasKivyAgg(self.fig)) def plot(self, x, y): ''' プロットする共通のメソッド ''' # フォントの種類とサイズを設定する。 plt.rcParams['font.size'] = 20 plt.rcParams['font.family'] = 'Times New Roman' # 目盛を内側にする。 plt.rcParams['xtick.direction'] = 'in' plt.rcParams['ytick.direction'] = 'in' # Subplot設定とグラフの上下左右に目盛線を付ける。 self.fig = plt.figure() self.ax1 = self.fig.add_subplot(111) self.ax1.yaxis.set_ticks_position('both') self.ax1.xaxis.set_ticks_position('both') # 軸のラベルを設定する。 self.ax1.set_xlabel('x') self.ax1.set_ylabel('y') # スケールを設定する。 self.ax1.set_xlim(0, 10) self.ax1.set_ylim(-2, 2) # プロット self.ax1.plot(x, y, lw=1) # レイアウト設定 self.fig.tight_layout() return self.fig def on_button(self): ''' ボタンがクリックされた時のイベント:波形を更新する ''' # 更新値(ランダム波形) x = np.arange(0, 10, 1) y = np.random.normal(loc=0, scale=1, size=len(x)) # figの再作成 self.fig = self.plot(x, y) # 値の更新(ウィジェットを削除して新しく追加している。なんかもっと良い方法が??) self.remove_widget(self.children[0]) self.add_widget(FigureCanvasKivyAgg(self.fig)) class MyApp(App): title = 'My Application' if __name__ == '__main__': MyApp().run() |
実行結果
以下が実行結果です。見事、ボタンをクリックすると波形が更新されるアプリができました!
まとめ
この記事ではkivyの中でmatplotlibを使う時に、筆者自身が躓いたポイントを解決する方法について述べました。ただし、この方法はIT初心者の方法かもしれないため、もしプロのご意見があればコメントしてくださると幸いです。
問題はgarden install matplotlibが通らないことにありましたが、原因はgarden.batの場所にありました。実行に使うgardenを適切に指定し、インストールファイルを普段プログラミングで使っている場所に適切に置くことで正常なインストール状態を再現しました。
最後にkivyでmatplotlibを使うコード例を紹介しました。ボタンで波形が更新される動作として、計算部分をPythonクラスで表現した内容になっています。
まだまだ改良の余地はあるかもしれませんが、とりあえずやりたい動作はできた…という段階でしょうか。今後はより実用的なプロットアプリを作ってみたいと思いますので、お楽しみに。
Tkinter、wxPythonに続きkivyでもmatplotlibを使うことができました!
Twitterでも関連情報をつぶやいているので、wat(@watlablog)のフォローお待ちしています!