【Django入門】データ入力してグラフを描画する方法【Restframework+Vue-chartjs】

この記事は約32分で読めます。
広告

vue-chartjsを使うと見栄えのいいグラフが描けます。vue-chartjsをDjangoで扱うために3段階に分けて解説したいと思います。

  1. vue-chartjsをCDNを使ってメモ帳で作ってみる。
  2. VueCLIを使って単独でデータ入力して描画するアプリを作ってみる。
  3. Djangoのテンプレートとstaticで扱えるようにビルドする。さらにRestframeworkを使ってサーバーからデータを送りグラフ描画する。

完成品
以前PythonのMatplotlibを使ってDjangoでグラフを書く記事を書きました。今回はグラフ描画をフロントエンド側(Vue)に持っていきDjango(Python)ではデータ管理のみにしてみました。

この記事書いたあとに気づいたのですが
実は1番のindex.htmlをDjangoのテンプレートに入れるのが簡単です(jsファイルに入れてる場合は無理かも)。そのことを追記しました。

広告

CDNでvue-chartjsを使ってグラフ表示

まず1番簡単な方法でvue-chartjsを使ってみます。(2023年/末サポート終了のVue2で作ってます)
CDNとは難しく考えず、CDNにおいてあるファイルを<script src=”ファイル名”></script>タグで読み込んでおくだけで、いろんな機能(Vue、Chart、vue-chartjs)が使えるようになると考えればいいと思います。
実際にvue-chartjsが、どんなものか自分で体験したほうがいいので以下のindex.htmlをメモ帳に保存してプラウザで開いてみてください。(ファイルをダブルクリックまたはプラウザにドラッグアンドドロップ)
Windowsのメモ帳で保存するときは
ファイル→名前を付けて保存→ファイルの種類欄を必ず「すべてのファイル」にする→ファイル名をindex.htmlにして保存。
参考:https://www.kabanoki.net/4118/のLineサンプルだけhtmlひとつにまとめました。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style type="text/css">
        body {
        padding: 20px;
        font-family: Helvetica;
      }

      #app {
        background: #fff;
        border-radius: 4px;
        padding: 20px;
        transition: all 0.2s;
      }

      li {
        margin: 8px 0;
      }

      h2 {
        font-weight: bold;
        margin-bottom: 15px;
      }

      del {
        color: rgba(0, 0, 0, 0.3);
      }

      .box {
            width: 100%;
            float: left;
            background-color: #fff;
          }
    </style>
  </head>
  <body>
    <div id="app">
      <div class="box">
          <h2>Line</h2>
          <line-chart></line-chart>
      </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.7.1/Chart.min.js"></script>
    <script src="https://unpkg.com/vue-chartjs@3.5.1/dist/vue-chartjs.min.js"></script>
    <script>
       Vue.component('line-chart', {
          extends: VueChartJs.Line,
          mounted () {
            this.renderChart({
              labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
              datasets: [
                {
                  label: 'Data One',
                  backgroundColor: '#f87979',
                  data: [40, 39, 10, 40, 39, 80, 40]
                }
              ]
            }, {responsive: true, maintainAspectRatio: false})
          }
        })  

      new Vue({
         el: '#app',
       })
    </script>
  </body>
</html>
<script src=で3つJavascriptファイルを読み込んでいます。1個目でVueを使えるようにして、2個目でChart、3個目でvue-chartjsが使えるようになります。
このままindex.htmlをDjangoのテンプレートで使う方法もあるかなと思いました。データをJavascriptで扱っているためDjangoのテンプレートでデータを変更するのは多分無理かと思ったら、できました。

DjangoのテンプレートのJavascriptに{{}}でデータを入れてみる。

上のindex.htmlを少し修正し、こちらの記事のサンプルコードのindex.htmlと交換しviews.pyを変えるだけで確認できます。

views.py修正

from django.shortcuts import render
from django.views import View

class HajimeteView(View):
    def get(self, request, *args, **kwargs):
        context = { "a1"     : [10,20,50,40,50,100,10]
                    }
        return render(request,"hajimete_app/index.html",context)
index.html修正部分
datasets: [
                {
                  label: 'Data One',
                  backgroundColor: '#f87979',
                  <!-- data: [40, 39, 10, 40, 39, 80, 40] -->
                  data: {{a1}}
                }
              ]

これで終わってしまいますが、せっかくなのでVueで作ったフロントエンドにデータをRestframeworkで送り込んでみます。
vue-chartjs は Chart.jsというライブラリ が必要です。
また上の例はバージョンがvue/2.6.10/Chart.js/2.7.1/vue-chartjs@3.5.1です。Vueの場合このバージョン管理がうまくいってないとネットの情報と特に合わない感じがします。
そこで2022/9時点で最新のVue3でVue CLIを使って作ります。

実際に作ったもののバージョンです。package.jsonより
  "dependencies": {
    "chart.js": "^3.9.1",
    "core-js": "^3.8.3",
    "vue": "^3.2.13",
    "vue-chartjs": "^4.1.1"
  },
  "devDependencies": {
    "@babel/core": "^7.12.16",
    "@babel/eslint-parser": "^7.12.16",
    "@vue/cli-plugin-babel": "~5.0.0",
    "@vue/cli-plugin-eslint": "~5.0.0",
    "@vue/cli-service": "~5.0.0",
    "eslint": "^7.32.0",
    "eslint-plugin-vue": "^8.0.3"
  },

PaizaCloudで準備

Vueを使うにはNode.jsが必要です。Node.jsはサーバーサイドのライブラリなのでローカルでサーバーを立ち上げる必要があります。
WEB開発環境PaizaCloudだと簡単に開発環境が整いますので、それを使っていきます。PaizaCloudのやり方はこちらの記事を参考にしてください。
PaizaCloudで
新規サーバー作成→サーバー名入力
Web開発:はNode.jsとDjango選択
データベース:はPostgreSQLを選択
新規サーバー作成
目次へ

Vue(vue-chartjs)だけで作ってみる。

まずフロントエンドのみでデータ更新ボタン押したら再描画されるグラフを作ってみます。

それでは作っていきます。
PaizaCloudの左にあるターミナルアイコンをクリックしてターミナルを立ち上げます。
npm install -g @vue/cli
vue create vue-chart
defaultのままEnter 
Use NPMを↓キーで選んでEnter

vue.config.jsを修正してPaizaCloud対策する。これをやらないとInvalid Host headerというエラーになります。(~allowedHosts: "all",~の部分、注意、ビルド前には消した方がいい)
またビルドしたとき相対パスで出力するようにpublicPathの設定もしておきます。
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true
})
module.exports = {
  devServer: {
    allowedHosts: "all",
  },
  publicPath: './',
}
ディレクトリを移動して実行します。
cd vue-chart
npm run serve
で左の8080アイコンをクリック。Vueの画面が表示されればインストール成功です。
ctrl+Cで停止
vue-chartjs chart.jsをインストールします。
npm i vue-chartjs chart.js
vue-chartjsの公式ガイド?を参考にしました。
Getting Started | 📈 vue-chartjs
⚡ Easy and beautiful charts with Chart.js and Vue.js
下の2つのファイルだけでできます。
src/App.vueを以下のように書き換えます。
上記のサンプルで赤い部分だけ変えました。

<template>
  <BarChart />
</template>

<script>
import BarChart from './components/BarChart.vue'

export default {
  name: 'App',
  components: { BarChart }
}
</script>
<template>

    <div class="day">
      <p>データ: (3つの数字を半角で,で区切って入力し更新をクリック! 例10,20,30)</p>
      <input v-model="new_data1">
    </div>
      <button v-on:click="New_Data">更新</button>
  <Bar :chart-data="chartData" :chart-options="chartOptions" />
</template>

<script>
// DataPage.vue
import { Bar } from 'vue-chartjs'
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale } from 'chart.js'

ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale)

export default {
  name: 'BarChart',
  components: { Bar },
   data() {
    return {
        new_data1:[],
      chartData1: {
        labels: [ 'January', 'February', 'March'],
        datasets: [
          {
            label: 'Data One',
            backgroundColor: '#f87979',
            data: [40, 20, 12]
          }
        ]
      }
    }
  },
  computed: {
      chartData() { return this.chartData1 },
      //chartOptions() { return /* mutable chart options */ }
    },
    methods: {
        New_Data: function(){
            this.chartData1={
        labels: [ 'January', 'February', 'March'],
        datasets: [
          {
            label: 'Data One',
            backgroundColor: '#f87979',
            data: this.new_data1.split(',')
          }
        ]
      }
        }
    }
}
</script>
npm run serveで動作確認できます。

ビルドするとVueをインストールしてない状態でも使えるindex.html、Javascriptファイル、CSSファイルができます。
distフォルダにできているので、index.html、css/*.css、js/app○○○○.jsとchunk-vendors○○○○.jsの4つのファイルをコピーして使えばいいです。*.mapは不要。
ターミナルで以下のコマンドでビルドします。
npm run build

実際にサーバーにアップロードしてあるので動作確認してみてくだい。
Vue-chartjで作ったグラフ
(少しcssで見た目は整えました。グラフの大きさを変えたかったけど調べるのが大変でやめました。)

グラフの大きさが変更できないのでgridを使って見た目を整えたものも載せておきます。
Vue-chartsで作ったグラフ(Vueとgridを使ったバージョン)
今回Vue3で作っていますが
2023年末でサポートの終了するVue2で作った方がネットの情報が多くて作りやすいかもしれません。
Vue-chartsで作ったグラフ(Vutify版)
目次へ

<template>
    <div class="grid">
      <article class="main">
        <h1>Vue-chartjs<br>データ入力でグラフ描画</h1>
        <div class="newdata">
          <p>データ: (3つの数字を半角で,で区切って入力し更新をクリック! 例10,20,30)</p>
          <input v-model="new_data1">
        </div>
          <button v-on:click="New_Data">更新</button>
      </article>
      <section class="side">
          <Bar :chart-data="chartData" :chart-options="chartOptions" />
      </section>
    </div>
</template>

<script>
// DataPage.vue
import { Bar } from 'vue-chartjs'
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale } from 'chart.js'

ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale)

export default {
  name: 'BarChart',
  components: { Bar },
   data() {
    return {
        new_data1:[],
      chartData1: {
        labels: [ 'January', 'February', 'March'],
        datasets: [
          {
            label: 'Data One',
            backgroundColor: '#f87979',
            data: [40, 20, 12]
          }
        ]
      }
    }
  },
  computed: {
      chartData() { return this.chartData1 },
      //chartOptions() { return /* mutable chart options */ }
    },
    methods: {
        New_Data: function(){
            this.chartData1={
        labels: [ 'January', 'February', 'March'],
        datasets: [
          {
            label: 'Data One',
            backgroundColor: '#f87979',
            data: this.new_data1.split(',')
          }
        ]
      }
        }
    }
}
</script>
<style scoped>
.grid {
  text-align:center;
  display: grid;
  gap: 10px;
  grid-template-columns: 1fr 2fr;
}
.main {
  background: #fcc;
}
.side {
  background: #fea;
}
.main,
.side {
  padding: 2%;
  border-radius: 10px
}

@media(max-width: 420px){
  .grid {
    grid-template-columns: 1fr;
  }
}
.newdata{
    width:80%;
    margin:0 auto;
    padding:15px 0;
    margin-bottom:30px;
    background-color:whitesmoke;
}
.rdata{
    margin:20px;
    color: red;
}
input{
    padding:5px 10px;
}
button{
    width:80%;
    background-color: teal;
    color:white;
    padding:20px 0;
    border:none;
    border-radius:10px;
    cursor:pointer;
    margin-bottom: 50px;
}

</style>

ソースは載せてませんがVue-chartsで作ったグラフ(折れ線と円グラフ追加バージョン)

完成品の動作確認

次にDjangoでサーバーからデータを送れるようにします。まず完成品で動作確認してみましょう。
見た目の動作はサーバーに送られているかどうか分かりません。
https://miyamiko-django-nocli2.herokuapp.com/api/app_vchart
でDjango Restframeworkに備わっている画面が見れます。そこでデータの確認ができます。
またプラウザの開発者ツールでデータの送受信の様子が見れます。
Edgeの場合、どこか適当なところで右クリック→開発者ツール→ネットワークのタブをクリック
データを入力して「サーバーに送って更新」ボタンを押せばPUTで送っていることが分かります。具体的には名前をクリックすると通信内容が見れます。ペイロードが送信データでプレビューや回答が受信データになります。

バックエンド(Django)側作成


それでは作っていきます。まずDjangoのプロジェクトを作成します。

PaizaCloudの左にあるターミナルアイコンをクリックするとターミナルが立ち上がります。
django-admin startproject project_vchart でEnter、プロジェクトが作成されます。(以下Enterは省略)
cd project_vchart ディレクトリが移動されます
python manage.py startapp app_vchart アプリ作成

settings.pyでPaizaCloudの場合、以下変更も必要です。青色部分はCorsを無効にするための処理です。あとで説明します。
ALLOWED_HOSTS = []
→ALLOWED_HOSTS = ['*']

LANGUAGE_CODE = 'ja'
TIME_ZONE = 'Asia/Tokyo'

INSTALLED_APPS = [
・・・
    'django.contrib.staticfiles',
  'rest_framework', # 追加
    'corsheaders',#追加
    'app_vchart', #追加
]
MIDDLEWARE = [
・・・・
'corsheaders.middleware.CorsMiddleware',#追加
]
CORS_ORIGIN_ALLOW_ALL =True #追加 どこからでも受け入れるので危険な設定
# CORS_ORIGIN_WHITELIST = [ #個別に許可する場合はこっちを使う
#     'http://***.jp', 
# ]
# レスポンスを公開する
CORS_ALLOW_CREDENTIALS = True #追加
にして保存
ターミナルで
pip install djangorestframework
pip install django-cors-headers

python manage.py runserverでDjangoの動作確認。
左の8000と書いてあるアイコンをクリックすると見れます。
CTRL+Cで停止。
以下のソースファイルを入力してください。
app_vchart/models.py修正

from django.db import models

class VueChart(models.Model):
    vuedata = models.CharField('y値', max_length=100)

    def __str__(self):
        return self.vuedata
app_vchart/admin.py修正

from django.contrib import admin
from .models import VueChart

class VueChartAdmin(admin.ModelAdmin):
    pass

admin.site.register(VueChart, VueChartAdmin)
app_vchart/serializers.py追加

from rest_framework import serializers
from .models import VueChart

class VueChartSerializer(serializers.ModelSerializer):
	class Meta:
		model = VueChart
		fields = '__all__'
app_vchart/viewsets.py追加

from rest_framework import viewsets
from .models import VueChart
from .serializers import VueChartSerializer

class VueChartList(viewsets.ModelViewSet):
    queryset = VueChart.objects.all()
    serializer_class = VueChartSerializer
project_vchart/routers.py追加

from rest_framework import routers
from app_vchart.viewsets import VueChartList

router = routers.DefaultRouter()
router.register('app_vchart',VueChartList)
project_vchart/urls.py修正

from django.contrib import admin
from django.urls import path, include
from .routers import router# 追加
from django.views.generic import TemplateView # 追加

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/', include(router.urls)),# 追加
    path('app_vchart', TemplateView.as_view(template_name='app_vchart/index.html')), # 追加
]
データベース構築
python manage.py makemigrations
python manage.py migrate
管理ユーザー作成
python manage.py createsuperuser
ユーザー名、メールアドレス(空欄でEnterでOK)パスワード(入力しても反応なく見えないが、ちゃんと入力されている)
サンプルデータ作成
python manage.py runserver
左の8000アイコンをクリックしてプラウザを立ち上げたらアドレスの最後に/adminでEnterすると管理画面に入れます。
 https://localhost-それぞれのサーバー名.paiza-user-free.cloud:8000/admin
Vue chartsの+追加をクリックして半角で数字をカンマ区切りで3つ入力して保存してください。(例50,20,70)
https://localhost-それぞれのサーバー名.paiza-user-free.cloud:8000/api/app_vchart/でDRFのページに遷移します。
そこでも確認や登録POSTは可能です。

これでDjango側は一旦完成です。

詳しく書いていませんがCorsが無効になっています。これをしないと開発中Corsエラーになります。しかし最終的にDjangoのテンプレートを使うので同一オリジンになります。
なのでCors対策は不要。Vueで画面が完成したらCors対策は削除したほうが安全だと思います。今回Herokuへデプロイしたものも削除しています。Corsの詳細はこちらを参考にしてください。https://kikuichige.com/13634/#toc7
目次へ

Vue側

先ほど作ったものにAxiosを使ってデータの送受信機能を追加します。

Axiosを使っています。
CSRF対策でクッキーを付ける必要があるとき(開発中CSRFエラーになるとき)は以下のように()の最後に追加してください。今回のPaizaCloudでは開発中は必要です。ビルドする直前で削除をおすすめします。
GETの場合
axios.get(省略,{withCredentials: true})

この辺を入れてもエラーになるのはVue側とDjango側が別々のオリジン(例えば:8000と:8080)のときPOST、PUT、Deleteするとプリフライトがうまくいかずエラーになります。GETは大丈夫です。今回もPUTではエラーになります。なのでPUTの動作確認はビルド後になります。
axiosをインストールします。
npm install axios --save
修正するファイルは以下のファイル1個です。
<template>
<div class="container">
    <h1>Restframework+Vue-chartjs<br>データ入力でグラフ描画</h1>
    <div class="newdata">
      <p>データ: (3つの数字を半角で,で区切って入力し更新をクリック! 例10,20,30)</p>
      <input v-model="new_data1">
      <div class="rdata">受信データ : {{res_data.vuedata}}</div>
    </div>
      <button v-on:click="New_Data">サーバーに送らないで更新</button>
      <button v-on:click="Put_New_Data">サーバーに送って更新</button>
  <Bar :chart-data="chartData" :chart-options="chartOptions" />
</div>
</template>

<script>
// DataPage.vue
import { Bar } from 'vue-chartjs'
import { Chart as ChartJS, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale } from 'chart.js'
import axios from 'axios'
ChartJS.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale)

export default {
  name: 'BarChart',
  components: { Bar },
   data() {
    return {
        new_data1:[],
        res_data:null,
      chartData1: {
        labels: [ 'January', 'February', 'March'],
        datasets: [
          {
            label: 'Data One',
            backgroundColor: '#f87979',
            data: [40, 20, 12]
          }
        ]
      }
    }
  },
  created() {
        axios.defaults.xsrfCookieName = 'csrftoken' ;
        axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
         this.getData();
    },
  computed: {
      chartData() { return this.chartData1 },
      //chartOptions() { return /* mutable chart options */ }
    },
    methods: {
        New_Data(){
            this.chartData1={
            labels: [ 'January', 'February', 'March'],
                datasets: [
                  {
                    label: 'Data One',
                    backgroundColor: '#f87979',
                    data: this.new_data1.split(',')
                    //data: this.res_data.vuedata.split(',')
                  }
                ]
          }
        },
        Get_New_Data(){
            this.chartData1={
            labels: [ 'January', 'February', 'March'],
                datasets: [
                  {
                    label: 'Data One',
                    backgroundColor: '#f87979',
                    //data: this.new_data1.split(',')
                    data: this.res_data.vuedata.split(',')
                  }
                ]
          }
        },
        Put_New_Data(){
            axios.put('https://localhost-人それぞれ違うサーバー名.paiza-user-free.cloud:8000/api/app_vchart/1/?format=json',{ vuedata: this.new_data1 },{withCredentials: true})
                .then(response => {
                    this.res_data=response.data,
                    this.Get_New_Data()
                  })
              .catch((err) => {
               console.log(err);
              })
        },
        getData() {
            axios.get('https://localhost-人それぞれ違うサーバー名.paiza-user-free.cloud:8000/api/app_vchart/1/?format=json',{withCredentials: true})
                .then(response => {
                    this.res_data=response.data,
                    this.Get_New_Data()
                  })
              .catch((err) => {
               console.log(err);
          })
         }
    }
}
</script>
<style scoped>
.container{
    text-align:center;
}
.newdata{
    width:80%;
    margin:0 auto;
    padding:15px 0;
    margin-bottom:30px;
    background-color:whitesmoke;
}
.rdata{
    margin:20px;
    color: red;
}
input{
    padding:5px 10px;
}
button{
    width:80%;
    background-color: teal;
    color:white;
    padding:20px 0;
    border:none;
    border-radius:10px;
    cursor:pointer;
    margin-bottom: 50px;
}

</style>
Django側をpython manage.py runserver
Vue側をnpm run serveで動作確認できます。
しかしこの段階ではサーバーにデータは送れません。(プリフライトでエラーになります)
サーバーのデータは以下の画面でPUT(入れ替えできます)https://localhost-それぞれのサーバー名.paiza-user-free.cloud:8000/api/app_vchart/1
api/app_vchartの画面はPUTできませんが、最後に1を付けてapi/app_vchart/1にすることでデータをPUT、DELETE操作できます。そこでいじってみてください。

ビルドしてindex.htmlと静的ファイルをDjango側へ

参考
開発中はいいですが本番では以下は削除した方がいいと思います。
*.vueのクッキーが送られる以下の部分
,{withCredentials: true}

vue.config.jsのどのホストからも配信可能な以下の部分
  devServer: {
    allowedHosts: "all",
  },

Django側のCorsを無効にした処理。
VueをCTRL+Cで停止し
npm run build
ビルドするとvue-chart/distディレクトリにhtmlとjs、cssファイルができています。mapは不要。jsとcssのファイル名はビルドするたびに変更されるので都度以下の作業で注意してください。
dist/index.htmlをDjangoのapp_vchart/templates/app_vchart/に移動
dist/cssとjsにあるそれぞれのファイルをDjango側にapp_vchart/static/cssとjsディレクトリを作りそれぞれ移動*.mapは不要。

index.htmlを以下のように変更(メモ帳にコピペして編集するとやりやすいです)

冒頭に{% load static %}
jsファイルやcssファイルを参照しているところを{% static 'js or cssファイル' %}で囲む。
python manage.py runserverで動作確認。

あとがき

Djangoでグラフ描画に利用することを目的として作りましたがVueを結構勉強しないと応用は難しいと感じました。今回、グラフの大きさ変えるのも簡単と思いきや、それらしいwidthなどの変数いじってもうまくいきません。レスポンシブ(大きさが画面に収まるように変わる)部分はいいのですがスマフォ重視な作りになってると思います。パソコンは蚊帳の外みたいな。
あとvue、Chart.js、vue-chartjs、この3つのバージョンの組み合わせもかなり曲者です。
Vueについてもう少し調べて、記事にしたいと思います。

この記事を書いたイチゲを応援する
Vプリカでのお支払いがおすすめです。

MENTAやってます(ichige)
目次へ

コメント

タイトルとURLをコピーしました