見出し画像

【110日目】Django_matplotlibで円グラフを作成

以前作成したTodoアプリ内で、完了したTodoから傾向を分析するためのツールを作成しています。

元々は棒グラフまで作成しておりましたが、今回はここに円グラフを追加する形です。

↓棒グラフが完成した回


Todoを記載するときに、どういう種類の作業なのかカテゴリー分けするようにしていました(納品物/社内タスクなど)。今回は対象期間の完了済タスクにおいて、このカテゴリーがどういう内訳になっているか可視化していきたいと思います。


↓完成版の画面です。

これがあれば、どういう仕事に時間を使っていたのか分かりそうです。


大きく以下の手順で進めました。

①graph.pyに円グラフを作成するためのPlot_Piechart関数を作成
②views.pyのTodoAnalysisクラスに円グラフ用のデータを集めるコードを記載
③テンプレートを編集して円グラフをブラウザに表示



①graph.pyに円グラフを作成するためのPlot_Piechart関数を作成

円グラフ用にmatplotlibを使って関数を作成しました。あまり特殊な処理は加えておらず、ベーシックなやり方だと思います。

引数のpに円グラフのデータが、lにそのデータのラベル(カテゴリー名)が入ってくる想定です。

import matplotlib.pyplot as plt
import base64

# グラフを画像データにするための関数(過去の記事でご紹介したもの)
def Output_Graph():
    buffer = BytesIO()
    plt.savefig(buffer, format="png")
    buffer.seek(0)
    img = buffer.getvalue()
    graph = base64.b64encode(img)
    graph = graph.decode("utf-8")
    buffer.close()
    return graph


def Plot_PieChart(p,l):
    # 日本語を表示する設定
    plt.rcParams['font.family'] = 'Yu Gothic'
    # 円グラフの色の設定。順番に適用されていき、最後まで到達したら最初に戻って同じ色が適用される。
    c = ["skyblue", 'powderblue', 'lightcyan', 'cadetblue',"cornflowerblue"]
    plt.switch_backend("AGG")
    plt.figure(figsize=(4,4))
    # 円グラフを描画。デフォルトは3時の方向から開始
    # autopoctで比率を表示するようにしている。小数点第一位まで表示するときはautopct='%.1f%%'。
    # counterclock=Falseで時計回りに、startangle=90で12時の方向から開始、radiusで半径を変更(2で2倍?)
    plt.pie(p, autopct="%d%%", labels = l, colors = c, counterclock=False, startangle=90, radius=0.8, center=(0, 0))
    plt.title('内訳', fontsize=15)
    
    graph = Output_Graph()
    return graph

円グラフの開始地点がデフォルトで3時方向なのが謎でした。。が、設定自体はどれも非常にシンプルです。

上部のOutput_Graph関数については以下の記事で詳しく解説しています。



②views.pyのTodoAnalysisクラスに円グラフ用のデータを集めるコードを記載

データベースには「タスク名」「タスク詳細」「カテゴリー」「ステータス」「最終更新日」などが入っています。これらのデータのうち、ステータスが【完了済】になっているタスクの、「カテゴリー」の内訳を円グラフにしていきます。

また、対象期間を定めて分析するので、フォームから受け取った対象期間の中に「最終更新日(=完了済になった日)」が入っているタスクに限定して集計しています。

from django.views.generic import FormView
from .models import Todo
from .forms import TodoForm, AnalysisPeriodForm

from django.urls import reverse_lazy
from numpy import character

from . import graph
import datetime
from datetime import timedelta
import pandas as pd
from django.utils.timezone import localtime
 

class TodoAnalysis(FormView):
    form_class = AnalysisPeriodForm
    template_name = "todo/todo_analysis.html"
    
    def form_valid(self, form):
        """
        棒グラフを作成するためのコード
        """

        ~ 中略 ~
        # Todoのデータを全てqsに代入する
        qs = Todo.objects.all()
        # グラフに表示する日数を指定する=for文で何回ループ処理するか
        N = (end_day - start_day).days + 1
        # 指定期間の辞書を作る
        tasks = {}
        for i in range(N):
            date = start_day + timedelta(i)
            tasks[date] = 0


    ~ 中略 ~


        """
        円グラフを作成するためのコード(qsは棒グラフ部分より流用)
        """
        # 納品物がいくつか、社内タスクがいくつか、とカテゴリー毎の数を算出する辞書を作る
        cate = {}
        for ele2 in qs:
            # qsのupdated_atの時間情報を繰り返しのたびに日付型に変換し、
           xx = pd.DataFrame({'time':[localtime(ele2.updated_at)]})
            xx['time'] = pd.to_datetime(xx['time']).dt.date
            # 指定期間の日付が格納された辞書(tasks)にその日付が入っていて、かつステータスが完了済だった場合、
            if xx['time'][0] in tasks and str(ele2.status) == "完了済":
                # 既に算出用の辞書にそのカテゴリーが存在していれば+1カウントする
                if ele2.category in cate:
                    cate[ele2.category] += 1
                # まだ算出用の辞書にそのカテゴリーがなければ新たに加える
                else:
                    cate[ele2.category] = 1
        # Plot_Piechart関数の「p」に渡す配列(円グラフの中身)
        pie = [pie for pie in cate.values()]
        # Plot_Piechart関数の「l」に渡す配列(円グラフのラベル)
        label = [label for label in cate.keys()]
        chart2 = graph.Plot_PieChart(pie, label)


        # ここで引数に渡せば、上記のどの変数もテンプレートに渡せる(以下ではchartは棒グラフを、chart2は円グラフを渡している)
        ctxt = self.get_context_data(chart=chart, chart2=chart2, form=form)

        return self.render_to_response(ctxt)

もっと美しいコードを書けるようになりたいと思いつつ、現状ではこういうやり方になりました。


③テンプレートを編集して円グラフをブラウザに表示

ブートストラップを活用してhtmlを記載しています。
base.htmlに基本設定をしておりますが、今回はテンプレート上の該当箇所のみ紹介します。

formで対象期間の入力を受け取って、グラフの画像を2列で表示しています。onerrorは期間が入力されていない時など、グラフの画像が表示されない時に何も表示しないようにするためのものです(この設定をしないと画像のエラー表示が出ます)。

<form action="" method="POST">{% csrf_token %}
   <div>
        対象期間:{{ form.start_day }} ~ {{form.end_day}}
   </div>
   <br>
   <input class="btn btn-info text-white" type="submit" Value="グラフを表示">
   <a class="btn btn-outline-secondary" href="{% url 'list' %}" role="button">Topへ戻る</a>
</form>

<div class="container">
    <div class="row">
        <div class="col">
            <img src="data:image/png;base64,{{ chart| safe }}" onerror="this.style.display='none'"/>
        </div>
        <div class="col">
            <img src="data:image/png;base64,{{ chart2| safe }}" onerror="this.style.display='none'"/>
        </div>
    </div>
</div>


Djangoでは、各グラフの関数を作ってしまえば一つのページで一気に何パターンもグラフ表示できるのでとても便利です。


ここまでお読みいただきありがとうございました!



これまで修了したコース等

【YouTube_Django関係】
Pythonでウェブサービスを作ろう! 1
テンプレートをマスターしよう! 2
静的ファイルを配信しよう !3
本番公開しよう! 4
データベースと接続しよう! 5
ブログを作って学ぶモデル入門! 6
これが汎用ビューの力! 7
Djangoフォームを自由自在に操ろう! 8
djagoを最大限使って効率よくログインを作ろう! 9
ログイン完成!サインアップ & メール認証 10
データベースマイグレーション前編 15
データベースマイグレーション後編 16

【YouTube_Pandas関係】
3時間でマスター Pandas入門コース
Pandas20本ノック

【Paiza】
Aランクレベルアップメニュー 24/49問
データセット選択メニュー   12/17問
配列メニュー         64/64問
ループメニュー1      20/20問
ループメニュー2      12/20問
条件分岐メニュー       25/25問
二重ループメニュー      19/19問
配列活用メニュー       26/26問
文字列処理メニュー      30/30問
Bランクレベルアップメニュー 62/62問
Cランクレベルアップメニュー 30/30問
ランクB合格
ランクC合格
JavaScript体験篇       15/15講座
辞書(ディクショナリ)の基礎 8/8講座

【書籍/ブログ】
Django入門 | 初心者でも1時間でWebアプリ(Todoアプリ)を作成するコース
基礎からのMySQL
Web技術の基本
京大のPython教科書
Pythonデータベースプログラミング
Pythonエンジニアファーストブック

【Progate】
Python Ⅰ~Ⅴ
Python アプリ版 コースⅠ~Ⅴ
SQL Ⅰ~ Ⅳ
SQL アプリ版 コースⅢ
HTML&CSS 初級編

【環境構築】
Python, VSCode, MySQL(MAMP), Git / GitHub, HEROKU, anaconda, jupyter lab



この記事が気に入ったらサポートをしてみませんか?