ゆっくり開発

開発したい時に開発するブログ

Vue + D3 + Leaflet でヒートマップを描画する時に詰まったところ

概要

バイトの関係で Vue, D3, Leaflet を使ったヒートマップを作成することになった.なんとかできたけど色々手こずったので,それらをメモしとく.
下の画像は今回作成したヒートマップ.

f:id:uttnaoki:20180819024312p:plain

取り上げる項目

  • 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で地理データを描画する時に参考にした.

また,Leafletの新しいバージョンではL.geoJSON().addTo()jsonを読み込み,描画できる.属性値(style)もゴリゴリいじれるので,D3で描画したようなヒートマップを描画できる.
D3を使うことにこだわりがないならLeaflet単体で実装した方が簡単. コードもごちゃごちゃしない.

jsonファイルの読み込み

発生した問題

  1. vue-cliコンパイル処理で jsonファイルに文句言われる
    jsonファイルをassetsなどのディレクトリに配置するとvue-cliコンパイル処理にエラーを吐かれる.コンパイルできない的なこと言われた気がする.
  2. d3.json()でファイルを読めなかった
    理由はよくわらん.

今回の対処方法

  1. jsonファイルをstaticディレクトリに置く
    ここにおいたらコンパイル処理されないっぽい.
  2. 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()

詳しくは以下のサイトがわかりやすい.

qiita.com

ざっくり説明すると,関数を宣言する際,その関数名の前に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(): ヒートマップを描画する.

今回のヒートマップ作成に対する感想

なんとかヒートマップを作成できた.非同期処理の扱いが前よりわかってきてなかなか勉強になった.
ただまだ理解しきれていないので,次に非同期処理問題の絡むコードを扱う時は適当なコードを書くのではなく,勉強してきれいなコードを書きたい.