見出し画像

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

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

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


Djangoのmatplotlibというライブラリを活用して、グラフを作成するチャレンジを実施しています。

↓前回は関連するライブラリについて紹介させていただきました


今回は具体的なコードを書いていきます。
といっても、以下ブログで紹介してくださっている内容を真似ているだけです。ここに自分なりの解釈や留意点を交えて紹介していきたいと思います。

完成イメージは以下です。


それではDjangoのいつも通りの手順でアプリを作成しながら、各ファイルについて解説したいと思います。

まとめだすとボリュームが多くなったので、今回はgraph.pyを作成するところまでです。


models.pyに対象のクラスを作成

モデルの作成からマイグレーションはいつも通りです。今回は紹介されている内容に沿って以下のようなモデルを作っています。売上等のレコードは管理画面から追加しておきます。

from django.db import models

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

マイグレーションの方法は過去にまとめたので、興味ある方はチェックしてみてください。



graph.pyにグラフ表示用の関数を作成_Output_Graph

前回紹介した新しいライブラリはここでふんだんに活用します。

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

def Plot_Graph(x,y):
    plt.switch_backend("AGG")
    plt.figure(figsize=(10,5))
    plt.bar(x,y)
    plt.xticks(rotation=45)
    plt.title("Revenue per Date")
    plt.xlabel("Date")
    plt.ylabel("Revenue")
    plt.tight_layout()
    graph = Output_Graph()
    return graph

上から順に見ていきます。

    buffer = BytesIO()

メモリ上でバイナリデータを扱うための機能であるBytesIOを、bufferという変数に代入します。バイト列を格納するバッファ(データを一時的に格納するためのメモリ領域)と、バッファのどこを見ているかを表す位置を持っています。

↓次はsavefigです

    plt.savefig(buffer, format="png")

pyplotのsavefigメソッドでpng形式の画像データをバッファ(=一時的なメモリ)に保存しています。このコードを外すと、エラーにはならないものの、グラフの画像が消えて↓このような形になります。

続けてseekです↓

    buffer.seek(0)

以下のイメージで、ストリームの先頭から数えてoffsetバイト目に位置をずらします。0を指定しているので、先頭に移動しています。ただ、今回はこの数字を何に変えても正常に動作し、かつこのコードを消してもグラフには影響はありませんでした。

pythonのBytesIOによる標準入出力の再構築とパフォーマンス検証より抜粋


↓続けてgetvalueです。

    img = buffer.getvalue()

getvalueメソッドはバッファの内容全体を含むbytesを返すので、それを変数imgに代入しています。seekの数字が関係なかった理由はこれじゃないかと考えています(一部ではなく全てを取得しているため、seekでどの位置を指定していても同じ結果になっている)。


↓続けてエンコード&デコードです。

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

この工程を飛ばすと(上記のimgをそのまま返すと)、以下のように文字化けします。バイト列がそのまま表示されているのかな。

これをきちんとグラフとして表示させるための手順がb64encodeとdecodeです。base64を使用すると画像が軽くなり、ページ速度アップを図ることもできます。バッファの内容全体が代入されたimgをb64encodeし、それをutf-8にdecodeしています。

↓Output_Graph関数は以下で最後です。

    buffer.close()
    return graph

このcloseでストリームを閉じていますが、このコードがなくても正常に動作はします。ただ、明示的に閉じるのが良い習慣(=予期せぬエラーや攻撃を防ぐ)とのことです。最後にgraphを返して完了です。


graph.pyにグラフ表示用の関数を作成_Plot_Graph

続けてPlot_Graphに関してです。各コードの意味はコード内のコメントで一気にまとめます。

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

def Plot_Graph(x,y):
    plt.switch_backend("AGG")     # どの機能で描画を行うか指定している。消しても変化なしで、PDFとすると少し小さくなった。
    plt.figure(figsize=(10,5))    # グラフの外枠を定義する
    plt.bar(x,y)                  # 棒グラフを表示
    plt.xticks(rotation=45)       # X軸の「日付」を45度回転させる
    plt.title("Revenue per Date") # グラフのタイトルを定義
    plt.xlabel("Date")            # X軸のラベル
    plt.ylabel("Revenue")         # Y軸のラベル
    plt.tight_layout()            # 余白部分が自動的に調整されて、よりきれいなグラフが仕上がる
    graph = Output_Graph()        # ここまでの内容をグラフとして画像化する
    return graph                  # 完成したグラフの画像を返す

最初の2つ「switch_backend」と「figure」はややこしいので個別に触れたいと思います。

まずswitch_backendについてです。バックエンドという名前の通り、図を作成するための裏側の機能について指定しています。バックエンドには「インタラクティブバックエンド(PyQt / PySide、PyGObject、Tkinter、wxPython、またはmacOS / Cocoaで使用)」と「非インタラクティブバックエンド(PNG、SVG、PDF、PS)」の2種類あり、今回はPNGなので後者です。

レンダラー(実際に描画を行うもの)指定していることになります(以下のうちのAGGです)。


次にfigureです。これは図の外枠を定義するもので、他の要素より上に書かないとグラフが作成されません。以下の図が理解を助けてくれました。

早く知っておきたかったmatplotlibの基礎知識、あるいは見た目の調整が捗るArtistの話より抜粋


また、先に定義していたOutput_Graph関数を呼び出すことで、pyplotの内容をブラウザに表示します。この関数の中で「plt.savefig()」が呼び出され、ここまでで記述したpltの内容がpng形式でバッファに保存されます。

    graph = Output_Graph()        # ここまでの内容をグラフとして画像化する


ここまでで一旦グラフ化に使用する関数は完成です。
次回はここからどうやってViewに繋いでいるか等について解説します。


参考

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

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

【Paiza】
JavaScript体験篇       15/15講座
Aランクレベルアップメニュー 24/49問
データセット選択メニュー   4/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合格

【書籍/ブログ】
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の環境構築

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