0%

9.11テロの関与を疑われてアメリカに拘束され、キューバのグランタナモ基地(かの悪名高き)で過酷な拷問を受けた主人公サラヒと、彼の人権を守るために闘った弁護士ナンシーを描いた実話。

まず最初、画面には「この映画は実話です」と写しだされる。

「この映画は実話を基にしています」ではない。「この映画は実話です」。と言い切っている。

サラヒは、確たる証拠もなく、起訴もされていないのに、9.11の主要人物として、14年2か月間にわたって拘留される。

ナンシーは政府に対して情報公開請求を行うが、出てくるのは黒塗りの文書ばかり。

やっとのことで見ることのできた記録の内容は、人として目を疑うばかりの過酷な拷問、そしてその結果としての供述、自白。

結果的に主人公は無罪の判決を受けるが、その後、オバマ政権は7年間も彼を拘束しつづけたとナレーションが出る。

裁判まで7年間、無罪の判決後も7年間拘留されたということになる。

それでも最後に彼は言う。「私はアメリカは正義の国だと信じていた。まさかアメリカが私を恐怖で支配するだなんて。やってもいない罪でせめられ続けた。だが許そうと思う。許したい。」

アラビア語では「自由」と「許し」は同じ言葉だ

エンドロール、実在のサラヒ当人が登場する。そう、「この映画は実話です」なのだ。

彼は満面の笑みで、ボブ・ディランの『the man in me』を歌う。これは私のことだと。

ADHDとは

ADHDは、「注意欠如・多動症/注意欠如・多動性障害」とも呼ばれ、不注意(集中力がない)、多動性(じっとしていられない)、衝動性(思いつくと行動してしまう)といった症状が見られる障害。 症状の現れ方によって「不注意優勢に存在」「多動・衝動優勢に存在」「混合して存在」と分類されます

ADHDは「注意欠如・多動症/注意欠如・多動性障害」とも呼ばれ、不注意(集中力がない)、多動性(じっとしていられない)、衝動性(思いつくと行動してしまう)という3つの症状を主症状とする発達障害

症状の現れ方によって「不注意優勢に存在」「多動・衝動優勢に存在」「混合して存在」と分類される。

不注意優勢型(不注意が目立つ)

ケアレスミスが多い、約束を忘れてしまう、物をよく失くすといった、不注意を起因とする症状が主に現れるタイプ。

上記のミスはADHDでない方でも起こりうるが、ADHDではこれらのミスが年齢に対して不相応に生じ、日常生活・社会生活に支障をきたす。これらの症状は決して本人のやる気がない、怠けているというわけではなく、脳の機能発達の偏りから起こるものである。

多動・衝動性優勢型(落ち着きのなさが目立つ)

じっとすることができない、失言をしてしまうなど、多動性や衝動性から起こる症状が主に現れるタイプ。

ADHDの多動性というと、すぐに離席してしまうイメージがあるが、大人のADHDでは離席してしまうほどじっとできない方は多くない。大人のADHDで多動性が現れる場合は、何となくそわそわしている、体を小刻みに揺らす、といった形で現れることが多い。

混合型(不注意、多動性、衝動性ともに目立つ)

混合型は、上記に挙げた不注意・多動性・衝動性が同程度に目立つタイプ。

大人のADHDの症状により起こる行動や状態

大人のADHDの症状は、具体的に下記のような行動や状態となって現れる。

ADHDの症状は決して本人の努力不足や怠けではなく、「次は失敗しないようにしよう」と自分なりに対策を試みているにもかかわらずなかなか状況が改善されない。

不注意症状による行動や状態

  • ミスが多い、同じミスを繰り返す
  • 頭の中やスケジュール・タスクを整理できない、順序だてて行えない
  • 締め切りをなかなか守ることができない
  • 忘れ物や失くし物が多い
  • 仕事や作業に集中できない

多動・衝動性の症状による行動や状態

  • 失言をしてしまう
  • 衝動的に動いてしまう(衝動買い、独断で重要事項を決めてしまうなど)
  • そわそわしている、体を小刻みに揺らす(貧乏ゆすりなど)

ADHDでみられることがあるその他の症状

  • 気分の変動が激しい(1日のなかで瞬間的に気分が変わる)
  • 過度に集中してしまう ※自閉症スペクトラムとADHDが併存している可能性

なぜ自分がADHDかもしれないと思ったか

失言が多い

  • 思ったことをすぐ口にしてしまい、人間関係をうまく築けない
  • 人間関係をよくするために、頑張って話そうとするが、逆効果になることも多い

衝動的に動いてしまう

  • 思いついたらとりあえずやってしまう。待つことができない。
  • 衝動買いしてしまう、これまで貯金したことがない。

飽きっぽい

  • ひとつのことに集中できない。すぐ飽きる。

詰めが甘い

  • 仕事の最後の詰めが甘い。ミスが多い。

ADHDの診断

実際に、Webの診断テストをいくつか受けてみた。

  • https://www.kawada.or.jp/self_check/developmental_disorder/

    ADHDの症状を持っている可能性は低いです

  • https://azscitechfest.org/check/adhd.php

    スコア
    47点(100点満点で高得点ほど重い症状)
    状態
    ややADHDの可能性があります。
    寸評
    あなたのADHDの可能性は中です。
    正常な方よりも高めの点数のため、ADHDの可能性があります。
    成人の平均点よりも高く、ADHDの傾向が見受けられます。
    気になる場合は、専門医に相談すると良いでしょう。

  • https://cbt-career.work/screening-test/

    はっきりとしたADHDの症状が生きづらさに関係しているとまでは言えませんが、基準を満たさないグレーゾーンの症状があなたの生きづらさに関係している可能性は否定できません。

現象

node.jsの公式サイトでは確かにリリースされているバージョンなのに、nodenvにはないと言われた。

nodenv install –listを実行するも、 リストに欲しいバージョンがない

対策

nodenvで無効なバージョンを指定してインストールしようとすると、

1
2
See all available versions with `nodenv install --list'
If the version you need is missing, try upgrading node-build:

上のように返ってくる。言う通りにアップグレードしたら解決した。

node-bulidディレクトリに移動

1
cd ~/.anyenv/envs/nodenv/plugins/node-build

上記はanyenvを前提にnodenvを入れていた場合。
nodenvを単体で入れている場合は、そのフォルダを探して、その中の node-build フォルダまで移動する

gitからアップグレード

1
git pull

作業フォルダに戻ってnodenv install

1
2
cd ~/[your workspace]
nodenv install [versions]

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
6
component/
 ├ highcharts/
 │ └ PyramidChart.vue
 ├ populationPyramid/
   └ PopulationPyamidPref.vue

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

パラメータの設定

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

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

PopulationpyramidPref.vue#L44-L49link
44
45
46
47
48
49
props: {
selectedPref: {
type: Object,
required: true,
},
},

オブジェクトからprefCodeを取得し、resasParamsにセットする。
都道府県全体の人口を利用したいので、cityCodeは-
年次についてはyearLeftyearRightの2種類指定できる。

PopulationpyramidPref.vue#L67-L73link
67
68
69
70
71
72
73
resasParams() {
return {
prefCode: String(this.prefCode),
cityCode: '-',
yearLeft: String(this.year),
yearRight: '2040',
}

年次の設定

yearRightは2040で固定して、yearLeftをスライダーで選択する。

PopulationpyramidPref.vue#L13-L22link
13
14
15
16
17
18
19
20
21
22
<v-slider
v-model="year"
thumb-label="always"
class="Slider"
:max="2045"
:min="1980"
step="5"
hide-details
@change="$emit('input', $event)"
/>

スライダーの値に応じてthis.yearLeftの値が変わる。watchで検出して再度fetchすることを忘れないように。

PopulationpyramidPref.vue#L94-L98link
94
95
96
97
98
watch: {
year() {
this.$fetch()
},
},

axiosでデータ取得

Nuxtのfetchメソッドを利用してRESAS-APIの値を取得する。

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を忘れてはいけない。

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

データの整形

レスポンスのデータはAPI概要のとおり。

今回はHighcharts.jsのBar with negative stackを利用したいので、取得したデータをHighhartsで利用できる形に整える。

PopulationpyramidPref.vue#L78-L88link
78
79
80
81
82
83
84
85
86
87
88
chartData() {
const data = this.resasResponse.result.yearLeft.data
return data.map((d) => {
return {
category: d.class,
man: d.man,
woman: d.woman,
unit: this.unit,
}
})
},

グラフ表示

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

※chartDataをそのまま渡してもよいが、ColumnChartを利用する場合等に合わせて、グラフ表示するデータはdisplayDataに統一する。

まとめ

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

PopulationpyramidPref.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
<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-slider
v-model="year"
thumb-label="always"
class="Slider"
:max="2045"
:min="1980"
step="5"
hide-details
@change="$emit('input', $event)"
/>

<!-- グラフ -->
<HighchartsPyramidChart :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,
year: 2015,
unit: '人',
}
},
computed: {
prefCode() {
return this.selectedPref.prefCode
},
prefName() {
return this.selectedPref.prefName
},
resasUrl() {
return `api/v1/population/composition/pyramid`
},
resasParams() {
return {
prefCode: String(this.prefCode),
cityCode: '-',
yearLeft: String(this.year),
yearRight: '2040',
}
},
title() {
return `${this.prefName}の人口ピラミッド`
},
chartData() {
const data = this.resasResponse.result.yearLeft.data
return data.map((d) => {
return {
category: d.class,
man: d.man,
woman: d.woman,
unit: this.unit,
}
})
},
displayData() {
return this.chartData
},
},
created() {},
watch: {
year() {
this.$fetch()
},
},
}
</script>

PyramidChartコンポーネントの作成

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

コード全文

PyramidChart.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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
<template>
<div class="graph">
<highcharts :options="chartOptions" />
</div>
</template>

<script>
import { cloneDeep } from 'lodash'

export default {
props: {
displayData: {
type: Array,
required: true,
},
},
data() {
return {
// charWidth: 300,
// windowWidth: 0,
}
},
computed: {
categories() {
return this.displayData.map((d) => d.category)
},
unit() {
return this.displayData[0].unit
},
series() {
return [
{
name: '男性',
data: this.displayData.map((d) => -1 * d.man),
color: '#4169e1',
},
{
name: '女性',
data: this.displayData.map((d) => d.woman),
color: '#ff69b4',
},
]
},
chartOptions() {
return {
chart: {
height: 350,
type: 'bar',
backgroundColor: 'transparent',
},
title: {
text: null,
},
accessibility: {
point: {
valueDescriptionFormat: '{index}. {xDescription}, {value}.',
},
},
xAxis: [
{
// 左軸
categories: this.categories,
reversed: false,
labels: {
step: 1,
},
accessibility: {
description: '年齢(男性)',
},
},
{
// 右軸
opposite: true,
reversed: false,
categories: this.categories,
linkedTo: 0,
labels: {
step: 1,
},
accessibility: {
description: '年齢(女性)',
},
},
],
yAxis: {
title: {
text: null,
},
labels: {
formatter() {
return this.value.toLocaleString()
},
},
},
plotOptions: {
series: {
animation: false,
stacking: 'normal',
},
},
responsive: {
rules: [
{
condition: {
maxheight: 350,
},
chartOptions: {
legend: {
layout: 'horizontal',
align: 'right',
verticalAlign: 'top',
},
},
},
],
},
tooltip: {
crosshairs: true,
shared: true,
useHTML: true,
formatter() {
return this.points.map((point) => {
return `
<i style="
background-color:${point.color};
border-radius:50%;
display: inline-block;
height:6px;
margin-right:4px;
width:6px;"
></i>${point.series.name}: <b>${Math.abs(
point.y
).toLocaleString()}人</b><br>`
})
},
},
credits: {
enabled: false,
},
series: this.series,
}
},
},
}
</script>

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

概要

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

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

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でプラグインを有効化

1
2
3
4
5
6
plugins: [
{
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)
}

数値表示

桁区切りにカンマを利用したいのと、1000を1kと表現したくないので、下記を追記。

1
2
3
4
5
6
7
8
9
if (typeof Highcharts === 'object') {
Highcharts.setOptions({
lang: {
decimalPoint: '.',
thousandsSep: ',',
numericSymbols: null,
},
})
}

もし1000 -> 1千と表示したいなら、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>