Chart JSを利用してグラフ化してみよう

所要時間:20分

最後に、ツイッターのいいね数とリツイート数をグラフ化しましょう。

②いいね数とリツイート数のグラフを作成しよう

さて、最後に画像の②にあたる部分を作成します。 ここの部分をもう少し細かく見てみましょう。 リツイート数と、いいね数を一日ごとに計算しています。

今回は、chartjsという、JavaScriptのライブラリを利用して、グラフ表示を行っていきたいと思います。

ChartJSのインポート

ChartJSは、JavaScriptで利用できる、グラフ表示のライブラリです。

CDNでインポートします。まず、利用するためにheadタグ内にChartJSのCDNをコピーしておいてください。

https://www.chartjs.org/docs/latest/

<script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js">

Canvasタグの準備

次に、HTMLのここの部分の実装を見ていきましょう。

  <!-- chart -->
   <div class="row">
     <div class="col-md-12">
       <canvas id="chart"></canvas>
     </div>
   </div>

 

実装が必要なのは、canvasタグにidを入れた1行のみです。

のちほど、ここに対してJavaScriptで処理した値を、canvas上に出力する形になります。


 

グラフの実装

そこの部分は、bodyタグの下の部分で実装されているので、見てみましょう。ちょっと長いですが、最終的には以下のようなコードになります。

<script>
 {% if profile %}
  // bar chart data
  var barData = {
  labels : [{% for index, row in tweets_df.sort_values(by="created_at", ascending=True).iterrows() %}
                 "{{row['created_at']}}",
             {% endfor %}],
  datasets : [
     {
           label: "Retweets",
           backgroundColor: 'rgba(255, 99, 132, 0.2)',
           borderColor: 'rgba(255,99,132,1)',
           borderWidth:10,
           bezierCurve : false,
           data : [{% for index, row in tweets_df.sort_values(by="created_at", ascending=True).iterrows() %}
                     {{row["retweets"]}},
                   {% endfor %}]
     },{
           label: "Favorites",
           data : [{% for index, row in tweets_df.sort_values(by="created_at", ascending=True).iterrows() %}
                     {{row["fav"]}},
                   {% endfor %}],
           type: 'line',
           borderColor: 'rgb(63, 127, 191)',
     }
     ]
  }
  // draw bar chart
 var mychart = document.getElementById("chart");
 var chart = new Chart(mychart, {
   type:'bar',
   data:barData,
   options: {
       scales: {
         yAxes: [
           {
             ticks: {
               beginAtZero: true,
               min: 0,
               max: 1000
             }
           }
         ]
       }
     }
 });
  {% endif %}
</script>

これを理解するために、まずはChartJSの仕様について理解していきましょう。

全体としては、

  1. グラフのタイプ(棒グラフetc)の設定
  2. 挿入するデータの設定
  3. オプションの設定

となります。コードにすると、以下の通りです。

var ctx = document.getElementById("myChart"); //canvas要素の取得
var myChart = new Chart(ctx, {
	type: タイプ, //描画するグラフの種類
	data: データ, //ラベルとデータセット
	options: オプション //オプション設定
});

type:グラフの種類 

リファレンスに詳しく書いてありますが、グラフのtypeには、以下のような値を入れることができます。

https://www.chartjs.org/docs/latest/charts/

bar 棒グラフ
line 折れ線グラフ
rader レーダーチャート

今回利用するのは棒グラフのため、barを利用します。

グラフに表示するデータ data

次に、グラフに表示するデータについての設定を行います。

dataには、大きく分けて

  1. label:X軸のデータ。今回の場合だと日付にあたります。
  2. datasets:Y軸のデータ。今回の場合だといいね数、リツイート数にあたります。

があります。

labelは、X軸のデータです。以下のコードのように、X軸に出力する値をリスト形式でいれていきます。

labels : ["1/1", "1/2", "1/3", "1/4"] //X軸に出力する値をリスト形式で代入する

created_atで日付のデータを取得できているので、tweetsにたいしてfor文で、一つ一つcreated_atを出力し、配列として挿入します。

FlaskのHTML内でfor文を利用する場合には、{% for %} {% endfor %}で囲ってあげる必要があります。

実際に実装する場合は、以下のようなコードになります。

  var barData = {
  labels : [{% for index, row in tweets_df.sort_values(by="created_at", ascending=True).iterrows() %}
                 "{{row['created_at']}}",
             {% endfor %}],

 

datasets

datasetsは、グラフ上で表示するデータを扱います。今回は、datasetが以下の2つあります。

  • いいね
  • リツイート

を扱います。それぞれ別のデータなので、datasetsに配列として入れます。

棒グラフのdatasetとして利用できる値としては、以下のドキュメントに詳しく記載されています。

https://www.chartjs.org/docs/latest/charts/bar.html

Name Type Scriptable Indexable Default
backgroundColor Color Yes Yes 'rgba(0, 0, 0, 0.1)'
borderColor Color Yes Yes 'rgba(0, 0, 0, 0.1)'
borderSkipped string Yes Yes 'bottom'
borderWidth number|object Yes Yes 0
data object[] - - required
hoverBackgroundColor Color - Yes undefined
hoverBorderColor Color - Yes undefined
hoverBorderWidth number - Yes 1
label string - - ''
order number - - 0
xAxisID string - - first x axis
yAxisID string - - first y axis
  datasets : [
     {
           label: "Retweets", //ラベル名
           backgroundColor: 'rgba(255, 99, 132, 0.2)', //背景色
           borderColor: 'rgba(255,99,132,1)', //枠線の色
           borderWidth:10, //枠線の太さ
           bezierCurve : false,
           data : [{% for index, row in tweets_df.sort_values(by="created_at", ascending=True).iterrows() %}
                     {{row["retweets"]}},
                   {% endfor %}]
     },{
           label: "Favorites",
           data : [{% for index, row in tweets_df.sort_values(by="created_at", ascending=True).iterrows() %}
                     {{row["fav"]}},
                   {% endfor %}],
           type: 'line',
           borderColor: 'rgb(63, 127, 191)',
     }
     ]
  }

で、グラフ描画の部分がこちらですね。

 // draw bar chart
 var mychart = document.getElementById("chart");
 var chart = new Chart(mychart, {
   type:'bar',
   data:barData,
   options: {
       scales: {
         yAxes: [
           {
             ticks: {
               beginAtZero: true,
               min: 0,
               max: 1000
             }
           }
         ]
       }
     }
 });

縦軸の上限を1000に設定したいです。というのも、ユーザーを単純に比較したいので。その場合はoptionsで設定してあげます。

options: {
       scales: {
         yAxes: [
           {
             ticks: {
               beginAtZero: true,
               min: 0,
               max: 1000
             }
           }
         ]
       }
     }

こんな感じでやると、グラフが描画できるはずです。

再度flaskを起動して、特定のアカウントのIDを入れると完成です。

最終的なコードをこちらにまとめておきます。

index.html

<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Toptweets</title>
        <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>
        <link rel="stylesheet" href="/static/css/style.css">
        <link rel="stylesheet" href="https://pro.fontawesome.com/releases/v5.10.0/css/all.css" integrity="sha384-AYmEC3Yw5cVb3ZcuHtOA93w35dYTsvhLPVnYs9eStHfGJvOvKxVfELGroGkvsg+p" crossorigin="anonymous"/>
        <!--[if lt IE 9]>
               <script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>;;
               <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>;;
        <![endif]-->
    </head>
     <body class="bg-gray">
        <main>
            <div class="container">
    
                <!-- 検索フォーム  -->
                <div class="row">
                    <div class="col-md-12 p-2">
                        <h1><a href="/" class="no-decoration">Toptweets</a></h1>
                        <p>ツイッターのIDを入れると、直近のいいね数とリツイート数、最もリツイートされたツイートが一覧できます。</p>
                        <form class="form-inline" method="post">
                            <div class="form-group">
                              <label class="sr-only" for="screen_name"></label>
                              <div class="input-group mb-3">
                                 <span class="input-group-text">@</span>
                                 <input id="screen_name" name="screen_name" placeholder="ここにツイッターのIDを入力してください。" type="text" class="form-control">
                              </div>
                              <button type="submit" class="btn btn-primary">取得する</button>
                            </div>
                        </form>
                    </div><!-- col-md-12 -->
                </div><!-- row -->
                
                {% if profile %}
                 <!-- chart -->
                <div class="row mb-3">
                    <div class="col-md-12">
                        <h2 class="mb-3">{{profile.screen_name}}さんの最近の投稿パフォーマンス</h2>
                        <canvas id="chart"></canvas>
                    </div>
                </div><!-- row -->
                {% endif %}
                
                {% if profile %}
                <div class="row">
        
                    <!-- プロフィール -->
                    <div class="col-md-4">
                        <div class="bg-white p-5">
                            <div class="pb-3 border-bottom">
                                <img src="{{profile.image}}" width="100px">
                                <a href="https://twitter.com/{{profile.screen_name}}">@{{profile.screen_name}}</a>
                                <div class="pt-3">{{profile.description}}</div>
                            </div>
                            <div class="pt-3">
                                {% for index, row in grouped_df.iterrows() %}
                                <div>{{index}} 
                                    <span class="float-end">
                                        <i class="fas fa-retweet" style="color: limegreen;"></i>{{row["retweets"]}} 
                                        <i class="fa fa-heart pr-2" style="color: hotpink;"></i> {{row["fav"]}}
                                    </span>
                                </div>
                                {% endfor %}
                            </div>
                        </div>
                    </div><!-- col-md-4 -->    
                    
                    <!-- ツイート一覧 -->
                    <div class="col-md-8">
                        <h2 class="mb-3">もっともリツイート・いいねされたツイート</h2>
                        {% for index, row in sorted_df.iterrows() %}
                            <div class="bg-white p-3 border-bottom mb-3">
                                <div>{{row["created_at"]}}</div>
                                <div class="tweet-text">{{row["text"]}}</div>
                                <div class="">
                                    <i class="fas fa-retweet" style="color: limegreen;"></i><span class="pr-2">{{ row["retweets"] }}</span>
                                    <i class="fa fa-heart" style="color: hotpink;"></i><span class="pr-2">{{ row["fav"] }}</span>
                                </div>
                            </div>
                        {% endfor %}
                    {% endif %}
                    </div><!-- col-md-8 -->
                </div><!-- row -->
            </div><!-- container -->
        <! -- ここがBootstrapのCDN -->
      <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
      <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.1/js/bootstrap.min.js"></script>
      <script src="https://cdn.jsdelivr.net/npm/chart.js@2.9.4/dist/Chart.min.js"></script>
      <!-- chartjs -->
      <script>
        {% if profile %}
        // bar chart data
        var barData = {
        labels : [{% for index, row in tweets_df.sort_values(by="created_at", ascending=True).iterrows() %}
                     "{{row['created_at']}}",
                 {% endfor %}],
        datasets : [
         {
               label: "リツイート数",
               backgroundColor: 'rgba(255, 99, 132, 0.2)',
               borderColor: 'rgba(255,99,132,1)',
               borderWidth:10,
               bezierCurve : false,
               data : [{% for index, row in tweets_df.sort_values(by="created_at", ascending=True).iterrows() %}
                         {{row["retweets"]}},
                       {% endfor %}]
         },{
               label: "いいね数",
               data : [{% for index, row in tweets_df.sort_values(by="created_at", ascending=True).iterrows() %}
                         {{row["fav"]}},
                       {% endfor %}],
               type: 'line',
               borderColor: 'rgb(63, 127, 191)',
         }
         ]
        }
        // draw bar chart
        var mychart = document.getElementById("chart");
        var chart = new Chart(mychart, {
        type:'bar',
        data:barData,
        options: {
           scales: {
             yAxes: [
               {
                 ticks: {
                   beginAtZero: true,
                   min: 0,
                   max: 1000
                 }
               }
             ]
           }
         }
        });
        {% endif %}
        </script>
    </main>
    </body>
</html>

app.py

from flask import Flask, render_template, request
import os
from dotenv import load_dotenv

import tweepy
import pandas as pd

app = Flask(__name__)

load_dotenv()

CONSUMER_KEY = os.getenv("CONSUMER_KEY")
CONSUMER_SECRET = os.getenv("CONSUMER_SECRET")
ACCESS_TOKEN = os.getenv("ACCESS_TOKEN")
ACCESS_SECRET = os.getenv("ACCESS_SECRET")

auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET)
auth.set_access_token(ACCESS_TOKEN, ACCESS_SECRET)

api = tweepy.API(auth)
@app.route('/', methods = ["GET" , "POST"]) 
def index():
    if request.method == 'POST':
        screen_name = request.form['screen_name'] 
        tweets_df = get_tweets_df(screen_name)
        return render_template(
            'index.html',
            profile=get_profile(screen_name),
            tweets_df = tweets_df,
            grouped_df = get_grouped_df(tweets_df),
            sorted_df = get_sorted_df(tweets_df)
        )
    return render_template('index.html') 

def get_tweets_df(screen_name):
   columns = [
      "tweet_id",
      "created_at",
      "text",
      "fav",
      "retweets"
      ]
   tweets_df = pd.DataFrame(columns=columns) #1
   for tweet in tweepy.Cursor(api.user_timeline,screen_name = screen_name, exclude_replies = True).items(): #2
       try:
           if not "RT @" in tweet.text: #3
               se = pd.Series([ #4
                       tweet.id,
                       tweet.created_at,
                       tweet.text.replace('\n',''),
                       tweet.favorite_count,
                       tweet.retweet_count
                   ]
                   ,columns
                   )
               tweets_df = tweets_df.append(se,ignore_index=True) #5
       except Exception as e:
           print (e)
   tweets_df["created_at"] = pd.to_datetime(tweets_df["created_at"]) #6
   return tweets_df

def get_profile(screen_name):
   user = api.get_user(screen_name= screen_name)
   profile = {
       "id": user.id,
       "screen_name": screen_name,
       "image": user.profile_image_url,
       "description": user.description
   }
   return profile

def get_grouped_df(tweets_df):
    grouped_df = tweets_df.groupby([tweets_df["created_at"].dt.date]).sum().sort_values(by="created_at", ascending=False)
    return grouped_df

def get_sorted_df(tweets_df):
   sorted_df = tweets_df.sort_values(by="retweets", ascending=False)
   return sorted_df

if __name__ == '__main__':
    app.run(host = '0.0.0.0', port = 50000, debug=True)

style.css

.bg-gray{
    background-color:#e6ecf0;
}
.bg-white{
 background-color:#ffffff;
}
.no-decoration{
 text-decoration: none;
}

.env

CONSUMER_KEY=xxxxx
CONSUMER_SECRET=xxxxx
ACCESS_TOKEN=xxxxx
ACCESS_SECRET=xxxxx

 

おめでとうございます!🎉
このチュートリアルはこれで終了です。
次のコースを探す