RESAS-APIから人口構成データを取得してHighchartsでグラフ表示する。

概要

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

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

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

パラメータの設定

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

都道府県については、親コンポーネントからpropsで受け取る。型は{prefCode:number, prefName:string}

PopulationPref.vue#L38-L43link
38
39
40
41
42
43
props: {
selectedPref: {
type: Object,
required: true,
},
},

オブジェクトからprefCodeを取得し、resasParamsにセットする。
都道府県全体の人口を利用したいので、cityCodeは-

PopulationPref.vue#L61-L66link
61
62
63
64
65
66
resasParams() {
return {
prefCode: String(this.prefCode),
cityCode: '-',
}
},

axiosでデータ取得

Nuxtのfetchメソッドを利用してRESAS-APIの値を取得する。
似た機能であるasyncDataはpageコンポーネントでしか利用できない。

axiosの共通設定はプラグイン化しているので、this.$resas.getでデータを取得する。

プラグインについては下記の記事を参照

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
},

これでresasResponseに結果が格納される。

1
2
3
4
5
data() {
return {
resasResponse: null,
}
},

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

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

レスポンスのデータは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}]
}
]

データの整形

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

PopulationPref.vue#L70-L84link
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
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,
}
}),
}
})
},

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#L85-L91link
85
86
87
88
89
90
91
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" />

まとめ

これまでをまとめると、RESAS-APIからデータを取得して加工するまでのコンポーネントは次のとおりとなる。

PopulationPref.vue#L19link
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>

series

propsで受け取ったdisplayDataをclondeepして、必要な情報を付与する。
ここでは棒グラフの色colorを追加した。

ColumnChart.vue#L33-L40link
33
34
35
36
37
38
39
40
series() {
const series = cloneDeep(this.displayData)
return series.reduce((acc, cur, i) => {
cur['color'] = this.colors[i]
acc.push(cur)
return acc
}, [])
},

chartOptions

グラフの高さはサイトに応じて設定。
チャートタイプにはcolumnを指定する。もし折れ線グラフにしたいならline

ColumnChart.vue#L43-L47link
43
44
45
46
47
chart: {
height: 280,
zoomType: 'xy',
type: 'column',
},

xAxisに横軸のオプションを設定する。
今回は、データ1990年から2020年までなのでminとmaxにそれぞれ直接指定。
コンポーネントの再利用を考えるなら、ここをdataで持たせてもよいかもしれない。

ColumnChart.vue#L51-L58link
51
52
53
54
55
56
57
58
xAxis: {
min: 1990,
max: 2020,
scrollbar: {
enabled: true,
},
crosshair: true,
},