以下のようなSVG地図を持っていると仮定します:
上記の地図は日本の都道府県を示しています。しかし、すべての都道府県が同じ色を共有しているため、見えません。それぞれの都道府県を異なる色で塗りましょう:
今の方が良く見えますね?
しかし、どの色がどの都道府県を表しているのかまだわかりませんので、凡例を追加しましょう:
さて、これで日本のどの都道府県がどこにあるのかがわかるようになりました。でも、もう一歩進みましょう。
別の凡例を持つ代わりに、それが面倒で多くのスペースを占有するため、ホバー機能を追加しましょう。都道府県にホバーすると、名前がツールチップに表示されます。
もっと良くなりましたね?
日本の都道府県のSVG地図は、このGitHubリポジトリから取得したものです。Svelteを使用してSVGを動的に操作することやツールチップを追加することは、予想以上に簡単ではありませんでした。上記の構造を作成するのに30分かかると予想していましたが、約2時間かかりました。
例えば、SVG内のg要素はホバーイベントを受け付けません。また、上記の地図のためにファイルに3回SVGを複製したくなかったので、SVGを別のSvelteファイルに移動し、一度インポートして地図を作成するときにそのSVGファイルをクローンしました。これによりコードがはるかにクリーンになります。
以下のコードを参照してください:
<script>
import { onMount } from 'svelte';
// Get the SVG file from GitHub repo: https://github.com/geolonia/japanese-prefectures/blob/master/map-full.svg
import Prefectures from './Prefectures.svelte';
function getUniqueColor(n) {
const rgb = [0, 0, 0];
for (let i = 0; i < 24; i++) {
rgb[i % 3] <<= 1;
rgb[i % 3] |= n & 0x01;
n >>= 1;
}
return '#' + rgb.reduce((a, c) => (c > 0x0f ? c.toString(16) : '0' + c.toString(16)) + a, '');
}
let container, svg, colorfulSvg, tooltipSvg, tooltip;
$: tooltip = null;
let prefectures = [];
let noOfPefectures = 47;
let randomColors = Array.from({ length: noOfPefectures }, (_, index) => index)
.map((value) => ({ value, sort: Math.random() }))
.sort((a, b) => a.sort - b.sort)
.map(({ value }) => getUniqueColor(value));
onMount(() => {
svg = container.firstChild;
colorfulSvg = svg.cloneNode(true);
colorfulSvg.getElementById('prefectures').childNodes.forEach((node, index) => {
let color = randomColors[index];
let code = node.getAttribute('data-code');
let titleText = node.getElementsByTagName('title')[0].textContent;
let title = {
en: titleText.split('/')[1].trim(),
tr: titleText.split('/')[1].trim(),
jp: titleText.split('/')[0].trim()
};
prefectures = [...prefectures, { color, title, code }];
node.setAttribute('fill', randomColors[index]);
});
document.getElementById('colorfulContainer').appendChild(colorfulSvg);
tooltipSvg = colorfulSvg.cloneNode(true);
tooltipSvg.getElementById('prefectures').childNodes.forEach((node, index) => {
let span = document.createElement('span');
span.classList.add('tooltiptext');
span.innerHTML = prefectures[index].title[$language];
node.classList.add('tooltip');
node.appendChild(span);
});
document.getElementById('tooltipContainer').appendChild(tooltipSvg);
document.addEventListener('mouseover', function (e) {
if (!e.target.closest('#tooltip')) {
tooltip.style.visibility = 'hidden';
}
let target = e.target.closest('#tooltipContainer #prefectures g');
if (target) {
let elemRect = target.getBoundingClientRect();
let bodyRect = document.getElementById('tooltipContainer').getBoundingClientRect();
tooltip.style.top = `${elemRect.top - bodyRect.top}px`;
tooltip.style.left = `${elemRect.left - bodyRect.left}px`;
tooltip.style.visibility = 'visible';
let titleText = target.getElementsByTagName('title')[0].textContent;
tooltip.textContent =
$language == 'jp' ? titleText.split('/')[0].trim() : titleText.split('/')[1].trim();
}
});
});
</script>
<div id="mainContainer" class="flex gap-4 flex-col relative">
<p>Original MAP</p>
<div class="flex justify-center" bind:this={container}><Prefectures /></div>
<p>Colorful MAP</p>
<div class="flex justify-center" id="colorfulContainer"></div>
<p>Legend</p>
{#key prefectures}
<div class="flex flex-wrap gap-2">
{#each prefectures.sort((a, b) => a.code - b.code) as pref}
<span
class="text-sm text-white px-2 py-1 rounded-full"
style="background-color: {pref.color}">{pref.title[$language]}</span
>
{/each}
</div>
{/key}
<p>Tooltipped MAP</p>
<div class="flex justify-center relative" id="tooltipContainer">
<span
style="visiblity:hidden;"
bind:this={tooltip}
id="tooltip"
class="absolute px-2 py-1 rounded bg-white/[0.9]"
></span>
</div>
</div>
次のブログ投稿では、都道府県をクリックしたときにいくつかの統計を表示しましょう。
それまで、ハッキングを楽しんでください!