SvelteとSVGを用いてインタラクティブな地図を作成する

出版日: 2024年5月29日

SvelteとSVGを用いてインタラクティブな地図を作成する

以下のようなSVG地図を持っていると仮定します:

沖縄 / Okinawa鹿児島 / Kagoshima宮崎 / Miyazaki大分 / Oita熊本 / kumamoto長崎 / Nagasaki佐賀 / Saga福岡 / Fukuoka高知 / Kochi愛媛 / Ehime香川 / Kagawa徳島 / Tokushima山口 / Yamaguchi広島 / Hiroshima岡山 / Okayama島根 / Shimane鳥取 / Tottori和歌山 / Wakayama奈良 / Nara兵庫 / Hyogo大阪 / Osaka京都 / Kyoto滋賀 / Shiga三重 / Mie愛知 / Aichi静岡 / Shizuoka岐阜 / Gifu長野 / Nagano山梨 / Yamanashi福井 / Fukui石川 / Ishikawa富山 / Toyama新潟 / Niigata神奈川 / Kanagawa東京 / Tokyo千葉 / Chiba埼玉 / Saitama群馬 / Gunma栃木 / Tochigi茨城 / Ibaraki福島 / Fukushima山形 / Yamagata秋田 / Akita宮城 / Miyagi岩手 / Iwate青森 / Aomori北海道 / Hokkaido

上記の地図は日本の都道府県を示しています。しかし、すべての都道府県が同じ色を共有しているため、見えません。それぞれの都道府県を異なる色で塗りましょう:

今の方が良く見えますね?

しかし、どの色がどの都道府県を表しているのかまだわかりませんので、凡例を追加しましょう:

さて、これで日本のどの都道府県がどこにあるのかがわかるようになりました。でも、もう一歩進みましょう。

別の凡例を持つ代わりに、それが面倒で多くのスペースを占有するため、ホバー機能を追加しましょう。都道府県にホバーすると、名前がツールチップに表示されます。

もっと良くなりましたね?

日本の都道府県の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>

次のブログ投稿では、都道府県をクリックしたときにいくつかの統計を表示しましょう。

それまで、ハッキングを楽しんでください!

このブログは英語からChatGPTによって翻訳されました。不明な点がある場合は、お問い合わせページからご連絡ください。

コメントを残す

コメント

その他のブログ

SvelteとJavaScriptを使用してシンプルで動的なツールチップを作成する

2024/06/19

SvelteとJavaScriptを使用してシンプルで動的なツールチップを作成する

JavaScriptSvelteTooltip動的シンプルツールチップフロントエンド
JavaScriptを用いて東京都のインタラクティブな地図を作成する

2024/06/17

JavaScriptを用いて東京都のインタラクティブな地図を作成する

SvelteSVGJavaScriptTailwindインタラクティブな地図東京市区町村23区地図
Matplotlibで日本語文字化けを解決できる簡単な方法

2024/06/14

Matplotlibで日本語文字化けを解決できる簡単な方法

MatplotlibグラフチャートPython日本語文字化け問題バグ
書評 | トーキング・トゥ・ストレンジャーズ 「よく知らない人」について私たちが知っておくべきこと by マルコム・グラッドウェル

2024/06/13

書評 | トーキング・トゥ・ストレンジャーズ 「よく知らない人」について私たちが知っておくべきこと by マルコム・グラッドウェル

書評トーキング・トゥ・ストレンジャーズ「よく知らない人」について私たちが知っておくべきことマルコム・グラッドウェル
日本語で最もよく使われる3000字の漢字

2024/06/07

日本語で最もよく使われる3000字の漢字

3000よく使う準漢字使用回数漢字日本語漢字リスト漢字普及率日本語能力試験独学勉強単語
VSCodeでRegexを使用してReplaceする方法

2024/06/07

VSCodeでRegexを使用してReplaceする方法

VSCodeRegex検索置き換える条件付き置換FindReplaceConditional Replace
SvelteではReadable Storeを使用するな

2024/06/06

SvelteではReadable Storeを使用するな

SvelteReadableWritableステート管理ストアStore速度メモリファイルサイズ
GzipとPakoでデータを圧縮してWebサイトのローディング速度を上げる方法

2024/06/05

GzipとPakoでデータを圧縮してWebサイトのローディング速度を上げる方法

Gzip圧縮PakoWebサイトローディング速度SvelteKit
JavaScriptを使用してWebページ上でマウスが指している単語を特定する

2024/05/31

JavaScriptを使用してWebページ上でマウスが指している単語を特定する

JavascriptマウスPointerHoverWeb開発
書評 | Originals 誰もが「人と違うこと」ができる時代 by アダム・グラント & シェリル・サンドバーグ

2024/05/28

書評 | Originals 誰もが「人と違うこと」ができる時代 by アダム・グラント & シェリル・サンドバーグ

書評Originals誰もが「人と違うこと」ができる時代アダム・グラント & シェリル・サンドバーグ
Javascriptを使用して数独を解く方法

2024/05/27

Javascriptを使用して数独を解く方法

数独を解くアルゴリズムJavaScriptコーディング
ウェブサイトへのトラフィックを1か月で10倍に増やした方法

2024/05/26

ウェブサイトへのトラフィックを1か月で10倍に増やした方法

ウェブサイトへのトラフィック増加クリックインプレッションGoogle Search Console
人生はサイクリングに似ている

2024/05/24

人生はサイクリングに似ている

サイクリング人生哲学成功
JavaScriptでバックトラッキング・アルゴリズムを用いて完全な数独グリッドを生成する

2024/05/19

JavaScriptでバックトラッキング・アルゴリズムを用いて完全な数独グリッドを生成する

数独バックトラッキング・アルゴリズム完全なグリッドJavaScript
Tailwindが素晴らしい理由とWeb開発をいかに楽にするか

2024/05/16

Tailwindが素晴らしい理由とWeb開発をいかに楽にするか

Tailwind素晴らしいフロントエンドWeb開発
PythonとGitフックを使用してサイトマップを自動的に生成する

2024/05/15

PythonとGitフックを使用してサイトマップを自動的に生成する

GitフックPythonサイトマップSvelteKit
書評 | Range (レンジ) 知識の「幅」が最強の武器になる by デイビッド・エプスタイン

2024/05/14

書評 | Range (レンジ) 知識の「幅」が最強の武器になる by デイビッド・エプスタイン

書評Range (レンジ)David Epstein (デイビッド・エプスタイン)知識の「幅」が最強の武器になる
SvelteとSvelteKitはなんですか?

2024/05/13

SvelteとSvelteKitはなんですか?

SvelteSvelteKitFront-endVite
SvelteKitで国際化(多言語化)

2024/05/12

SvelteKitで国際化(多言語化)

国際化多言語SvelteKitI18N
SvelteでCachingを用いてDeploy時間を短縮する方法

2024/05/11

SvelteでCachingを用いてDeploy時間を短縮する方法

SvelteEnhanced ImageCachingDeploy Time
SvelteとIntersection Oberverによるレイジーローディング

2024/05/10

SvelteとIntersection Oberverによるレイジーローディング

レイジーローディングウェブサイト速度の最適化SvelteIntersection Observer
遺伝的アルゴリズムで最適な株式ポートフォリオを作る方法

2024/05/10

遺伝的アルゴリズムで最適な株式ポートフォリオを作る方法

株式書状ポートフォリ最適化遺伝的アルゴリズムPython
Pythonを用いてShapeFileをSVGに変換できる方法

2024/05/09

Pythonを用いてShapeFileをSVGに変換できる方法

ShapeFileSVGPythonGeoJSON
Svelteの反応性:変数、バインディング、およびキー関数

2024/05/08

Svelteの反応性:変数、バインディング、およびキー関数

Svelte反応性バインディングキー関数
書評 | 孫子の兵法

2024/05/07

書評 | 孫子の兵法

書評The Art Of War (兵法)Sun Tzu (孫子)Thomas Cleary
スペシャリストは終了。ゼネラリスト万歳!

2024/05/06

スペシャリストは終了。ゼネラリスト万歳!

専門家ジェネラリストパラダイムシフトソフトウエア・エンジニアリング
トルコ人の有権者の投票行動をPythonでの分析

2024/05/03

トルコ人の有権者の投票行動をPythonでの分析

トルコ投票者年齢分析国家投票有権者行動分析
Seleniumを用いてトルコ投票データベースを作る方法

2024/05/01

Seleniumを用いてトルコ投票データベースを作る方法

PythonSeleniumWeb Scrapingトルコ国家投票
SvelteとTailwindを使用してInfinite Scrollできる方法

2024/04/30

SvelteとTailwindを使用してInfinite Scrollできる方法

SvelteTailwindInfinite ScrollFront-end
1年間以内で日本語を駆使できるようになるための方法

2024/04/29

1年間以内で日本語を駆使できるようになるための方法

日本語短時間言語学習日本語能力試験ビジネス日本語
SvelteとTailwindを用いたWebサイトテンプレート

2024/04/25

SvelteとTailwindを用いたWebサイトテンプレート

Web開発フロントエンドSvelteTailwind
怠惰なエンジニアとひどいデザイン

2024/01/29

怠惰なエンジニアとひどいデザイン

怠け者エンジニア質の悪い製品StarbucksSBI証券
偉大さについて

2024/01/28

偉大さについて

雄大さ人生の意味満足できる人生目的
MacBook で PDF を PNG に変換する

2024/01/28

MacBook で PDF を PNG に変換する

PDFPNGMacBookAutomator
2023年振り返り:24冊の読んだ本のまとめ

2023/12/31

2023年振り返り:24冊の読んだ本のまとめ

読書 2023振り返り
Python PILを使用して写真コラージュを作成する方法

2023/12/30

Python PILを使用して写真コラージュを作成する方法

PythonPIL画像処理コラージュ
ウェブサイトの訪問者のデバイスとブラウザを検出する方法

2024/01/09

ウェブサイトの訪問者のデバイスとブラウザを検出する方法

Javascript端末検知ブラウザ検知Website分析
ChatGPT回答の解析

2024/01/19

ChatGPT回答の解析

ChatGPT大規模言語モデル機械学習生成AI