Tom Fevrier and Matthias Stahl recently gave the Svelte community an overview of the techniques that can be used to achieve responsive, interactive, and animated data visualization with Svelte.
In a talk organized by the Svelte Society (Svelte and D3 for data-based journalism), Tom Fevrier, a graphics journalist for the leading French financial newspaper Les Echos, implemented two animated, responsive graphs from scratch with Svelte.
The resulting responsive graphs can be observed here:
The graph animation can be observed here:
Fevrier explained that the core idea behind the implementation is to use the respective strengths of both D3 and Svelte. D3 has a large set of utility functions for graph visualization that includes the computation of scales, interpolation, shapes, and more. On the other hand, Svelte handles best the modularity (through components), interactivity (event handling), reactivity, and responsiveness (through data binding) of the graph visualization.
Fevrier went further by positing that Svelte allows developer to turn imperative D3 code into declarative Svelte code. Fevrier illustrated his thesis by observing that the following D3 code:
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')
becomes with Svelte:
{#each data as d}
<circle
cx = {xScale(d.a)}
cy = {xScale(d.b)}
r = {radiusScale(d.c)}
fill = 'rebeccapurple'
>
{/each}
While D3 can handle on its own animations and interactions, and react to changes in the visualized data, Fevrier hinted that the corresponding D3 APIs (e.g. ``update`) generate a higher complexity than Svelte’s.
The responsiveness of the graph to the size of its enclosing window is achieved easily with Svelte data bindings. Developers can use Svelte’s built-in animation, motion and transition APIs to implement smooth animations that leverage CSS rather than JavaScript — they do not block the main thread. Developers can also break down a complex graph into reusable components, leveraging the modularity offered by Svelte.
The bubble chart reproduced thereabove is implemented with a ScatterPlot
component and a reusable Axis
component. The code for the ScatterPlot
component is as follows:
<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>`
Note how in the previous code sample, the bind:clientWidth
ensures that the width
variable is always updated with the size of the graph element, allowing to update the graph when the size of the window changes (responsiveness). Note also how the in:reveal
parameter of the <circle>
SVG primitive serves to implement the corresponding animation effect. D3 is used here to compute the domain of the data, the scales for the axes, and to select the graph DOM node.
The full codebase uses d3-fetch to import data in the CSV format; d3-axis; d3-selection; d3-array to compute the domain and ranges for the visualized data; d3-shape; and d3-scale to compute linear, logarithmic, and square scales.
The demo is available online. The implementation is available on GitHub. The reader is encouraged to review the full talk that contains plenty of additional technical details.
In a talk at Svelte Summit 2020, Matthias Stahl provided an example of advanced data visualization with Svelte — this time without D3.
While the visualization is more advanced, Stahl uses similar Svelte APIs and techniques. The Stahl visualization is additionally interactive as it reacts when the user moves his mouse or clicks over given entities of the graph. Stahl also uses Svelte components (here a Slider
component) for reusability and modularity, the Svelte animation and transition APIs. Stahl additionally uses event dispatching for communication between components, and the Svelte use
directive.
The visualization (titled Foreign Interference Attribution Tracker) is also available online. The implementation can be consulted on GitHub. Examples of advanced visualization created by Stahl with Svelte and D3 (e.g. flight prices or childhood mortality) can also be accessed online.
Developers interested in data visualization with Svelte are also encouraged to review Pancake, an experimental charting library for Svelte, created by Rich Harris, the creator of Svelte. Pancake strives to implement responsive charts with no JavaScript.
Svelte Summit is a virtual conference about Svelte. The 2020 edition took place online in October. The full list of talks will be made available on the Youtube channel of the Svelte Society.