LoginSignup
1
1

血圧と体重管理アプリを作ってみた

Last updated at Posted at 2024-03-06

背景

健康診断で血圧が高いと判断されたので、再受診しました。その時に血圧測定手帳はあるのですが、せっかくなので、アプリで管理したいと思い、作ってみた

完成形

image.png

要件

  • 血圧、心拍数、体重の入力ができること
  • グラフ表示できること
  • 画像保存できること

ポイント解説

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

追加情報

image.png

UIを日本語化し、さらにデータ更新とグラフの更新も追加

グラフの更新手順は以下に示す。

  1. グラフを描画するメソッドを新規作成
  2. initから描画メソッドコールする
  3. init関数はclass初期化する時、一度のみしかコールされないため、切り出す必要がある
  4. データ更新メソッドから再描画メソッドをコール
  5. 再描画メソッドは以下の通り
# 再描画
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時間で作れてしまう時代ってめっちゃすごいと思いました。

1
1
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
1
1