0%

Nuxtプロジェクトの作成

公式ドキュメントに従ってNuxt.jsをインストールしていく。
下記の前提条件は満たしているものとする。

前提条件

create-nuxt-app

プロジェクト名は仮に「nuxt-estat」とする。

1
$ npx create-nuxt-app nuxt-stat

インストール時の設定は次のとおり。
ここで、TypeScriptを選択することに注意。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
create-nuxt-app v3.7.1
✨ Generating Nuxt.js project in nuxt-resas
? **Project name:** nuxt-resas
? **Programming language:** TypeScript
? **Package manager:** Yarn
? **UI framework:** Vuetify.js
? **Nuxt.js modules:** (Press **<space>** to select, **<a>** to toggle all, **<i>** to invert selection)
? **Linting tools:** (Press **<space>** to select, **<a>** to toggle all, **<i>** to invert selection)
? **Testing framework:** None
? **Rendering mode:** Universal (SSR / SSG)
? **Deployment target:** Static (Static/Jamstackhosting)
? **Development tools:** (Press **<space>** to select, **<a>** to toggle all, **<i>** to invertselection)
? **What is your GitHub username?** [ここにGitHubアカウント]
? **Version control system:** Git

Vue/composition-apiの導入

プロジェクトのディレクトリに移動し、@vue/composition-apiをインストール。

1
2
$ cd nuxt-resas
$ yarn add @vue/composition-api

plugins/composition-api.tsを作成。

composition-api.tslink
1
2
3
4
import Vue from 'vue';
import VueCompositionApi from '@vue/composition-api';

Vue.use(VueCompositionApi);

nuxt.config.jsでプラグインを有効化。

nuxt.config.js#L36-L37link
36
37
plugins: [
{ src: '@/plugins/composition-api', ssr: true, },

Nuxt/composition-apiの導入

@nuxtjs/composition-apiをインストールする。

1
$ yarn add @nuxtjs/composition-api

nuxt.config.jsに@nuxtjs/composition-apiを追加する。

nuxt.config.js#L49-L53link
49
50
51
52
53
buildModules: [
'@nuxt/typescript-build',
'@nuxtjs/vuetify',
'@nuxtjs/composition-api/module',
],

Nuxt2とNuxt/composition APIの違い

composition APIを導入することで書き方がどう変わるのか。
こちらの記事で利用した、Highchartsでグラフを作成するコンポーネントで比較してみる。

Nuxt2の場合:

ColumnChart.vuelink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
<template>
<div class="graph">
<highcharts :options="chartOptions" />
</div>
</template>

<script>
import { cloneDeep } from 'lodash'

export default {
props: {
displayData: {
type: Array,
required: true,
},
},
data() {
return {
colors: [
'#058DC7',
'#50B432',
'#ED561B',
'#DDDF00',
'#24CBE5',
'#64E572',
'#FF9655',
'#FFF263',
'#6AF9C4',
],
}
},
computed: {
series() {
const series = cloneDeep(this.displayData)
return series.reduce((acc, cur, i) => {
cur['color'] = this.colors[i]
acc.push(cur)
return acc
}, [])
},
chartOptions() {
return {
chart: {
height: 280,
zoomType: 'xy',
type: 'column',
},
title: {
text: null,
},
xAxis: {
min: 1990,
max: 2020,
scrollbar: {
enabled: true,
},
crosshair: true,
},
yAxis: {
opposite: false,
title: {
text: '',
},
},
plotOptions: {
series: {
pointWidth: 12,
animation: false,
label: {
connectorAllowed: false,
},
},
column: {
stacking: 'normal',
},
},
legend: {
enabled: false,
},
tooltip: {
pointFormat:
'<span style="color:{series.color}">{series.name}</span>: <b>{point.y}{point.unit}</b> ({point.percentage:.0f}%)<br/>',
shared: true,
},
credits: {
enabled: false,
},
series: this.series,
}
},
},
}
</script>

<style lang="sass" scoped>
.graph
margin-top: 10px
height: 100%
</style>

composition APIの場合:

ColumnChart.vuelink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
<template>
<div class="graph">
<highcharts :options="chartOptions" />
</div>
</template>

<script lang="ts">
import { defineComponent, ref, computed } from '@nuxtjs/composition-api'
import { cloneDeep } from 'lodash'

type Series = {
name: string
data: {
x: number
y: number
unit: string
}
color: string
}

export default defineComponent({
props: {
displayData: {
type: Array,
required: true
}
},
setup(props) {
const colors = ref<string[]>([
'#058DC7',
'#7dbae5',
'#ff69b4',
'#DDDF00',
'#24CBE5',
'#64E572',
'#FF9655',
'#FFF263',
'#6AF9C4'
])

const colorList = computed((): string[] => {
const i = props.displayData.length
const c: string[] = cloneDeep(colors.value)
if (i === 1) {
return c.slice(0, 1)
} else {
return c.slice(1, c.length - 1)
}
})

const series = computed((): Series[] => {
const d: Series[] = props.displayData
const c = colorList.value

return d.reduce((acc, cur, i) => {
cur['color'] = c[i]
acc.push(cur)
return acc
}, [])
})

const chartOptions = computed(() => {
return {
chart: {
height: 280,
zoomType: 'xy',
type: 'column'
},
title: {
text: null
},
xAxis: {
min: 1990,
max: 2020,
scrollbar: {
enabled: true
},
crosshair: true
},
yAxis: {
opposite: true,
title: {
text: ''
}
// labels: {
// formatter() {
// return this.value.toLocaleString()
// }
// }
},
plotOptions: {
series: {
pointWidth: 12,
animation: false,
label: {
connectorAllowed: false
}
},
column: {
stacking: 'normal'
}
},
legend: {
enabled: false
},
tooltip: {
pointFormat:
'<span style="color:{series.color}">{series.name}</span>: <b>{point.y}{point.unit}</b> ({point.percentage:.0f}%)<br/>',
shared: true
},
credits: {
enabled: false
},
series: series.value
}
})
return {
chartOptions
}
}
})
</script>

<style lang="sass" scoped>
.graph
margin-top: 10px
height: 100%
</style>

defineComponent

v2.x のときは、Vue.extendが必要だったが、Composition API では、defineComponent に変更された。

1
2
3
4
5
<script lang="ts">
import { defineComponent } from '@vue/composition-api';

export default defineComponent({});
</script>

props → props

親コンポーネントから子コンポーネントへの渡し方は:users="data.users"のように渡す。親コンポーネントはComposition APIによる違いはない。

子コンポーネントでpropsの値を使って何らかの処理をするにはsetupの第1引数からpropsを取得して操作する。

setup関数

今まではdataプロパティやmethods、createdのようなライフサイクルフックなどそれぞれ分けて定義していたが、Composition APIではすべてsetup関数の中で定義する。

data → ref, reactive

dataはComposition APIでrefあるいはreactiveで表現される。
refはプリミティブな値を管理し、reactiveはオブジェクトや配列を管理する。
ただし、refにオブジェクトや配列を渡すと、内部でreactiveが呼ばれるため問題なく使える。

computed → computed

computed(算出プロパティ)はsetup関数のなかで呼び出す。
今まではthis.usersのようにthisを経由してもとになる値を参照していたが、 Composition APIではsetup関数内に定義された変数をそのまま参照する。

e-Stat-APIとは

政府統計の総合窓口e-Statで公表されている統計データを取得できるAPI

ユーザ登録

利用するためにはユーザ登録してアプリケーションIDを取得する必要がある

API機能を利用する際に、アプリケーションIDを送信する必要があります。
政府統計の総合窓口(e-Stat)のマイページにログインし、API機能(アプリケーションID発行)から、開発するアプリケーションごとにアプリケーションIDを取得してください。
アプリケーションID取得時に入力する名称、URL、概要は後から変更しても構いません。また、URLについては、公開サイトで利用しない場合は、ローカルアドレス(「http://test.localhost/」等)を入力してください。

クレジット表示

API機能を使用したサービスを公開する場合は、下記のクレジット表示が必要。

使い方

公式サイトにあるとおり、リクエストURLからデータを取得します。

1
2
3
4
5
// XMLの場合
http://api.e-stat.go.jp/rest/<バージョン>/app/getStatsData?<パラメータ群>

// JSONの場合
http://api.e-stat.go.jp/rest/<バージョン>/app/json/getStatsData?<パラメータ群>

例えば東京の老年人口割合[65歳以上人口]を取得したい場合、リクエストパラメータは下記のとおりとなる。

1
http://api.e-stat.go.jp/rest/2.0/app/getStatsData?appId=<アプリケーションID>&statsDataId=C0020050213000&cdCat01=%23A03503

estat-APIを利用する準備

estatの利用者登録とappIdの取得

公式サイト新規登録から利用者登録を完了する。

appIdを環境変数として利用

プロジェクト直下に.envファイルを作成して、取得したAPIキーをESTAT_APPIDとして記載しておく。

1
ESTAT_APPID = '○○○○○○○○○○○○○○○○○'

Nuxtv2.13以降ならdotenvを利用しなくても、Nuxtの標準機能で環境変数を利用できる。

公式サイトにあるとおり、**nuxt.config.jspublicRuntimeConfig**を定義する。

1
2
3
publicRuntimeConfig: {
ESTAT_APPID: process.env.ESTAT_APPID,
},

環境変数は$configのグローバルに定義されるので、どこからでも呼び出し可能。

GitHub Actionsで環境変数を利用

GitHub Actionsでビルドする場合、プロジェクト内で設定した.envは反映されないので、別途設定する必要がある。

GitHubのリポジトリにseacretを設定する

公式ドキュメントを参考に、SECRET 情報を登録する。

  1. リポジトリのメインページからSettings項目を選択し、設定ページに飛ぶ。
  2. 左のサイドバーからSecretsを押下する。
  3. New repository secretボタンを押下する。
  4. Nameに SECRET 情報の名前ESTAT_APPIDを、Valueに SECRET 情報を入力する。
  5. Add secretボタンを押下し、SECRET 情報を登録する。

workflowでseacretを読み込む

ワークフローのbuildセクションで、登録したSECRET 情報を呼び出す。
※詳しくは公式ドキュメント参照

netlify.yml#L8-L12link
8
9
10
11
12
jobs:
build:
runs-on: ubuntu-18.04
env:
RESAS_API_KEY: ${{ secrets.RESAS_API_KEY }}

nuxtjs/axiosのインストールと設定

APIを利用する場合、Promise ベースの HTTP クライアントである「axios」を利用することが多い。

ここでは、nuxt.js用のブラグイン「nuxt/axios」をインストールする。

インストール

1
yarn add @nuxtjs/axios

nuxt.config.jsで@nuxtjs/axiosを有効化する。

nuxt.config.js#L57-L59link
57
58
59
modules: [
"@nuxtjs/axios",
],

estat-APIを利用するときの共通処理

公式ドキュメントに従って、axiosの共通処理をプラグイン化する。

プラグインの作成

Nuxtプロジェクト内にplugins/estat.jsを作成。プラグインの詳細は公式ドキュメント参照。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import qs from 'qs';

export default function ({ $axios, $config }, inject) {
const api = $axios.create({
headers: {
common: {
Accept: 'application/json',
},
'Content-Type': 'application/json',
},
params: {
appId: $config.ESTAT_APPID,
},
paramsSerializer: (params) => {
return qs.stringify(params, { arrayFormat: 'comma' });
},

data: {},
})

api.setBaseURL(`https://api.e-stat.go.jp/rest/3.0/app/json/getStatsData`)

inject('estat', api)
}

パラメータの初期設定

axiosのリクエストパラメータのうち、共通で利用する値をセットする。
ここでは、appIdの値を環境変数から取得してセットしている。

estat.js#L11-L13link
11
12
13
params: {
appId: $config.ESTAT_APPID,
},

paramsSerializerオプションの設定

このままでは、例えばパラメータをcdArea:['28000',28100','28200']とした場合に、下記のクエリパラメータが生成されるため、エラーとなる。

1
cdArea[]=28000&cdArea[]=28100&cdArea[]=28200

そこで、qsというライブラリを利用する。

estat.js#L14-L16link
14
15
16
paramsSerializer: (params) => {
return qs.stringify(params, { arrayFormat: 'comma' });
},

この結果、クエリパラメータは次のとおり生成される。

1
cdArea=28000&%2C28100&%2C28200

なお、qsを利用すると他の形にも生成可能。詳細は公式サイト参照。

1
2
3
4
5
6
7
8
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' })
// 'a[0]=b&a[1]=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' })
// 'a[]=b&a[]=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' })
// 'a=b&a=c'
qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'comma' })
// 'a=b,c'

axios/proxyを利用してCORS回避

結論から先に言うと、このままではeStat-APIのデータを取得できない。
下記のとおり、CORSのエラーが発生する。

1
Access to XMLHttpRequest at 'https://opendata.resas-portal.go.jp/?statsDataId=0000010101&cdArea=28000&cdCat01=A1101' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

そこで、proxyを利用してこのCORSエラーを回避する。

nuxt.config.jsでaxios/proxyを有効化して、proxy設定を追加する。

nuxt.config.js#L84-L95link
84
85
86
87
88
89
90
91
92
93
94
95
axios: {
proxy: true
},

proxy: {
'/json/': {
target: 'http://api.e-stat.go.jp/rest/3.0/app/json',
pathRewrite: {
'^/json/': '/',
},
},
},

これで、http://localhost:3000/json/へのアクセスがhttps://api.e-stat.go.jp/rest/3.0/app/json/にリダイレクトされるので、
plugins/estat.jsのBaseURLは次のとおり修正する。

api.setBaseURL(http://localhost:3000/json/getStatsData`)

開発環境と実行環境

このままでは、実行環境でもhttp://localhost:3000/にアクセスしてしまうので、環境変数SITE_URLを設定して、開発環境と実行環境で使い分ける。

開発環境のURLは.envに追記

1
SITE_URL = 'http://localhost:3000/'

実行環境のURLは、前述したとおりGitHubのseacretを設定する。

共通関数として利用

これまでをまとめると、最終的にplugins/estat.jsは次の形になる。

estat.jslink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import qs from 'qs';

export default function ({ $axios, $config }, inject) {
const api = $axios.create({
headers: {
common: {
Accept: 'application/json',
},
'Content-Type': 'application/json',
},
params: {
appId: $config.ESTAT_APPID,
},
paramsSerializer: (params) => {
return qs.stringify(params, { arrayFormat: 'comma' });
},

data: {},
})

api.setBaseURL(`${$config.SITE_URL}json/getStatsData`)

inject('estat', api)
}

最後に、injectで関数を共通処理化している。これで別のコンポーネントからthis.$estatで呼び出すことができる。

estat.js#L23link
23
inject('estat', api)

プラグインの有効化

nuxt.config.jsでプラグインを有効化。

nuxt.config.js#L38link
38
{ src: '@/plugins/estat', ssr: true, },

Nuxt.jsでeStat-APIのデータを取得

axiosの基本的な使い方(GET)

公式ドキュメントにあるとおり、クエリパラメータを指定する方法が2種類ある。

axios.getに指定するURLに直接記述する方法

1
axios.get('/user?ID=12345')

axios.getの第2引数に、オプション指定する方法

1
2
3
4
5
axios.get('/user', {
params: {
ID: 12345
}
})

ここでは、②の方法を利用する。

estat-API概要

政府統計の総合窓口eStatから入手したい統計表データを検索し、APIを取得する。
今回は社会・人口統計体系 A 人口・世帯より、都道府県の総人口データを取得する。

パラメータの設定

兵庫県の総人口データを取得する場合、パラメータは次のとおりとなる。

A1101が総人口、A110101が男性人口、A110102が女性人口。

PopulationPref.vue#L64-L70link
64
65
66
67
68
69
70
estatParams() {
return {
statsDataId: '0000010101',
cdArea: ['28000'],
cdCat01: ['A1101', 'A110101', 'A110102'],
}
},

データの取得

共通化した this.$estatのgetメソッドを利用する。
これで、estatResponseに結果が格納される。

PopulationPref.vue#L45-L50link
45
46
47
48
49
50
async fetch() {
const params = this.estatParams
const { data } = await this.$estat.get(null, { params })
console.log(data)
this.estatResponse = data
},

概要

グラフ表示部分については、汎用性を考慮して別コンポーネントを作成。

1
2
3
4
5
component/
 ├ highcharts/
 │ └ ColumnChart/
 └ PopulationPref.vue

都道府県の人口構成データの取得

パラメータの設定

API概要のとおりURLとパラメータを設定する。

都道府県については、親コンポーネントからpropsで受け取っている。型は{prefCode:number, prefName:string}
オブジェクトからprefCodeを取得し、resasParamsにセットする。
都道府県全体の人口を利用したいので、cityCodeは-

PopulationPref.vue#L38-L43link
38
39
40
41
42
43
props: {
selectedPref: {
type: Object,
required: true,
},
},
PopulationPref.vue#L58-L66link
58
59
60
61
62
63
64
65
66
resasUrl() {
return `api/v1/population/composition/perYear`
},
resasParams() {
return {
prefCode: String(this.prefCode),
cityCode: '-',
}
},

axiosでデータ取得

Nuxtのfetchメソッドを利用してRESAS-APIの値を取得する。
公式サイトでは

fetch メソッドは、ページがレンダリングされる前に、データをストアに入れるために使われます。

と書かれているが、今回はAPIのレスポンスをコンポーネント内のdataに格納する。

なお、似た機能であるasyncDataはpageコンポーネントでしか利用できない。

PopulationPref.vue#L32-L37link
32
33
34
35
36
37
async fetch() {
const url = this.resasUrl
const params = this.resasParams
const { data } = await this.$resas.get(url, { params })
this.resasResponse = data.result.data
},
1
2
3
4
5
data() {
return {
resasResponse: null,
}
},

なお、APIを取得した後にページを表示させる必要があるので、$fetchState.pendingを忘れてはいけない。

PopulationPref.vue#L3-L5link
3
4
5
<v-card :loading="$fetchState.pending">
<p v-if="$fetchState.pending" />
<div v-else>

レスポンスのデータはAPI概要のとおり。
ここでは、JSON内の['result']['data']を抽出しているので、次の形の連想配列となる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[
{
"label": "総人口",
"data": [{"year": 1980, "value": 12817 }
]
},
{
"label": "年少人口",
"data": [{"year": 1980, "value": 2906, "rate": 22.6}]
},
{
"label": "生産年齢人口",
"data": [{"year": 1980, "value": 8360, "rate": 65.2}
]
},
{
"label": "老年人口",
"data": [{"year": 1980, "value": 1550, "rate": 12}]
}
]

データの整形

今回はColumnChartを利用したいので、取得したデータを公式リファレンスに従って、Highhartsで利用できる形に整える。

PopulationPref.vue#L70-L83link
70
71
72
73
74
75
76
77
78
79
80
81
82
83
chartData() {
return this.resasResponse.map((d) => {
return {
name: d.label,
data: d.data.map((d) => {
return {
x: d.year,
y: d.value,
unit: this.unit,
}
}),
}
})
},

これでthis.chartDataは次の形になる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[
{
name: '総人口',
data:: [{x: 1980, y: 12817, unit:'人' }]
},
{
name: '年少人口',
data: [{x: 1980, y: 2906, unit:'人' }]
},
{
name: "生産年齢人口",
data: [{x: 1980, y: 8360, unit:'人' }
]
},
{
name: "老年人口",
data: [{x: 1980, y: 1550, unit:'人' }]
}
]

総数/内訳の切り替え

このままでは、総人口と内訳(年少人口、生産年齢人口、老年人口)のデータを一緒に描画してしまうので、総数と内訳を切り替えるラジオボタンを追加する

PopulationPref.vue#L13-L16link
13
14
15
16
<v-radio-group v-model="series" row>
<v-radio label="総数" value="all"></v-radio>
<v-radio label="内訳" value="break"></v-radio>
</v-radio-group>

ラジオボタンの値(series)によって、displayDataの結果が切り替わる。

PopulationPref.vue#L44-L50link
44
45
46
47
48
49
50
data() {
return {
resasResponse: null,
series: 'all',
unit: '人',
}
},
PopulationPref.vue#L84-L90link
84
85
86
87
88
89
90
displayData() {
if (this.series === 'all') {
return this.chartData.filter((f) => f.name === '総人口')
} else {
return this.chartData.filter((f) => f.name !== '総人口')
}
},

グラフ表示

最後に、this.displayDataの値をColumnChartコンポーネントに渡してグラフを表示させる。

PopulationPref.vue#L19link
19
<HighchartsColumnChart :display-data="displayData" />

まとめ

これまでをまとめると、コンポーネントは次のとおりとなる。

PopulationPref.vuelink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
<template>
<v-col cols="4" sm="8" md="6">
<v-card :loading="$fetchState.pending">
<p v-if="$fetchState.pending" />
<div v-else>
<!-- タイトル -->
<v-card-title class="headline">
{{ title }}
</v-card-title>

<v-card-text>
<!-- ラジオボタン -->
<v-radio-group v-model="series" row>
<v-radio label="総数" value="all"></v-radio>
<v-radio label="内訳" value="break"></v-radio>
</v-radio-group>

<!-- グラフ -->
<HighchartsColumnChart :display-data="displayData" />

<!-- 注釈 -->
<hr class="my-3" />
<p>地域経済分析システムRESASのAPIを利用して作成</p>
</v-card-text>
</div>
</v-card>
</v-col>
</template>

<script>
export default {
async fetch() {
const url = this.resasUrl
const params = this.resasParams
const { data } = await this.$resas.get(url, { params })
this.resasResponse = data
},
props: {
selectedPref: {
type: Object,
required: true,
},
},
data() {
return {
resasResponse: null,
series: 'all',
unit: '人',
}
},
computed: {
prefCode() {
return this.selectedPref.prefCode
},
prefName() {
return this.selectedPref.prefName
},
resasUrl() {
return `api/v1/population/composition/perYear`
},
resasParams() {
return {
prefCode: String(this.prefCode),
cityCode: '-',
}
},
title() {
return `${this.prefName}の人口構成`
},
chartData() {
const data = this.resasResponse.result.data
return data.map((d) => {
return {
name: d.label,
data: d.data.map((d) => {
return {
x: d.year,
y: d.value,
unit: this.unit,
}
}),
}
})
},
displayData() {
if (this.series === 'all') {
return this.chartData.filter((f) => f.name === '総人口')
} else {
return this.chartData.filter((f) => f.name !== '総人口')
}
},
},
created() {},
}
</script>

ColumnChartコンポーネントの作成

受け取ったdisplayDataを元に、Highchart.jsでグラフ描画する。

ColumnChart.vuelink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
<template>
<div class="graph">
<highcharts :options="chartOptions" />
</div>
</template>

<script>
import { cloneDeep } from 'lodash'

export default {
props: {
displayData: {
type: Array,
required: true,
},
},
data() {
return {
colors: [
'#058DC7',
'#50B432',
'#ED561B',
'#DDDF00',
'#24CBE5',
'#64E572',
'#FF9655',
'#FFF263',
'#6AF9C4',
],
}
},
computed: {
series() {
const series = cloneDeep(this.displayData)
return series.reduce((acc, cur, i) => {
cur['color'] = this.colors[i]
acc.push(cur)
return acc
}, [])
},
chartOptions() {
return {
chart: {
height: 280,
zoomType: 'xy',
type: 'column',
},
title: {
text: null,
},
xAxis: {
min: 1990,
max: 2020,
scrollbar: {
enabled: true,
},
crosshair: true,
},
yAxis: {
opposite: false,
title: {
text: '',
},
},
plotOptions: {
series: {
pointWidth: 12,
animation: false,
label: {
connectorAllowed: false,
},
},
column: {
stacking: 'normal',
},
},
legend: {
enabled: false,
},
tooltip: {
pointFormat:
'<span style="color:{series.color}">{series.name}</span>: <b>{point.y}{point.unit}</b> ({point.percentage:.0f}%)<br/>',
shared: true,
},
credits: {
enabled: false,
},
series: this.series,
}
},
},
}
</script>

<style lang="sass" scoped>
.graph
margin-top: 10px
height: 100%
</style>

Highchartsとは

Highchartsは、Webでグラフを描写するためのJavaScriptライブラリ。
個人サイトや教育サイト、非営利組織は無料で利用可能。
ライセンスの詳細はこちら

NuxtでHighchartsを利用する場合の選択肢

プラグインの種類

Nuxt.jsのプロジェクトでHighchartsを利用する場合、プラグインがいくつか公開されている。

今回は、

  1. HighchartsMapsで独自のtopojsonを利用したい。
  2. HighchartsStockの機能である横スクロールを利用したい。

という2つの理由から、highcharts-vueを利用する。

他の2つでも同様の機能は実現できるかもしれない。
少なくとも私にとってはhighcharts-vueが一番使いやすかった。

highcharts-vueのインストール

インストール

highchartshighcharts-vueをインストール。公式ドキュメント参照。

1
2
yarn add highcharts
yarn add highcharts-vue

プラグインの作成

プロジェクトのplugins配下にhighcharts-vue.jsを作成。

highcharts-vue.jslink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import Vue from 'vue'
import HighchartsVue from 'highcharts-vue'
import Highcharts from 'highcharts'
import stockInit from 'highcharts/modules/stock'
import exportingInit from 'highcharts/modules/exporting'
import exportingCSV from 'highcharts/modules/export-data'
import HighchartsMapModule from 'highcharts/modules/map'

Vue.use(HighchartsVue)

if (typeof Highcharts === 'object') {
stockInit(Highcharts)
exportingInit(Highcharts)
exportingCSV(Highcharts)
HighchartsMapModule(Highcharts)

Highcharts.setOptions({
lang: {
decimalPoint: '.',
thousandsSep: ',',
numericSymbols: null,
},
})

}

nuxt.config.jsでプラグインを有効化

nuxt.config.js#L35-L38link
35
36
37
38
{
src: '@/plugins/highcharts-vue',
mode: 'client',
},

highcharts-vueの設定

プラグインの設定内容を順に説明する。

基本設定

highcharts-vueを使う場合は必須。公式ドキュメント参照。

1
2
3
4
5
import Vue from 'vue'
import HighchartsVue from 'highcharts-vue'
import Highcharts from 'highcharts'

Vue.use(HighchartsVue)

exporting機能

グラフをPNGやCSV等で出力できるexporting機能を有効化する。
公式ドキュメントには次のとおり紹介されているが、私の環境ではエラーになった。

1
2
3
4
5
6
import Highcharts from 'highcharts'
import exportingInit from 'highcharts/modules/exporting'
import exportingCSV from 'highcharts/modules/export-data'

exportingInit(Highcharts)
exportingCSV(Highcharts)

[こちら]を参考に、下記のとおり修正したら解決。

1
2
3
4
5
6
7
8
import Highcharts from 'highcharts'
import exportingInit from 'highcharts/modules/exporting'
import exportingCSV from 'highcharts/modules/export-data'

if (typeof Highcharts === 'object') {
exportingInit(Highcharts)
exportingCSV(Highcharts)
}

stock機能

グラフを横スクロールしたい場合、標準のhighchatsではなく、highcharts stockのが必要となる

https://www.highcharts.com/demo/stock

公式ドキュメントを参考に、プラグインに下記の通り追記。

1
2
3
4
import Highcharts from 'highcharts'
import stockInit from 'highcharts/modules/stock'

stockInit(Highcharts)

これもエラーが出たので、次の通り修正。

1
2
3
4
5
6
import Highcharts from 'highcharts'
import stockInit from 'highcharts/modules/stock'

if (typeof Highcharts === 'object') {
stockInit(Highcharts)
}

mapChart

Highcharts Mapsを利用したいので、公式ドキュメントを参考に次のとおり追記。

1
2
3
4
5
import HighchartsMapModule from 'highcharts/modules/map'

if (typeof Highcharts === 'object') {
HighchartsMapModule(Highcharts)
}

数値表示

桁区切りにカンマを利用したいのと、日本語単位を表示したいので、下記を追記。
もし1千 -> 1000と表示したいなら、numericSymbols:null

1
2
3
4
5
6
7
8
9
if (typeof Highcharts === 'object') {
Highcharts.setOptions({
lang: {
decimalPoint: '.',
thousandsSep: ',',
numericSymbols: ['千', '百万', '十億', '兆', '千兆', '百京'],
},
})
}

Highchartsのグラフを表示する

グラフを表示したいコンポーネントに下記のとおり記述する。
chartOptionsについては、公式デモにたくさんのサンプルがあるので参照。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template>
<highcharts :options="chartOptions" />
</template>

<script>
export default {
data() {
return {}
},
computed: {
series() {
'ここでグラフデータを定義'
},
chartOptions() {
return {
chart: {},
title: {},
xAxis: {},
yAxis: {},
plotOptions: {},
series: this.series,
}
},
},
}
</script>

API概要

社会・人口統計体系(都道府県) A 社会・人口からデータを取得する。

パラメータは次のとおり。今回は兵庫県28000の総人口A1101で抽出している。

1
2
3
4
5
{
statsDataId: '0000010101',
cdArea: '28000',
cdCat01: 'A1101',
}

取得結果の解析

取得結果の全文はこちらを参照。

API仕様については公式に説明がある。

取得結果のJSONを変数resに格納する前提で進める。

1
const res = eStatResponse(APIの取得結果のJSON

主目的である、統計結果(値)は次の階層にアクセスする。

1
const value = res.GET_STATS_DATA.STATISTICAL_DATA.DATA_INF.VALUE

結果は、次のようなオブジェクトが格納された配列となる。

populationAll.json#L350-L357link
350
351
352
353
354
355
356
357
{
"@tab": "00001",
"@cat01": "A1101",
"@area": "28000",
"@time": "1975100000",
"@unit": "人",
"$": "4992140"
},

“@tab”: “00001”,
“@cat01”: “A1101”,
“@area”: “28000”,
“@time”: “1975100000”,
“@unit”: “人”,
“$”: “4992140”

表章事項

地域

RESAS-APIを利用する準備

RESAS-APIの利用者登録とAPIキーの発行

公式サイトから利用者登録を完了する。

ログインしてマイページにアクセスすると、一番下にAPIキーが表示されているのでメモ。

APIキーを環境変数として利用

プロジェクト直下に.envファイルを作成して、取得したAPIキーをRESAS_API_KEYとして記載しておく。

1
RESAS_API_KEY = '○○○○○○○○○○○○○○○○○'

Nuxtv2.13以降ならdotenvを利用しなくても、Nuxtの標準機能で環境変数を利用できる。

公式サイトにあるとおり、nuxt.config.jspublicRuntimeConfigを定義する。

nuxt.config.js#L4-L6link
4
5
6
publicRuntimeConfig: {
RESAS_API_KEY: process.env.RESAS_API_KEY,
},

環境変数は$configのグローバルに定義されるので、どこからでも呼び出し可能。

GitHub Actionsで環境変数を利用

GitHub Actionsでビルドする場合、プロジェクト内で設定した.envは反映されないので、別途設定する必要がある。

GitHubのリポジトリにseacretを設定する

公式ドキュメントを参考に、SECRET 情報を登録する。

  1. リポジトリのメインページからSettings項目を選択し、設定ページに飛ぶ。
  2. 左のサイドバーからSecretsを押下する。
  3. New repository secretボタンを押下する。
  4. Nameに SECRET 情報の名前RESAS_API_KEYを、Valueに SECRET 情報を入力する。
  5. Add secretボタンを押下し、SECRET 情報を登録する。

workflowでseacretを読み込む

ワークフローのbuildセクションで、登録したSECRET 情報を呼び出す。
※詳しくは公式ドキュメント参照

1
2
3
4
5
jobs:
build:
runs-on: ubuntu-18.04
env:
RESAS_API_KEY: ${{ secrets.RESAS_API_KEY }}

nuxtjs/axiosのインストールと設定

APIを利用する場合、Promise ベースの HTTP クライアントである「axios」を利用することが多い。

ここでは、nuxt.js用のブラグイン「nuxt/axios」をインストールする。

インストール

1
yarn add @nuxtjs/axios

nuxt.config.jsで@nuxtjs/axiosを有効化する。

nuxt.config.js#L56-L58link
56
57
58
modules: [
"@nuxtjs/axios",
],

RESAS-APIを利用するときの共通処理

公式ドキュメントに従って、baseURLとheader(API-KEY)を共通処理化する。

プラグインの作成

Nuxtプロジェクト内にplugins/resas.jsを作成。プラグインの詳細は公式ドキュメント参照。

resas.jslink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export default function ({ $axios, $config }, inject) {
const api = $axios.create({
headers: {
common: {
Accept: 'application/json',
},
'X-API-KEY': $config.RESAS_API_KEY,
'Content-Type': 'application/json',
},
data: {},
})

api.setBaseURL('https://opendata.resas-portal.go.jp')

inject('resas', api)
}

RESAS-APIのAPI概要にあるとおり、APIのエンドポイントはhttps://opendata.resas-portal.go.jp/となるので、これをBaseUrlに設定している。

resas.js#L13link
13
api.setBaseURL('https://opendata.resas-portal.go.jp')

また、headersの’X-API-KEY’にRESAS-API-KEYを設定する。(環境変数から取得する)

resas.js#L7link
7
'X-API-KEY': $config.RESAS_API_KEY,

最後に、injectで関数を共通処理化している。これで別のコンポーネントからthis.$resasで呼び出すことができる。

resas.js#L15link
15
inject('resas', api)

プラグインの有効化

nuxt.config.jsでプラグインを有効化。

nuxt.config.js#L37link
37
{ src: '@/plugins/resas', ssr: true, },

Nuxt.jsでRESAS-APIの都道府県一覧を取得

axiosの基本的な使い方(GET)

公式ドキュメントにあるとおり、クエリパラメータを指定する方法が2種類ある。

axios.getに指定するURLに直接記述する方法

1
axios.get('/user?ID=12345')

axios.getの第2引数に、オプション指定する方法

1
2
3
4
5
axios.get('/user', {
params: {
ID: 12345
}
})

ここでは、②の方法を利用する。

RESAS-API概要

地域経済分析システムRESASが提供している都道府県一覧をAPIから取得する。

APIのURLはapi/v1/prefectures、パラメータはなし。

データの取得

共通化した this.$resasのgetメソッドを利用する。

prefList.js#L27-L28link
27
28
const url = 'api/v1/prefectures'
const {data} = await this.$resas.get(url)

Pythonを利用してローカルJSONに保存

都道府県一覧はよく利用するデータなので、ローカルにJSONで保存しておく方法もある。

Pythonで環境変数を利用

Pythonで環境変数を利用するには、pyshon-dotenvが必要。

1
$ pip install python-dotenv

pythonで.envファイルから環境変数を取得する。

setCodes.py#L7-L10link
7
8
9
10
# 環境変数からRESAS-API-KEYを取得
from dotenv import load_dotenv
load_dotenv()
RESAS_API_KEY = os.getenv('RESAS_API_KEY')

Pythonで都道府県一覧をローカルに保存

RESAS-APIから都道府県一覧データを取得して、ローカルJSON(ここではdata/codes/preflist.json)に保存。

setCodes.py#L12-L21link
12
13
14
15
16
17
18
19
20
21
# RESAS-APIから都道府県一覧を取得を取得
url = 'https://opendata.resas-portal.go.jp/api/v1/prefectures'
req = urllib.request.Request(url, headers={'X-API-KEY': RESAS_API_KEY})
with urllib.request.urlopen(req) as response:
data = response.read()
prefList = json.loads(data.decode())

# 都道府県一覧をJSONファイルに保存
with open('data/codes/preflist.json', 'w') as f:
json.dump(prefList, f, ensure_ascii=False, indent=4)

Nuxtプロジェクトの作成

公式ドキュメントに従ってNuxt.jsをインストールしていく。
下記の前提条件は満たしているものとする。

前提条件

create-nuxt-app

プロジェクト名は仮に「nuxt-resas」とする。

1
$ npx create-nuxt-app nuxt-resas

インストール時の設定は次のとおり。
ここではVuetifyをインストールしている。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
create-nuxt-app v3.7.1
✨ Generating Nuxt.js project in nuxt-resas
? **Project name:** nuxt-resas
? **Programming language:** TypeScript
? **Package manager:** Yarn
? **UI framework:** Vuetify.js
? **Nuxt.js modules:** (Press **<space>** to select, **<a>** to toggle all, **<i>** to invert selection)
? **Linting tools:** (Press **<space>** to select, **<a>** to toggle all, **<i>** to invert selection)
? **Testing framework:** None
? **Rendering mode:** Universal (SSR / SSG)
? **Deployment target:** Static (Static/Jamstackhosting)
? **Development tools:** (Press **<space>** to select, **<a>** to toggle all, **<i>** to invertselection)
? **What is your GitHub username?** [ここにGitHubアカウント]
? **Version control system:** Git

インストールが完了すると、ローカルにプロジェクト名のディレクトリが作成される。

build

プロジェクトのディレクトリに移動し、ビルドする。

1
2
$ cd nuxt-resas
$ yarn dev

ビルドが成功したらlocalhost:3000にアクセスする。
初回起動では下記画面が表示される。

GitHubリポジトリと連携

リモートリポジトリ作成

GitHubホーム画面のRepositories> Newから新規リポジトリを作成する。
Repository nameDescriptionを記入したら他の選択はせずに、Create Repositoryを押して完了する。
Repository name は任意だが、個人的にはいつもローカルのディレクトリ(ここではnuxt-resas)に合わせている。

リポジトリの作成に成功したら、画面にリモートURLが発行される。

ローカルリポジトリと連携

再びターミナルでローカルプロジェクトを開き、下記を実行。

1
2
3
4
5
$ git init
$ git add .
$ git commit -m "first commit"
$ git remote add origin https://github.com/******/nuxt-resas.git
$ git push -u origin master

これで、ローカルリポジトリとリモートリポジトリが連携できた。

GitHub ActionsでビルドしてNetlifyでデプロイする

Netlifyの無料プランではビルド時間が月300分までに限定されているため、比較的規模の大きいプロジェクトでは足りない可能性がある。
そこで、GitHub ActionsでビルドしてNetlifyにデプロイする方法を採用する。
Github Actions ではパブリックリポジトリは完全無料。プライベートリポジトリでも 2000 分/月までビルド時間を使うことが許されている。

NetlifyでアクセストークンとサイトIDを発行

アクセストークンの発行

Netlifyのアカウントページを開き、Personal access tokens内のNew access tokenへ移動する。
任意のトークン名を入力する。例えばGitHub ActionsとしてGenerate tokenをクリックするとアクセストークンが表示・発行されるのでメモしておく。

サイトの作成

Netlifyトップページに戻り、Sitesへ移動する。
ページ下部にDrag and drop your site output folder hereの欄が表示されているので、ここにNuxtプロジェクトのdistディレクトリをドラッグ&ドロップする。

サイトIDの発行

作成したサイトのSite settings内のSite detailsAPP IDが発行されているのでメモしておく。

GitHub Seacret にアクセストークンとサイトIDを設定

GitHubのリポジトリを開き、SettingsSeacretsからNew repository secretをクリックする。
NameとValueの組み合わせで値を設定できる。仮に次のように設定する。

  • NETLIFY_AUTH_TOKEN:アクセストークン
  • NETLIFY_SITE_ID:サイトID

GitHub Actions にworkflowを設定

最後に、GitHub Actionsのワークフローを設定する。
リポジトリ内の.github/workflowsディレクトリ にnetlify.ymlを作成する。GitHub Actionsから作成することもできる。
ワークフローについての詳細は公式ドキュメント参照。

workflow

netlify.ymllink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
name: Netlify

on:
push:
branches:
- master

jobs:
build:
runs-on: ubuntu-18.04

steps:
- uses: actions/checkout@v2

# 「./dist」にビルド結果が生成する
- uses: actions/setup-node@v1
with:
node-version: 14.x
- run: npm ci
- run: npm run build
- run: npm run generate

# Netlifyにデプロイする
- run: npx netlify-cli deploy --dir=dist --prod
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}

ビルドするブランチを変更したい場合、on pushの部分を変更すれば良い。

netlify.yml#L3-L6link
3
4
5
6
on:
push:
branches:
- master

GitHub Actionsでエラーがでた場合

GitHub Actionsで下記エラーが発生する場合がある

1
2
Run npm ci
npm ERR! cipm can only install packages with an existing package-lock.json or npm-shrinkwrap.json with lockfileVersion >= 1. Run an install with npm@5 or later to generate it, then try again.

pakege.lock.jsonがないということなので、プロジェクトのディレクトリでnpm installするとpackege.lock.jsonが生成される

1
npm install

GitHubの既存リポジトリを全て初期化して「first commit」にする手順。

Gitの初期化

1
2
3
4
5
6
7
8
9
# リモートリポジトリのURLを控える
$ git remote -v

# 初期化
$ rm -rf .git
$ git init
$ git add .
$ git commit -a -m"first commit"
$ git remote add origin 控えたリモートリポジトリのURL

GitHubへプッシュ

GitHubへプッシュする際は -f を忘れないように。
後戻りはできない。

1
$ git push -f origin master

Node.js上で動作するコードフォーマッター「Prettier」の設定方法を記録しておく。

Prettierのインストール

VSCodeの拡張機能から「Prettier」をインストール。詳しい方法は下記リンク先を参照。
https://ma-vericks.com/vscode-prettier/

Prettierの設定

プロジェクト直下に「.prettierrc」というファイルを作成し、そこにJSON形式で設定を追加。

私のプロジェクトで利用する共通設定はとりあえず下記の通り。

1
2
3
4
{
"semi": false,
"singleQuote": true,
}

“semi”:ステートメントの最後にセミコロンを付与(falseで付与しない)
“singleQuote”:二重引用符の代わりに単一引用符を使用(true)

デフォルトの設定

「.prettierrc」ファイルを作成していない場合は、下記の設定が適用される。

詳しくはPrettier公式サイトを参照。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": false,
"quoteProps": "as-needed",
"jsxSingleQuote": false,
"trailingComma": "none",
"bracketSpacing": true,
"jsxBracketSameLine": false,
"arrowParens": "avoid",
"rangeStart": 0,
"rangeEnd": Infinity,
"parser": "none",
"filepath": "none",
"requirePragma": false,
"insertPragma": false,
"proseWrap": "preserve",
"htmlWhitespaceSensitivity": "css",
"vueIndentScriptAndStyle": false,
"endOfLine": "auto",
}