Tom Fevrier、Matthias Stahl両氏は先頃、Svelteでアニメーションを使用した、レスポンシブでインタラクティブなデータ可視化テクニックの概要をSvelteコミュニティに公開した。
Svelte Society (データに基づくジャーナリズムのためのSvelteとD3) の主催する講演の中で、フランスを代表する金融新聞Les EchosのグラフィックスジャーナリストであるTom Fevrier氏は、アニメーションを使用したレスポンシブな2つのグラフィックを、Sveletを使ってスクラッチから実装してみせた。
作成されたレスポンシブなグラフィックはこちらで見ることができる。
グラフィックアニメーションはこちらで確認できる。
実装の背景となった中核的な発想について、Fevrier氏は、D3とSvelteそれぞれのメリットを活用することにある、と説明している。D3にはスケールや補間、形状の計算などを含む、グラフ可視化のためのユーティリティ関数が豊富に揃っている。一方でSvelteは、グラフ可視化の(コンポーネントによる)モジュール化、(イベント処理による)対話性、リアクティブ性、(データバインディングによる)応答性といった処理に長けている。
さらに氏は、Svelteを使用することで、命令型であるD3コードを宣言型のSvelteコードに変換することが可能である点も指摘し、その論を説明するために次のようなD3コードの例を挙げている。
d3.selectAll('circle')
.data(data).enter()
.append('circle')
.attr('cx', d => xScale(d.a))
.attr('cy', d => xScale(d.b))
.attr('r', d => radiusScale(d.c))
.attr('fill', 'rebeccapurple')
Svelteでは次のような記述が可能になる。
{#each data as d}
<circle
cx = {xScale(d.a)}
cy = {xScale(d.b)}
r = {radiusScale(d.c)}
fill = 'rebeccapurple'
>
{/each}
D3自体でもアニメーションやインタラクションを処理し、それに反応して可視化データを変更することは可能だが、それらに対応するD3 API(例えば"update")はSvelteよりも複雑になっている、とFevier氏は示唆する。
ウィンドウのサイズ変更にグラフを応答させる処理は、Svelteのデータバインディングを使えば簡単に実現することができる。Svelteの組み込み機能であるanimation、motion、teransitionといったAPIを使えば、CSSを活用した、JavaScriptよりもスムーズなアニメーション — メインスレッドをブロックすることがないため — の実装が可能になる。Svelteが提供するモジュラリティを活用することで、複雑なグラフを再利用可能なコンポーネントにブレークダウンすることも可能だ。
上で再現したバブルチャートは、ScatterPlot
コンポーネントと再利用可能なAxis
コンポーネントを使って実装されている。ScatterPlot
コンポーネントのコードは次のようなものだ。
<script>
import { scaleLinear, scaleLog, scaleSqrt } from 'd3-scale';
import { extent } from 'd3-array';
import { select } from 'd3-selection';
import Axis from './Axis.svelte';
export let data;
const height = 400;
const margin = 40;
let width;
$: xScale = scaleLog()
.domain(extent(data, d => +d.gdp / +d.population))
.range([margin, width - margin]);
$: yScale = scaleLinear()
.domain(extent(data, d => +d.life_expectancy))
.range([height - margin, margin]);
$: radiusScale = scaleSqrt()
.domain(extent(data, d => +d.population))
.range([2, 50]);
const reveal = (node, { duration }) => {
const radius = select(node).attr('r');
return {
duration,
tick: (t) => select(node).attr('r', t * radius)
};
}
</script>
<div class='scatter-plot' bind:clientWidth={width}>
{#if width}
<svg width={width} height={height}>
<Axis {width} {height} {margin} scale={xScale} position='bottom' />
<Axis {width} {height} {margin} scale={yScale} position='left' />
{#each data as d}
<circle
cx={xScale(+d.gdp / +d.population)}
cy={yScale(+d.life_expectancy)}
r={radiusScale(+d.population)}
fill='rebeccapurple'
in:reveal={{ duration: 1000 }}
/>
{/each}
</svg>
{/if}
</div>
<style>
circle {
opacity: 0.9;
}
</style>`
上記のコード例で注目すべきなのは、bind:clientWidth
によってwidth
変数が常にグラフ要素のサイズによって更新されていることだ。これにより、ウィンドウのサイズが変更した場合に、グラフを(レスポンシブに)更新することが可能になる。<circle>
SVGプリミティブのin:reveal
パラメータが、対応するアニメーション効果を実装する役割を果たしている点にも注目してほしい。ここでのD3は、データのドメインと軸スケールの計算、およびグラフDOMノードの選択に使用されている。
完全なコードベースでは、CVS形式でのデータインポートにd3-fetch、d3-axis、d3-selection、可視化データのドメインと範囲の計算にd3-array、d3-shape、線形や対数や平方といったスケール計算にd3-scaleを使用している。
デモはオンラインで公開されており、実装はGitHubから取得が可能である。講演ではさらに技術的な詳細が述べられているので、読者には講演の全内容を確認することを推奨する。
Svelte Summit 2020での講演で、Matthias Stahl氏は、Svelteを使用した高度なデータ可視化の例を紹介した — こちらでは、D3は使用されていない。
可視化は高度だが、使用されているSvelte APIやテクニックはほぼ同じである。Stahl氏の可視化はさらにインタラクティブで、ユーザによるマウスの移動やグラフ上の特定エンティティのクリックにも反応する。Stahl氏もSvelteコンポーネント(ここではSlider
コンポーネント)を使って再利用性とモジュール性を実現している他、SvelteのanimationやtransitionといったAPIも使用している。さらに、コンポーネント間のコミュニケーション手段としてイベントのディスパッチや、Svelteのuse
ディレクティブを使用する。
可視化部分についても("Foreign Interference Attribution Tracker"という名称で)オンラインで公開されている。実装の内容についてはGitHubで確認が可能だ。Stahl氏がSvelteとD3で開発した高度な可視化の例(航空運賃や乳幼児死亡率など)も、オンラインでアクセスすることができる。
Svelteを使用したデータ可視化に興味のある開発者には、Svelteの開発者であるRich Harris氏が開発した実験的なチャートライブラリのPancakeも確認しておくことを推奨する。Pancakeの目標は、レスポンシブなチャートをJavaScriptを使用せずに実装することだ。
Svelte SummitはSvelteに関する仮想カンファレンスである。2020年版は10月にオンラインで開催された。講演はすべて、Svelte SocietyのYouTubeチャネルで公開される予定である。