d3.jsで地図をぬるっとアニメーションさせる。


前回、d3で気象庁降灰予報を描画しましたが、
https://qiita.com/miyawa-tarou/items/f101f0248cdc5744302d
地図の中心緯度経度やズームなどは一度描画した後でも変えることができます。
また単に変えるのではなく、ぬるっとスムーズにアニメーションしながら動かすことができます。

完成版はこちら:https://miyawa-tarou.github.io/d3_nuru_map/

基本は前回のコードをもとに説明します。今回の説明に邪魔なので桜島に置いた丸は消しています。
https://github.com/miyawa-tarou/d3_nuru_map/tree/01.setting

1.クリックしたらズームするようにする

やることは、初めに設定した

        // 中心座標とスケール
        var projection = d3.geoMercator()
            .center([137, 38])
            .scale(1600)
            .translate([width / 2, height / 2]);

この部分を
pathのtransform属性で再定義するような形になります。

今回鹿児島をクリックしたらズームするという形にします。

            svg.selectAll("path")  // おまじない。というか理解できてない
                .data(topojson.feature(data, data.objects.pref).features)
                .enter() // dataで入れた配列をそれぞれ以下のpathにしていく
                .append("path")
                .attr("d", path) // <path d="">に path()の返り値を入れていく
                .attr("fill", function(d,i){ return d.properties.code === 46 ? "darkgreen" : "green";}) // 鹿児島だけダークグリーン
                .on("click", function(d,i){ // 各pathをクリックすると実行されることを書く
                    if (d.properties.code === 46) { // 鹿児島をクリックしたときに
                        var zoomCenter = projection([130.6, 31.6]); // 中心座標
                        var zoom = 20000/1600; // ズーム
                        svg.selectAll("path").attr("transform", "translate(" + width/2 + "," + height/2 + ")scale(" + zoom + ")translate(" + - zoomCenter[0] + "," + - zoomCenter[1] + ")");
                    }
                });

すべてのpathに対して移動・ズームするため、
svg.selectAll("path")
に対して行っています。classやidなどで指定を限定すれば一部だけ動かすみたいなことも可能です。

この時ズームに20000/1600を入れていますが、ここは「はじめの設定からのズーム比」になります。
なので前回桜島にズームしたときscale(20000)にしたため、初期のズーム1600との比の20000/1600にしています。
もし2度以上移動する場合も、scale()ははじめの1600基準の数値になります(多分)

そしてtransform属性に
translate():おまじないのwidth/2, height/2 を入れる
scale():上で説明した通り
translate():移動先の座標
を入れます(参考:https://developer.mozilla.org/ja/docs/Web/SVG/Attribute/transform

scale()を指定しなければ移動するだけです。1以下にすればズームアウトするはずです。

2.降灰予報もズームする

1では地図はズームしましたが、肝心の降灰予報がごみのようにズームされずにそのまま残っています。
これは前回書いたのがpolygonタグだったためで、pathで書けば同様に処理されます。

初めにMをつけて区切り文字をLにしZで終了すると同じものが描画できます。
https://qiita.com/a-ide/items/107c9044d0f4e0354112#%E3%83%91%E3%82%B9

            svg.append('g')
                .attr('class', 'ashFall')
                .append("path")
                .attr("d", 'M' + svgCoordinate.join('L') + 'Z') // "Mx,yLx,yLx,yZ"

3.ぬるっと動かす

d3ではズームする際に簡単にアニメーションにすることができます

svg.selectAll("path").transition().duration(2000).attr("transform", ...);

.transition().duration(2000)
を追加しただけです。
duration()の引数が移動の時間でミリ秒になります。

ここが一番楽しいけどQiitaには貼れないのが残念
ちなみに回転とかもできますが、今回はそれはなしで。

また動き方も直線的なのから加速していくのなども指定できます(ease()を挟む)
詳しくは

4.火山灰の枠線をどうにかする

ズームすると線がズームの分太くなります。
なんかきもいのでこれも変更します。
地図の方は影響を避けるために別にアニメーションを指定します。

            svg.select(".ashFall").select('path').transition().duration(2000).attr("stroke-width", 1600/20000).attr("transform", "translate(" + width/2 + "," + height/2 + ")scale(" + zoom + ")translate(" + - zoomCenter[0] + "," + - zoomCenter[1] + ")");

.attr("stroke-width", 1600/20000)を追加しています。
ズーム前はstroke-widthが1だったので、ズームの分細くしている感じです。
二つのアニメーションが同時に動かせなくて、上で設定したtransformよりこちらの設定が勝ってしまうので、transformをこちらにも記載します。
なんかきれいに細くなっていってない気がするが。。。

ex.クリッカブルに見せるためにカーソルにする

鹿児島をクリックできる感を持たせるためにカーソルにします。
せっかくなので、都道府県ごとにclassを指定し、鹿児島のclassに対してカーソルがつくスタイルシートを当てます

        svg.selectAll("path")  // おまじない。というか理解できてない
            .data(topojson.feature(data, data.objects.pref).features)
            .enter() // dataで入れた配列をそれぞれ以下のpathにしていく
            .append("path")
            .attr("d", path) // <path d="">に path()の返り値を入れていく
            .attr("class", function(d,i){ return "Prefs Pref" + d.properties.code;}) // すべての都道府県にPrefsクラスを、各都道府県にPref{Code}クラスを指定

<style>
    .Prefs {
        fill: green; /* 緑で塗る */
    }
    .Pref46 { /* 鹿児島限定 */
        fill: darkgreen;
        cursor: pointer;
    }
</style>