見出し画像

【98日目】Django_matplotlibの棒グラフの見た目を整える①

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

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

2歳児(わが子)の相手って本当に大変ですね。。。
可愛くて楽しいけど、体力と気力をこれでもかってくらい削られます。

そんなこんなでやり切りたかったことは終わらず、一部を明日に持ち越すことにしました。


前回まででグラフ表示の機能自体はできたので、見た目を整えるためのコードを中心に書きました。

↓前回


↓こんな感じにしています。


↓昨日の状態よりは少し見やすくなりました



全てPlot_Graphというクラス内での設定なので、先にコード全文を載せておきます。

[graph.py]
 
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
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, width=0.8, color='C2')
    plt.xticks(rotation=45)
    # y軸の目盛りを指定(yにタスク数が入っているため、最大のタスク数+1をY軸で表示した)
    plt.yticks([i+1 for i in range(max(y)+1)])
    plt.title("完了済タスク数の推移", fontsize=15, fontname="Yu Gothic")
    # labelpad 軸からのラベルの距離 0:デフォルト位置,  正の値:軸から離して表示,  負の値:軸に近づけて表示
    plt.ylabel("タスク数", fontname="Yu Gothic", fontsize=14, color='gray', labelpad=10)
    plt.tight_layout()
    # 図の枠線を消す
    plt.gca().spines['right'].set_visible(False)
    plt.gca().spines['top'].set_visible(False)
    plt.gca().spines['left'].set_visible(False)
    # 軸の目盛りを消す(ちょこっと出ている線)
    plt.gca().yaxis.set_ticks_position('none')
    plt.gca().xaxis.set_ticks_position('none')
    # x軸の日付フォーマットを変更している。Macなら「%-m/%-d」だが、windosなら以下
    plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%#m/%#d"))
    graph = Output_Graph()
    return graph



そもそもmatplotlibでのグラフ描画には2つのスタイルがある

matplotlibでのグラフ作成において、これを理解していることがまずは重要でした。以下の2つのスタイルです。

pltメソッド:plt.plot()から始めて、plt.○○○を使用していく
オブジェクト指向:fig, ax = plt.subplots()から始めて、ax.set_○○○を使用していく
~中略~
ほとんどの場合、plt.○○○とax.set_○○○の「○○○部分は共通」です。
片方の方法を覚えれば、自然ともう一方も覚えられます!

https://www.yutaka-note.com/entry/matplotlib_axis

matplotlibのコードを解説してくださっているブログはたくさんありますが、ここを押さえておくと理解度がぐっと上がると思います。


y軸の目盛りを指定して整数に変換

変更前は小数点第一位まで表示されてしまっており、整数のデータしか入らないので無駄になってしまっていました。これを解消しているのが以下の部分です。

    # y軸の目盛りを指定(yにタスク数が入っているため、最大のタスク数+1をY軸で表示した)
    plt.yticks([i+1 for i in range(max(y)+1)])

plt.yticksでy軸の目盛りに関する設定ができます。数字が入ったリストを渡すことで、そのリスト通りに目盛りが表示される仕組みです。ここではyの最大値+1が上限値として表示されるよう、リスト内包表記でループ処理をしています。



タイトルやラベルの文字化けを直す

デフォルトの状態でタイトルやラベルに日本語を渡すと文字化けしてしまいます。修正方法は簡単で、それぞれの引数にfontnameを渡せばOKです。

    plt.title("完了済タスク数の推移", fontsize=15, fontname="Yu Gothic")
    
    # labelpad 軸からのラベルの距離 → 0:デフォルト位置,  正の値:軸から離して表示,  負の値:軸に近づけて表示
    plt.ylabel("タスク数", fontname="Yu Gothic", fontsize=14, color='gray', labelpad=10)
    

ラベルの方では、ついでに色をグレーにした(color)のと、グラフとラベルの距離を広げました(labelpad)。



グラフの外枠を消す

余計な線はノイズになるので削除しました。僕は本業でデータコンサルをしているのですが、データを見せるときに意識しているポイントの一つです。

    # 図の枠線を消す
    plt.gca().spines['right'].set_visible(False)
    plt.gca().spines['top'].set_visible(False)
    plt.gca().spines['left'].set_visible(False)

    # 軸の目盛りを消す(ちょこっと出ている線)
    plt.gca().yaxis.set_ticks_position('none')
    plt.gca().xaxis.set_ticks_position('none')

gcaはget current axesの略で、現在のAxesを取得します。Axesは「座標軸」を意味するAxisの複数形で、matplotlibでは「グラフ描画領域」という意味で使われます。

https://python.atelierkobato.com/matplotlib/



x軸に表示している日付のフォーマットを変更する

matplotlibのdatesライブラリでフォーマットを変更できます。「2022年」などの「年」情報は、上部の期間指定しているフォームで一目で分かるので非表示にします。これもデータを見せるうえでノイズを少しでも消す工夫の一つです。

import matplotlib.dates as mdates

    # x軸の日付フォーマットを変更している。Macなら「%-m/%-d」だが、windosなら以下
    plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%#m/%#d"))

やや複雑だったのと、ドンピシャな事例が少なくて実装するのに少し苦労しました。


テンプレートとコード内での「時刻」のズレを直す

これは今日の朝にタスクを「完了済」にしてグラフに表示させようとしたとき、なぜか昨日の数字が増えて今日の数字が増えなかったことで気が付きました。

タスクの最終更新日時 ≠ フォームで入力した日付になっている、と。
どちらかが何時間かズレているな、と。

色々調べると以下が見つかりました。

Djangoのdjango.utils.timezoneを利用する際の注意点として、コード内でdatetimeを利用する時にはまだローカライズされていないという点が挙げられます。

少し分かりづらいですが、例えば、テンプレートで表示されるまではUTCを基準にしたawareなdatetimeオブジェクトを扱っており、それが表示されるタイミングで適切なローカルタイムに変換されているということです。

https://djangobrothers.com/blogs/django_datetime_localtime/

最初にsettings.pyで「TIME_ZONE = 'Asia/Tokyo'」とローカルタイムを指定していますが、これが適用されるのはテンプレート上で、つまりデータがブラウザに表示されるところでのみ変わっているらしいのです。

今回日付の情報を処理しているviews.py等、コード内ではUTC(=協定世界時)で処理がされているらしく、これがズレの原因でした。

修正方法はとても簡単で、localtimeメソッドをインポートして以下のように「localtime(日時データ)」とするだけです。これでコード内でもローカライズされた日時で処理することができます。

from django.utils.timezone import localtime
 
qs = Todo.objects.all()
update_list = [localtime(ele.updated_at) for ele in qs if str(ele.status) == "完了済"]

ここではele.updated_atにUTCでの日時データ入っているので、それをlocaltimeの引数に渡せばOKです。


何とか明日中に以下2点の修正を加えたい…!

・x軸の日付の表示間隔を変えたい(指定期間によってはスカスカになる)
・各グラフの上に値を表示したい


うまくいくかなーー



参考


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

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

【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     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の環境構築

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