背景
健康診断で血圧が高いと判断されたので、再受診しました。その時に血圧測定手帳はあるのですが、せっかくなので、アプリで管理したいと思い、作ってみた
完成形
要件
- 血圧、心拍数、体重の入力ができること
- グラフ表示できること
- 画像保存できること
ポイント解説
- matplotlibは比較的に人気のあるライブラリなのですが、kivyとの相性はあまりよくない(というより情報が散乱していた)ため、なかなかグラフ表示までが長かった
- subplotsという使い方もグラフ出力が可能となりました
requirements.txt
$ pip list
Package Version
---------------------- -----------
blinker 1.7.0
certifi 2024.2.2
charset-normalizer 3.3.2
click 8.1.7
colorama 0.4.6
contourpy 1.2.0
cycler 0.12.1
docutils 0.20.1
Flask 3.0.2
fonttools 4.49.0
idna 3.6
inputs 0.5
itsdangerous 2.1.2
Jinja2 3.1.3
Kivy 2.3.0
kivy-deps.angle 0.4.0
kivy-deps.glew 0.3.1
kivy-deps.sdl2 0.7.0
Kivy-examples 2.3.0
Kivy-Garden 0.1.5
kivy_matplotlib_widget 0.10.0
kivymd 1.2.0
kiwisolver 1.4.5
MarkupSafe 2.1.5
matplotlib 3.7.2
numpy 1.26.4
packaging 23.2
pillow 10.2.0
pip 24.0
pygame 2.5.2
Pygments 2.17.2
pyparsing 3.0.9
pypiwin32 223
python-dateutil 2.9.0.post0
pywin32 306
requests 2.31.0
setuptools 58.1.0
six 1.16.0
urllib3 2.2.0
watchdog 4.0.0
Werkzeug 3.0.1
Main.py
# import os
# os.environ["KIVY_NO_CONSOLELOG"] = "1"
import datetime
from kivy.lang import Builder
from kivymd.app import MDApp
from kivy.uix.floatlayout import FloatLayout
from kivy.garden.matplotlib.backend_kivyagg import FigureCanvasKivyAgg
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
from data_config import Config
#Build our app
class Matty(FloatLayout):
def __init__(self,**kwargs):
super().__init__(**kwargs)
self.create_blood()
box=self.ids.box
box.add_widget(FigureCanvasKivyAgg(self.plt.gcf()))
def get_json_data(self):
ins = Config({})
data = ins.get_json_info()
# リストの最初の要素を使用して、すべてのキーを抽出します。
keys = data[0].keys()
# 各キーに対して、リストを作成し、そのキーの値をリストに追加します。
data_by_key = {key: [item[key] for item in data] for key in keys}
return data_by_key
def create_blood(self):
data = self.get_json_data()
# Example blood pressure data sets
blood_pressure_data_set1 = data['high']
blood_pressure_data_set2 = data['low']
weight_data = data['weight']
pulse_data = data['pulse']
# Create a list of dates from the start_date to the end_date
dates = [datetime.datetime.strptime(date_string, "%Y-%m-%d").date() for date_string in data['date']]
# Plot the first set of blood pressure data
plt.plot_date(dates, blood_pressure_data_set1, marker='o', linestyle='--', label='High Blood Pressure', color='crimson')
# Annotate the plotted values
for date, value in zip(dates, blood_pressure_data_set1):
plt.annotate(str(value), (date, value), textcoords="offset points", xytext=(-10,10), ha='center', color='crimson')
# Plot the second set of blood pressure data
plt.plot_date(dates, blood_pressure_data_set2, marker='o', linestyle='--', label='Low Blood Pressure', color='tomato')
# # Annotate the plotted values
for date, value in zip(dates, blood_pressure_data_set2):
plt.annotate(str(value), (date, value), textcoords="offset points", xytext=(-10,10), ha='center', color='tomato')
plt.plot_date(dates, pulse_data, marker='o', linestyle='--', label='pulsation', color='deeppink')
for date, value in zip(dates, pulse_data):
plt.annotate(str(value), (date, value), textcoords="offset points", xytext=(-10,10), ha='center', color='deeppink')
# # Add annotations for normal values
plt.axhline(y=135, color='crimson', linestyle='--', label='Normal Upper Limit: 135')
plt.axhline(y=80, color='tomato', linestyle='--', label='Normal Lower Limit: 80')
plt.fill_between(dates, 135, 180, color='tomato', alpha=0.2)
plt.fill_between(dates, 80, 135, color='lightskyblue', alpha=0.2)
plt.legend(loc='upper left')
# Set labels and title
plt.title('Blood Pressure/pulsation Measurements')
plt.xlabel('Date')
plt.ylabel('Blood Pressure (mmHg)')
plt.ylim(60, 180)
ax2 = plt.gca().twinx()
ax2.plot_date(dates, weight_data, marker='o', linestyle='--', label='weight', color='g')
for date, value in zip(dates, weight_data):
ax2.annotate(str(value), (date, value), textcoords="offset points", xytext=(15,-10), ha='center')
ax2.axhline(y=70, color='g', linestyle='--', label='Normal weight: 70')
ax2.legend(loc='upper right')
ax2.set_ylabel('Weight (kg)', rotation=90, labelpad=15)
ax2.yaxis.set_label_position('right')
ax2.set_ylim(65,85)
# Format the x-axis to display dates
plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
plt.gca().xaxis.set_major_locator(mdates.DayLocator())
plt.gcf().autofmt_xdate()
self.plt = plt
def saveit(self):
td = datetime.date.today().strftime("%Y-%m-%d_%H_%M_%S")
self.plt.savefig(f'graph_{td}.png')
print('finish Save it to PNG.')
def create_data(self):
# get data
data = {
"dt": datetime.date.today().strftime("%Y-%m-%d"),
"high": int(self.ids.bld_high_in.text),
"low": int(self.ids.bld_low_in.text),
"pulse": int(self.ids.pulse_in.text),
"weight": float(self.ids.weight_in.text)
}
ins = Config(data)
ins.add_new_data()
print(f'finish add new data of {data}')
class MainApp(MDApp):
def build(self):
self.theme_cls.primary_palette="Blue"
self.theme_cls.primary_hue = "A700"
self.theme_cls.theme_style = "Light"
Builder.load_file('mappy.kv')
return Matty()
MainApp().run()
Subplotの場合
import matplotlib
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.clock import Clock
import matplotlib.pyplot as plt
from kivy.garden.matplotlib.backend_kivyagg import FigureCanvasKivyAgg
from kivy.lang import Builder
import random
class MyRootWidget(FloatLayout):
def __init__(self, **kwargs):
super(MyRootWidget, self).__init__(**kwargs)
self.fig, self.ax = plt.subplots(2,1)
self.i = 1
self.ch1 = random.randint(15, 20)
self.ch2 = random.randint(12, 16)
self.line = [self.i]
self.cards = [self.ch1]
self.cards_02 = [self.ch2]
Clock.schedule_interval(self.update_02, 0.5)
box = self.ids.box
box.add_widget(FigureCanvasKivyAgg(self.fig))
# argsはClock描画用の引数として渡される
def update_02(self, *args):
if self.i > 6:
pass
else:
self.data_set_01 = [1,2,3,4,5,6]
y = self.data_set_01[0:self.i]
self.ax[0].bar(self.line, y, color='r')
self.i += 1
self.line.append(self.i)
self.fig.canvas.draw_idle()
class MyApp(App):
def build(self):
Builder.load_file('plot.kv')
return MyRootWidget()
MyApp().run()
追加情報
UIを日本語化し、さらにデータ更新とグラフの更新も追加
グラフの更新手順は以下に示す。
- グラフを描画するメソッドを新規作成
- initから描画メソッドコールする
- init関数はclass初期化する時、一度のみしかコールされないため、切り出す必要がある
- データ更新メソッドから再描画メソッドをコール
- 再描画メソッドは以下の通り
# 再描画
def review(self):
# matplotlibの描画をクリア
plt.clf()
# ヴィジェットを削除
self.box.remove_widget(self.fig)
# グラフ初期描画メソッドをコール
self.init_graph()
# グラフ初期描画メソッド
def init_graph(self):
# 血圧描画
self.create_blood()
# 描画結果をfigに代入
self.fig = FigureCanvasKivyAgg(self.plt.gcf()) #ここでself.canvasという変数を使うとエラー
self.box=self.ids.box
# ヴィジェットに追加
self.box.add_widget(self.fig)
感想
こういうアプリがたくさんあると思うのですが、こんな感じで2、3時間で作れてしまう時代ってめっちゃすごいと思いました。