Vue + D3 + Leaflet でヒートマップを描画する時に詰まったところ
概要
バイトの関係で Vue, D3, Leaflet を使ったヒートマップを作成することになった.なんとかできたけど色々手こずったので,それらをメモしとく.
下の画像は今回作成したヒートマップ.
取り上げる項目
- D3とLeafletのバージョン
- jsonファイルの読み込み
- this変数の扱い
- 非同期処理の扱い
D3とLeafletのバージョン
D3とLeafletもバージョンでコードがかなり変わっており,
新しめなバージョンを使うと参考になるサイトがかなり少なくなる.
Leafletについては,普通にマップを描画するだけならあまり問題はないが,
今回のようにSVGを使う場合,ところどころコードが変わる.
具体的に,以下のバージョンでコードが変わる(最新=2018年8月時点の最新).
- D3: v3とv4のあたり(最新はv5)
- Leaflet: 0.7.0と0.8.0のあたり(最新は1.3.3)
新しいバージョンのものを使う場合,2014年あたりに書かれたサイトのコードはほとんど参考にならないと思っていい.
ちなみに,以下のサイトは,D3+Leafletで地理データを描画する時に参考にした.
- 古いバージョンを使う場合
https://shimz.me/blog/d3-js/3517 - 新しいバージョンを使う場合
D3 v4 + leaflet v1 - bl.ocks.org
また,Leafletの新しいバージョンではL.geoJSON().addTo()
でjsonを読み込み,描画できる.属性値(style)もゴリゴリいじれるので,D3で描画したようなヒートマップを描画できる.
D3を使うことにこだわりがないならLeaflet単体で実装した方が簡単.
コードもごちゃごちゃしない.
jsonファイルの読み込み
発生した問題
vue-cli
のコンパイル処理で jsonファイルに文句言われる
jsonファイルをassets
などのディレクトリに配置するとvue-cli
のコンパイル処理にエラーを吐かれる.コンパイルできない的なこと言われた気がする.d3.json()
でファイルを読めなかった
理由はよくわらん.
今回の対処方法
- jsonファイルを
static
ディレクトリに置く
ここにおいたらコンパイル処理されないっぽい. axios.get()
を使う
多分axios
じゃなくてもいいとは思う.ajax
みたいな適当なGET処理してくれるの使えばjsonファイルを読めると思う.
this変数の扱い
vueを使うとJSとHTML間で変数や関数を共有できて便利なのだが,その共有オブジェクトを使うために,this
変数を使わなければならない.
例えば,flag
という名前の変数を定義し,その変数を使おうと思ったら,以下の様にアクセスしなければならない.
this.flag = this.flag + 1;
(flag変数をインクリメントする処理)
しかし,JSでは色々な処理でthis
変数が上書きされてしまい,上記のような処理を実行できなくなる.
これの対処方法として,this
が上書きされる直前にthis
を退避させる方法がある.
例えとして,以下はaxios.get()
でGETしたgeojsonデータをthis.geojson
に格納する例である.
const vueObj = this; axios.get('../../../static/kurashiki.geojson') .then(function (json) { vueObj.geojson = json.data; });
これでもちゃんと動くが,axios
に対しては以下の様にbind()
を使うことで,axios
内でthis
にアクセスできる.
axios.get('../../../static/kurashiki.geojson') .then(function (json) { this.geojson = json.data; }.bind(this));
非同期処理の扱い
ヒートマップを描画する前にデータ取得などの処理をする必要があり, そういったデータ取得処理は非同期に実行されてしまう. このため,特定の前処理が終わるまで他の処理を実行させないようにする必要がある.
今回以下のコードを使った
- async
- await
- Promise.all()
詳しくは以下のサイトがわかりやすい.
ざっくり説明すると,関数を宣言する際,その関数名の前にasync
を付けるとその関数が非同期関数となる.返り値がPromise
になったり関数内でawait
を使えたりする.
await
を使うことで非同期処理の実行中,他の処理を停止させられる.
以下は実際に使ってみた例.
async setGeojson () { await axios.get('../../../static/kurashiki.geojson') .then(function (json) { this.geojson = json.data; }.bind(this)); }
上記例では,非同期処理のaxios.get()
の実行中,その他処理が止まる(たぶん).
上記のような非同期関数をまとめて実行し,
それら全ての完了を待ちたい時に便利なのがPromise.all()
.
Promise.all()
を使ったコードの例は以下の通り.
Promise.all([this.drawMap(), this.setGeojson(), this.setAreaValue()]) .then(() => { this.drawFeatures() })
上記例では,this.drawMap()
, this.setGeojson()
, this.setAreaValue()
の処理が全て完了した後,this.drawFeatures()
を実行するようになっている.
ちなみに,上記で呼び出している関数の処理内容は以下の様になっている.
this.drawMap()
: オープンストリートマップを描画する.this.setGeojson()
: geojson(地理データ)を読み込み,vueの変数に格納する.this.setAreaValue()
: 各エリアの値をvueの変数に格納する.この値を基に書くエリアの色の濃さを定義する.this.drawFeatures()
: ヒートマップを描画する.
今回のヒートマップ作成に対する感想
なんとかヒートマップを作成できた.非同期処理の扱いが前よりわかってきてなかなか勉強になった.
ただまだ理解しきれていないので,次に非同期処理問題の絡むコードを扱う時は適当なコードを書くのではなく,勉強してきれいなコードを書きたい.