見出し画像

【93日目】Django_matplotlibでグラフ作成_棒グラフを表示する手順②

 このnoteは、31歳未経験からエンジニアを目指して勉強していく記録を綴っているものです。現在はAdTechでカスタマーサクセスを担当しつつ、色んなチャンスに恵まれ、CS業務や子育てと並行しながらチャレンジしています。

 これからプログラミングを始める方にとってのTipsやモチベアップに繋げられるように頑張りたいと思っています。
--------------------------------------------


Djangoでグラフを表示する手順について学んでいます。
前回まででグラフ作成用のライブラリであるmatplotlibの使い方を中心にまとめました↓

真似させていただいているブログはこちらです↓


完成形のグラフはこちらです↓



view.pyにグラフを返すためのビューを作成

モデルからデータを集めて、前回作成したPlot_Graph関数を呼び出してグラフ化し、テンプレートに渡す役割を担っています。全体像は以下です↓。

[views.py]
 
from django.views.generic import TemplateView
from . import models
from . import graph


class Index(TemplateView):
    template_name = "index.html"

    def get_context_data(self, **kwargs):
        qs    = models.ProductA.objects.all()
        x     = [x.Date for x in qs]
        y     = [y.Revenue for y in qs]
        chart = graph.Plot_Graph(x, y)

        context = super().get_context_data(**kwargs)
        context['chart'] = chart
        return context
    
    def get(self, request, *args, **kwargs):
        return super().get(request, *args, **kwargs)

↓modelsは前回触れましたが以下のようになっています。

[models.py]
 
from django.db import models


class ProductA(models.Model):
    Date = models.DateField()
    Revenue = models.IntegerField()


一つずつ見ていきたいと思います。
↓まずモデルからデータを集めているところです。

        qs    = models.ProductA.objects.all()

上記のように書けば、ProductAに格納されている全てのデータを受け取ることができます。今回はモデルが単純なのでこのようにしてますが、特定のフィールドのみ受け取る方法は別途調べます。(自分へのTodoメモです)


↓続いてデータをx軸用とy軸用に分類し、Plot_Graph関数を呼び出す所です。

        x     = [x.Date for x in qs]
        y     = [y.Revenue for y in qs]
        chart = graph.Plot_Graph(x, y)

xとyはリスト内包表記で、それぞれDateフィールドとRvenueフィールドの要素を格納していっています。そしてそれをPlot_Graph関数の引数に渡しつつ、返ってくるグラフをchartという変数に代入しています。


↓chartに代入されたグラフをテンプレートに渡すための処理です

        context = super().get_context_data(**kwargs)
        context['chart'] = chart
        return context

chartを'chart'というコンテキスト変数でテンプレートに渡しています。これによってテンプレート側でchartという変数でグラフを描画することができるようになります。


↓こちらは削除しても正常に動作はしました。

    def get(self, request, *args, **kwargs):
        return super().get(request, *args, **kwargs)

get送信に対してget送信で返しているだけなので、このアプリではあってもなくてもいいのだと思います。実際この関数がなくても正常に動作してますし。ただ、バリデーションチェックが必要な場合などは、returnまでの間に処理を書くことで役割を持つはずです。今回は「明示的に書いておいた方がいい」という主旨だと思います(多分)。


urls.pyで表示設定

ここはいつも通りの設定です。プロジェクト全体のurlsにアプリのurlsを設定し、アプリのurlsに上記のIndexビューを登録します。

[全体のurls.py]
 
from django.contrib import admin
from django.urls import path
from django.urls import include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('test_graph.urls')),
]
[アプリのurls.py]
 
from django.urls import path
from . import views

urlpatterns = [
    path('', views.Index.as_view(), name="index"),
]


テンプレートファイル(Index.html)を作成

今回は練習用なのでテンプレートもシンプルです。
ただ、ここで分かったこともあるので丁寧にいきたいと思います。

[index.html]
 
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Document</title>
</head>
<body>
    <h1>日々の売上報告</h1>
        <img src="data:image/png;base64,{{ chart| safe }}">
</body>
</html>

このうちグラフを表示しているのは以下のコードです。

        <img src="data:image/png;base64,{{ chart| safe }}">

この「<img src="data:image/png;base64,Base64化されたデータの文字列>」で画像をhtmlファイルに直接表示させています。では{{ chart }}を直接表示させてみるとどうなるでしょうか?

以下のようにBase64化されたデータの文字列が表示され、こういう文字列でデータが行き来していたことが分かります。

右にずっと文字列が続く


ここでグラフ化用に作成したOutput_Graph関数を思い出してみます。

[graph.py]
 
import matplotlib.pyplot as plt
import base64
from io import BytesIO


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

このうちのエンコードとデコードを見てみます。

    graph = base64.b64encode(img)
    graph = graph.decode("utf-8")

utf-8にデコードしているのは、後々htmlファイル上で機能させるためということが分かりました。私はここで既に「グラフの画像」にまで変換されていると思っていたのですが、ここでは文字列にしているだけみたいですね。そしてその文字列を画像として表示するのはhtml上で行っている、という流れが正しそうです。

ちなみに{{ chart | safe }}のsafeフィルターは、値をエスケープ済みの安全な値としてマークし、出力時の HTML エスケープを行わないようにするものです。

エスケープ処理とは、内部のコードに影響する特殊文字(<>や""や''など)をHTML上で勝手に変換して表示するための処理です。これはフォームなどでユーザーが文字を送信するといった場合に、ユーザーが内部のプログラミング上にコードを入力できてしまう危険性に対処するために必要なものです。

Djangoではデフォルトでエスケープ処理を行ってくれているので、逆に安全であることが分かっている場合は今回のようにsafeフィルタでスキップも可能です。

なお、safeフィルタは1箇所ずつ対応しますが、変数が複数ある場合は以下のようにautoescape offタグで囲むことでもスキップできます。

{% autoescape off %}
    {{ text }}
{% endautoescape %}


さて、グラフ化の知見は溜まってきたので、次回からいよいよTodoアプリの機能として実装していきたいと思います。



参考


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

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

【Paiza】
Aランクレベルアップメニュー 24/49問
データセット選択メニュー   12/17問
配列メニュー         61/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     514/514頁
Web技術の基本      189/189頁 ※2周目中
京大のPython教科書    116/201頁
Pythonデータベースプログラミング 194/194頁
Pythonエンジニアファーストブック読了

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

【その他】
Pythonの環境構築
VSCodeの環境構築
MySQLの環境構築(MAMP)
Git / GitHubの環境構築
HEROKUの環境構築

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