見出し画像

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

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

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

matplotlibを活用した棒グラフ作成が一旦完了しました。
これまでの一連の作業は【91日目~98日目】の記事でまとめています。

↓前回である98日目です。


このグラフ化は以下のような目的でスタートした取り組みでした。


縦軸が【完了済タスクの数】で、横軸が【最終更新日】のグラフ
・完了日がバラついている人=
 マルチタスクせず一つずつ終わらせている?
 タスクを細分化している?
・完了日が固まっている人=
 複数のタスクを並行して進めている?
 タスクを大雑把にまとめている?

パフォーマンスが良い人がどういうグラフになっているかで、仕事の進め方の参考になりそうです。



最終的に以下のような画面になりました。

ちなみにトップページからは「データ分析」というボタンをクリックしてアクセスする動線にしています。


このうち今回の記事でご紹介するのは、棒グラフの上に値ラベルを表示するという点です(各グラフの上の数字)。


棒グラフの上に値ラベルを表示するには?

普段パワポやエクセルで作業しているとこのくらいは一瞬で実装できると思っていましたが、matplotlibでやろうとすると少し複雑でした。処理の流れとしては以下です。

①グラフに注釈をつける関数(anotateメソッド)を作る
②グラフ作成の関数内で①を呼び出す

以下がコード全文ですが、このうち「autolabel」が①で「Plot_Graph」が②に該当します。

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


def autolabel(rects):
    for rect in rects:
        height = rect.get_height()
        if height != 0:
            # matplotlib.pyplot.annotate(text, xy, *args, **kwargs)    https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.annotate.html
            plt.annotate('{}'.format(height),
                        xy=(rect.get_x() + rect.get_width()/2, height),
                        xytext=(0, 1),
                        textcoords="offset points",
                        ha='center', va='bottom', fontsize=10, fontname="Yu Gothic")


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,4.5))
    # 棒グラフを描画
    rects = plt.bar(x,y, width=0.8, color='skyblue')
    # x軸の設定
    plt.xticks(rotation=60, fontname="Yu Gothic")
    # y軸の目盛りを指定(今回は文字は非表示にするため色をwhiteにした)(yにタスク数が入っているため、最大のタスク数+1をY軸で表示した)
    plt.yticks([i+1 for i in range(max(y)+1)], fontname="Yu Gothic", color='white')
    plt.title("完了済タスク数の推移", fontsize=15, fontname="Yu Gothic")
    # 図の枠線を消す
    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」だが、windowsなら
    plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%#m/%#d"))

    autolabel(rects)

    plt.tight_layout()

    graph = Output_Graph()
    return graph


autolabel関数について一つずつ説明していきます。

まず、仮引数のrectsには棒グラフの情報が渡されてくる想定です。この関数が呼び出されてるPlot_Graph関数で、rects = plt.bar(x,y, width=0.8, color='skyblue')としていることからも分かります。つまりループ処理で取り出している「rect」にはx軸とy軸の情報が入っています。

def autolabel(rects):
    for rect in rects:

処理に入る前にget_height()で「画像の高さ」を取得します。これが後ほど表示する値ラベルの数字と、表示する位置の高さの情報になります。

def autolabel(rects):
    for rect in rects:
        height = rect.get_height()

値ラベルをつける(=注釈をつける)のを「高さが0ではない」時に限定するために条件分岐を入れています。これをしないと0が表示されてしまって見た目が悪くなるためです。

def autolabel(rects):
    for rect in rects:
        height = rect.get_height()
        if height != 0:

↓条件分岐しないと、こんな感じで0が表示されてしまいます。


次からが本題のannotateメソッドです。第一引数はtextで、表示するテキスト情報を指定します。ここではformat関数を使ってheightに代入されている数字(=y軸の高さ)をテキストとして渡してます。

def autolabel(rects):
    for rect in rects:
        height = rect.get_height()
        if height != 0:
            # matplotlib.pyplot.annotate(text, xy, *args, **kwargs)    https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.annotate.html
            plt.annotate('{}'.format(height),

第二引数はテキストを表示する場所を定義する「xy」です。
x軸は、「get_x()」でグラフのx軸の位置を取得し、それにグラフの幅の半分を足し算することで、グラフの真ん中にテキストが表示されるようにしています。グラフの幅は「get_width()」で取得しています。
y軸は、heightに入っているグラフの高さをそのまま使っています。

def autolabel(rects):
    for rect in rects:
        height = rect.get_height()
        if height != 0:
            # matplotlib.pyplot.annotate(text, xy, *args, **kwargs)    https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.annotate.html
            plt.annotate('{}'.format(height),
                        xy=(rect.get_x() + rect.get_width()/2, height),

続けてテキストの表示位置を詳細に設定する「xytext」です。(x, y)で指定していて、x軸に正の値を入れると右に、負の値では左に移動します。y軸に正の値を入れると上に、負の値では下に移動します。

def autolabel(rects):
    for rect in rects:
        height = rect.get_height()
        if height != 0:
            # matplotlib.pyplot.annotate(text, xy, *args, **kwargs)    https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.annotate.html
            plt.annotate('{}'.format(height),
                        xy=(rect.get_x() + rect.get_width()/2, height),
                        xytext=(0, 1),

次の「textcoords」は今回の値ラベル(=注釈)をつけるために必要なコードなのですが、役割は完全には理解しきれませんでした。ただこのコードを外すと値ラベルが表示されなくなったのと、「offset points」以外を指定すると変な位置にズレてしまったことから、位置に関する指定を行っていると思われます。

いずれにせよ「こういうもの」と思って書いておく必要がありそうです。

def autolabel(rects):
    for rect in rects:
        height = rect.get_height()
        if height != 0:
            # matplotlib.pyplot.annotate(text, xy, *args, **kwargs)    https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.annotate.html
            plt.annotate('{}'.format(height),
                        xy=(rect.get_x() + rect.get_width()/2, height),
                        xytext=(0, 1),
                        textcoords="offset points",

最後は値ラベルの書式や位置に関する細かい設定です。

def autolabel(rects):
    for rect in rects:
        height = rect.get_height()
        if height != 0:
            # matplotlib.pyplot.annotate(text, xy, *args, **kwargs)    https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.annotate.html
            plt.annotate('{}'.format(height),
                        xy=(rect.get_x() + rect.get_width()/2, height),
                        xytext=(0, 1),
                        textcoords="offset points",
                        ha='center', va='bottom', fontsize=10, fontname="Yu Gothic")


少し複雑な処理で、かつ日本語の紹介記事があまり見当たらなかったので少し細かくまとめてみました。

皆さんの理解を少しでも助けられれば幸いです。

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



参考


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

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

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