👋
D3.jsでクリッカブルマップを作成する
経緯
キャップ野球情報局というサービスを開発しています。
全国にあるキャップ野球チームを網羅しているのですが、地域ごとに目次があった方が便利だなーと常々思っていました。クリックできる地図があるともっと便利だと。
調べてみるとD3.jsを使うとクリッカブルマップが作れそう...
D3.jsそういえば触ったことあったなぁ...
D3.jsの導入
前に使っていたとはいえ、Nextjsに移行したときに廃止した機能で、package.jsonからも消えていたので新たに入れ直しました。
yarn add d3
yarn add @types/d3 -D
注意した方がいいこと
最新のv7をNext.jsにインポートするにはダイナミックインポートを使う必要があります。
dynamic(()=>import('d3'))
私はv6を使うことにしました。 "d3": "^6.3.1"
実装
昔のコードはreactのclass構文のものだったので多少手を加える必要がありました。基本的にはuseEffect
とuseRef
を使えばいいかと思います。
json地図データの入手・加工は割愛します。
item.properties.id
は都道府県コード(prefCode)が入っており、returnTeams
は地域別のチーム一覧を返しています。returnPref
は都道府県コードから都道府県名を返すようにしています。fill-opacity
でチーム数に応じて色の濃淡が出るようにopacityの調整を行っています。
const mapRef = useRef(null);
useEffect(() => {
var w = 320;
var h = 350;
// 地図の投影図法を設定する.
var projection = d3
.geoMercator()
//中心座標
.center([136.5, 37.5])
.scale(1300)
.translate([w / 2, h / 2]);
// GeoJSONからpath要素を作る.
var path = d3.geoPath().projection(projection);
const jpn = mapJson;
const svg = d3.select(mapRef.current);
const selection = svg
.attr("height", h)
.attr("width", w)
.selectAll("path")
.data(jpn.features)
.enter()
.append("path")
.attr("class", (d) =>
d.properties.region
? `region ${d.properties.region.toLowerCase()}`
: "region"
)
.attr("class", function (d) {
return "pref ";
})
.attr("d", path)
.attr(`stroke`, `#666`)
.attr(`stroke-width`, 0.25)
.style(`fill`, `#FF0000`)
.style(`fill-opacity`, (item) => {
return returnTeams([item.properties.id]).length / 20;
})
.on(`mouseover`, (ev, d) => {
// マウス位置の都道府県領域を赤色に変更
d3.select(ev.currentTarget).attr(`fill`, `#CC4C39`);
d3.select(ev.currentTarget).attr(`stroke-width`, `1`);
})
.on(`mouseout`, (ev, d) => {
d3.select(ev.currentTarget).attr(`fill`, `#FF0000`);
d3.select(ev.currentTarget).attr(`stroke-width`, `0.25`);
})
.on(`click`, (ev, d) => {
// マウス位置の都道府県領域を赤色に変更
d3.select(ev.currentTarget).attr(`fill`, `#CC4C39`);
d3.select(ev.currentTarget).attr(`stroke-width`, `1`);
// スクロール用要素取得
const rect = document.getElementById(returnPref(Number(d.properties.id))).getBoundingClientRect();
scrollTo(0, rect.y)
});
}, [mapRef]);
return (
<div>
<svg ref={mapRef} />
</div>
);
参考
Discussion