前置き
独学で、子供の成長アプリを作った時のことを、記録として残していきます。
間違っているところなどあれば、ご連絡お願いします。
①Djangoのようこそページへたどり着くまで
②NginxでDjangoのようこそページへたどり着くまで
③カスタムユーザーを作ってadminにたどり着く
④ログインログアウトをしよう
⑤ユーザー登録(サインイン)機能を作ろう
⑥ユーザーごとのデータ登録できるようにする〜CRU編
⑦ユーザーごとのデータ登録できるようにする〜削除編
⑧画像ファイルのアップロード
⑨身長体重を記録する@一括削除機能つき
⑩成長曲線グラフを描いてみよう <--ここです
⑪本番環境へデプロイ+色々手直し
Goal
登録された身長体重データをもとに、成長曲線を書いてみよう
成長曲線とは
横軸に年齢、縦軸に身長や体重をプロットする曲線。
厚生労働省が10年ごとに計測データを出していて、その標準偏差などと比較しながら
子供の成長具合を把握するのに使うもの。
平成22年乳幼児身体発育調査の概況について
https://www.mhlw.go.jp/stf/houdou/0000042861.html
View
グラフ描画はmatplotlib、pandas、numpyを利用。
4種類のグラフ描画を連結させて、1つのグラフとして描画している。
・固定線(身長、体重)
厚労省のデータはCSVにしてSTATICに保存。
データは折れ線グラフで描画。線自体は身長、体重ごとに7本ずつ描かれる。(標準偏差)
・分散図(身長、体重)
こちらは、1つ前で登録できるようにした身長体重データを読み込んでプロットする。
身長体重データは計測日しか持っていないので、計測日時点の子供の年齢は、生年月日から計算する。
簡易な計算方法はググれば出てくるのだが、なぜか0才のときの月齢計算が成立しないので、
そのときだけ別の計算式になるよう工夫(?)している。
from django.shortcuts import render, redirect
from django.http import HttpResponse
from django.contrib.auth.decorators import login_required
from .models import PhysData
from users.models import KidsProfile
from .forms import PhysDataForm, PhysDataEditForm
import matplotlib.pyplot as plt
from matplotlib.backends.backend_agg import FigureCanvasAgg
import numpy as np
import pandas as pd
import io
# 身長体重データを昇順で返却
@login_required
def phys_data_list(request, **kwargs):
(略)
# 身長体重データを新規登録する
@login_required
def phys_data_add(request):
(略)
# 身長体重データを編集する
@login_required
def phys_data_edit(request, dataPostId):
(略)
# 身長体重データを削除する
@login_required
(略)
#グラフ画像を表示するページ
@login_required
def graph_page_display(request, **kwargs):
user_name = request.user
if len(kwargs) > 0:
kidsProfileId = kwargs["kidsProfileId"]
else:
kidsProfileId = KidsProfile.objects.filter(user=user_name).order_by('id')[0].id
kids_profiles = KidsProfile.objects.filter(user=user_name)
kids_profile = KidsProfile.objects.filter(id=kidsProfileId)
params = {
'kidsProfiles' : kids_profiles,
'kidsProfileId' : kidsProfileId,
'kidsName' : kids_profile[0].name,
}
return render(request, 'phys/phys_graph_display.html', params)
# グラフの画像生成
@login_required
def graph_image_display(request, **kwargs):
# 初期設定
fig = plt.figure(figsize=(8, 5))
ax_h = fig.add_subplot(1, 1, 1)
ax_w = ax_h.twinx()
ax_hs = ax_h.twinx()
ax_ws = ax_h.twinx()
# グラフ自体の設定
# タイトル
ax_h.set_xlabel(u'age')
ax_h.set_ylabel(u'cm')
ax_w.set_ylabel(u'kg')
# 固定線の描画
border_color_h = 'skyblue' #身長の固定線色
border_color_w = 'lightgreen' #体重の固定線色
border_lw_mid = 2 #中央の固定線太さ
border_lw_other = 1 #中央以外の固定線太さ
# 固定値(身長)
# 3%
data_man_height_03 = pd.read_csv('static/physData/man_height_03.csv', names=['num1', 'num2'])
ax_h.plot(data_man_height_03['num1'], data_man_height_03['num2'], color=border_color_h, lw=border_lw_other)
# 10%
data_man_height_10 = pd.read_csv('static/physData/man_height_10.csv', names=['num1', 'num2'])
ax_h.plot(data_man_height_10['num1'], data_man_height_10['num2'], color=border_color_h, lw=border_lw_other)
# 25%
data_man_height_25 = pd.read_csv('static/physData/man_height_25.csv', names=['num1', 'num2'])
ax_h.plot(data_man_height_25['num1'], data_man_height_25['num2'], color=border_color_h, lw=border_lw_other)
# 50%
data_man_height_50 = pd.read_csv('static/physData/man_height_50.csv', names=['num1', 'num2'])
ax_h.plot(data_man_height_50['num1'], data_man_height_50['num2'], color=border_color_h, lw=border_lw_mid)
# 75%
data_man_height_75 = pd.read_csv('static/physData/man_height_75.csv', names=['num1', 'num2'])
ax_h.plot(data_man_height_75['num1'], data_man_height_75['num2'], color=border_color_h, lw=border_lw_other)
# 90%
data_man_height_90 = pd.read_csv('static/physData/man_height_90.csv', names=['num1', 'num2'])
ax_h.plot(data_man_height_90['num1'], data_man_height_90['num2'], color=border_color_h, lw=border_lw_other)
# 97%
data_man_height_97 = pd.read_csv('static/physData/man_height_97.csv', names=['num1', 'num2'])
ax_h.plot(data_man_height_97['num1'], data_man_height_97['num2'], color=border_color_h, lw=border_lw_other)
# 固定値(体重)
# 3%
data_man_weight_03 = pd.read_csv('static/physData/man_weight_03.csv', names=['num1', 'num2'])
ax_w.plot(data_man_weight_03['num1'], data_man_weight_03['num2'], color=border_color_w, lw=1)
# 10%
data_man_weight_10 = pd.read_csv('static/physData/man_weight_10.csv', names=['num1', 'num2'])
ax_w.plot(data_man_weight_10['num1'], data_man_weight_10['num2'], color=border_color_w, lw=1)
# 25%
data_man_weight_25 = pd.read_csv('static/physData/man_weight_25.csv', names=['num1', 'num2'])
ax_w.plot(data_man_weight_25['num1'], data_man_weight_25['num2'], color=border_color_w, lw=1)
# 50%
data_man_weight_50 = pd.read_csv('static/physData/man_weight_50.csv', names=['num1', 'num2'])
ax_w.plot(data_man_weight_50['num1'], data_man_weight_50['num2'], color=border_color_w, lw=1.5)
# 75%
data_man_weight_75 = pd.read_csv('static/physData/man_weight_75.csv', names=['num1', 'num2'])
ax_w.plot(data_man_weight_75['num1'], data_man_weight_75['num2'], color=border_color_w, lw=1)
# 90%
data_man_weight_90 = pd.read_csv('static/physData/man_weight_90.csv', names=['num1', 'num2'])
ax_w.plot(data_man_weight_90['num1'], data_man_weight_90['num2'], color=border_color_w, lw=1)
# 97%
data_man_weight_97 = pd.read_csv('static/physData/man_weight_97.csv', names=['num1', 'num2'])
ax_w.plot(data_man_weight_97['num1'], data_man_weight_97['num2'], color=border_color_w, lw=1)
# 子供データ取得
user_name = request.user
if len(kwargs) > 0:
kidsProfileId = kwargs["kidsProfileId"]
else:
kidsProfileId = KidsProfile.objects.filter(user=user_name).order_by('id')[0].id
kids_profile = KidsProfile.objects.filter(user=user_name, id=kidsProfileId)
phys_data = PhysData.objects.filter(user=user_name, kidsProfile=kidsProfileId)
# グラフデータ作成
graph_height = []
graph_weight = []
graph_date = []
for i in range(len(phys_data)):
graph_height.append(phys_data[i].height)
graph_weight.append(phys_data[i].weight)
# 誕生日計算(小数点計算)
dStr1 = phys_data[i].date.strftime('%Y%m%d')
dStr2 = kids_profile[0].birthday.strftime('%Y%m%d')
if kids_profile[0].birthday.year == phys_data[i].date.year:
dStr3 = (int(dStr1) - int(dStr2)) / 1000 # 0才のときの月齢を出す
else:
dStr3 = (int(dStr1) - int(dStr2)) / 10000 # 一般的な誕生日計算
graph_date.append(dStr3)
# グラフプロット(分散図)
ax_hs.scatter(graph_date, graph_height, marker='x', color='blue')
ax_ws.scatter(graph_date, graph_weight, marker='x', color='green')
# グリッド設定
ax_h.grid(which='both', color='gray', linestyle='solid')
ax_w.grid(which='both', color='gray', linestyle='solid')
# Y軸最大値設定
ax_h.set_ylim([0, 140])
ax_w.set_ylim([0, 35])
ax_hs.set_ylim([0, 140])
ax_ws.set_ylim([0, 35])
# Y軸メモリ
ax_h.set_yticks(np.linspace(0, 140, 15))
ax_w.set_yticks(np.linspace(0, 35, 15))
ax_hs.set_yticks([])
ax_ws.set_yticks([])
# X軸メモリ
ax_h.set_xticks(np.linspace(0, 7, 8))
ax_w.set_xticks(np.linspace(0, 7, 8))
ax_hs.set_xticks(np.linspace(0, 7, 8))
ax_ws.set_xticks(np.linspace(0, 7, 8))
# 描画
canvas = FigureCanvasAgg(fig)
buf = io.BytesIO()
canvas.print_png(buf)
response = HttpResponse(buf.getvalue(), content_type='image/png')
fig.clear()
response['Content-Length'] = str(len(response.content))
return response
CSVはこんな感じのデータ。左が年齢、右が身長。
原データはエクセルファイルなので、Excel上で加工してCSV化したもの。
0.00,44.0
0.08,48.7
0.17,50.9
Template & URL
graph_image_displayはイメージファイルを返却してくるので、
urlsでURL化した上で、HTMLではそのURLをタグで待ち受ける。
http://〜〜〜.pngみたいな形式でなくても画像が表示されるのは、何だか驚き。
画像描画たびに裏でグラフ描画が走るため、画像表示までに数秒かかってしまう。
そのため、画像が表示されるまではbootstrapのspinnerが表示されるようjsで制御。
{% extends 'base.html' %}
{% block extra_js %}
<script>
$(function() {
$('#loader-bg ,#loader').css('display','block');//ローディング画像を表示
});
$(window).on("load",function () { //読み込み完了したら実行する
$('#loader-bg').css('display','none');//ローディングを隠す
$('#loader').css('display','none');
$('#contents').fadeIn().removeClass("is-hide");//コンテンツを表示する
});
$(function(){
setTimeout('stopload()',10000); //いつまでもローディング状態にならないように10秒で強制表示させる
});
function stopload(){ //強制表示の関数
$('#contens').css('display','block');
$('#loader-bg').delay(900).fadeOut(800);
$('#loader').delay(600).fadeOut(300);
}
</script>
{% endblock extra_js %}
{% block content %}
<div class="row">
<div class="col-md-12 col-lg-2">
<div class="list-group">
{% for kidsProfile in kidsProfiles %}
<a href="{% url 'phys:graph_page_display' %}{{kidsProfile.id}}" class="list-group-item list-group-item-action">{{kidsProfile.name}}</a>
{% endfor %}
</div>
<br>
</div>
<div class="col-md-12 col-lg-7 overflow-auto">
<div class="card">
<div class="card-header">{{ kidsName }}</div>
<div class="card-body">
<div id="loader-bg">
<div id="loader">
<div class="text-center">
<div class="spinner-border" role="status">
<span class="sr-only">Loading...</span>
</div>
</div>
</div>
</div>
<img src="{% url 'phys:graph_image_display' kidsProfileId %}" id="contents" class="img-fluid is-hide">
</div>
</div>
</div>
{% endblock %}
from django.urls import path
from . import views
app_name = 'phys'
urlpatterns = [
path('phys/list/', views.phys_data_list, name='phys_data_list'),
path('phys/list/<kidsProfileId>', views.phys_data_list, name='phys_data_list'),
path('phys/data_add/', views.phys_data_add, name='phys_data_add'),
path('phys/data_edit/<dataPostId>', views.phys_data_edit, name='phys_data_edit'),
path('phys/data_delete/', views.phys_data_delete, name='phys_data_delete'),
path('phys/graph/', views.graph_page_display, name='graph_page_display'),
path('phys/graph/<kidsProfileId>', views.graph_page_display, name='graph_page_display'),
path('data/graph_imaga/', views.graph_image_display, name='graph_image_display'),
path('data/graph_imaga/<kidsProfileId>', views.graph_image_display, name='graph_image_display'),
]
]