SvelteのコンポーネントライブラリーであるLayerChartを触ってみた

## はじめに
SvelteのコンポーネントライブラリーであるLayerChartを触ってみようと思い立ち、触ってみましたので、その記録を記したいと思います

LayerChartは、Svelte向けのデータ視覚化ライブラリーであり、折れ線グラフや棒グラフなど、データの視覚化に必要なコンポーネント群を提供しています

単体での利用は可能ですが、Svelte UXというUIコンポーネントライブラリのインストール時にLayerChartのオプションがありますので、今回はそちらを利用してすぐに触ってみようという形で進めました

https://svelte-ux.techniq.dev/

https://www.layerchart.com/

## インストール
※パッケージマネージャーにpnpmを利用していますが、npmに読み替えてもOKです
```
pnpm create svelte-ux@latest
```

これで対話型のインストールが始まります
テンプレート選択時にLayerChartがありますので、そちらを選択して進めます
![スクリーンショット 2024-10-23 13.46.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2788/2a284810-5fab-e332-2d7b-38af8eabf51e.png)

最後にSvelte5使うかを聞かれますが今回は4のまま進めます
（既にstableでしたが追いついていないのでした、次の記事からは5でいきたい気持ちです）
![スクリーンショット 2024-10-23 13.47.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2788/2afbe53f-7cfd-36af-f358-3d11467970d3.png)

ローカルサーバーを起動すると以下のような画面が出ます
~~若干崩れていますが、~~ TaiwindCSSなども同時にセットアップしてくれています
![スクリーンショット 2024-10-23 13.55.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2788/633f84c3-160c-6379-8128-29654a7cf846.png)

~~ちなみに、上記は実はCSSの設定が壊れていて、手直しすると以下のような感じになります~~
こちらは修正のPRを送ってマージされたので、以下のデザインになると思います
![スクリーンショット 2024-10-29 4.17.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2788/381ae320-3a7c-7b74-b8ac-96578da1f350.png)


## LayerChartのコンポーネントを使ってみる
せっかくなのでGeo系のコンポーネントを使ってみようとしたのですが、その前に簡単な例をやってみようということで、Examplesの Cartesian & Polarの先頭にある、Arcの例をそのまま追加してみます

```javascript:routes/arc/+page.svelte
<script lang="ts">
	import { Arc, Chart, Svg, Group, LinearGradient, Text } from 'layerchart';
	import { RangeField } from 'svelte-ux';

	let value = 50;
</script>

<main class="p-2">
	<div class="mb-2">
		<RangeField label="Value" bind:value />
	</div>
	<div class="h-[120px] p-4 border rounded">
		<Chart>
			<Svg center>
				<Group y={16}>
					<LinearGradient class="from-secondary to-primary" let:url>
						<Arc
							{value}
							range={[-120, 120]}
							outerRadius={60}
							innerRadius={50}
							cornerRadius={5}
							spring
							let:value
							fill={url}
							track={{ class: 'fill-none stroke-surface-content/10' }}
						>
							<Text
								value={Math.round(value) + '%'}
								textAnchor="middle"
								verticalAnchor="middle"
								class="text-3xl tabular-nums"
							/>
						</Arc>
					</LinearGradient>
				</Group>
			</Svg>
		</Chart>
	</div>
</main>
```
左のメニューに追加して、見てみるとこんな感じで出来ています（グラデーションがかかるはずでしたが、色の設定をちゃんとしていないのでうまくいってないっぽいですが）
Svelte UXのRangeFieldコンポーネントとの相性も良いですね
![Kapture 2024-10-29 at 01.32.59.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2788/9e1dea36-4895-0289-a710-c1cad89e59df.gif)

こんな感じで大量のコンポーネントが用意されていますが、中身を見るとD3を利用しているものが多そうです
[D3](https://d3js.org/)をSvelteでいい感じに使えるライブラリーみたいなイメージでしょうか？

## Geoの例
最後にGeoのコンポーネントの例をやってみます
まず、d3-geoの追加が必要です
```
pnpm add d3-geo
```
例ではtopojson-clientも利用していたので追加しています
```
pnpm add topojson-client
```
型定義も入れておきます
```
pnpm add -D @types/d3-geo
pnpm add -D @types/topojson-client
pnpm add -D @types/topojson-specification
```
データ読み込み箇所

```javascript:routes/geoTile/+page.ts
import type { GeometryCollection, Topology } from 'topojson-specification';
import pageSource from './+page.svelte?raw';

export async function load() {
	return {
		geojson: (await fetch('https://cdn.jsdelivr.net/npm/us-atlas@3/states-10m.json').then((r) =>
			r.json()
		)) as Topology<{
			states: GeometryCollection<{ name: string }>;
		}>,
		meta: {
			pageSource,
			related: ['examples/ZoomableTileMap']
		}
	};
}
```
GeoTile使用例
```javascript:routes/geoTile/+page.svelte
<script lang="ts">
	import { geoMercator } from 'd3-geo';
	import { feature } from 'topojson-client';
	import { Canvas, Chart, GeoPath, GeoTile } from 'layerchart';

	export let data;
	const states = feature(data.geojson, data.geojson.objects.states);

	$: filteredStates = {
		...states,
		features: states.features.filter(
			(d) => Number(d.id) < 60 && d.properties.name !== 'Alaska' && d.properties.name !== 'Hawaii'
		)
	};
	let zoomDelta = 0;

	let selectedFeature: typeof filteredStates | (typeof filteredStates.features)[0];
	$: selectedFeature = filteredStates;
</script>

<main class="p-2">
	<div class="h-[600px]">
		<Chart
			geo={{
				projection: geoMercator,
				fitGeojson: selectedFeature
			}}
		>
			<Canvas>
				<GeoTile
					url={(x, y, z) => {
						return `https://tile.openstreetmap.org/${z}/${x}/${y}.png`;
					}}
					{zoomDelta}
				/>
			</Canvas>
			<Canvas>
				<GeoPath geojson={filteredStates} class="stroke-black/20" />
			</Canvas>
		</Chart>
	</div>
</main>

```
![スクリーンショット 2024-10-29 3.59.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2788/8e2103cc-d1fd-f34e-2b97-d7c4ad4c4ba8.png)
こんな感じで地図が出ます
（LayerChartのExampleをそのまま利用したので、場所はアメリカです）
タイルURLの指定の仕方がちょっと分かりづらくてハマりました

## 感想
触ってみた感想ですが、[D3](https://d3js.org/)をラップして使いやすくしているような感じを受けました
グラフ描画が必要であれば、選択肢としてなかなか良いのではないかと思います
Geo系のコンポーネントは個人的には使いづらくて、使い慣れている[Maplibre GL JS](https://maplibre.org/maplibre-gl-js/docs/)の方が良いかも…と思いました