Photo by Ferenc Almasi on Unsplash

Getting Started with Svelte and d3.js

Dominik Jäckle
CodeX
Published in
5 min readFeb 20, 2022

--

I have been using d3.js as my standard visualization library since 2013, about 2 years after Mike Bostock introduced it at the InfoVis 2011 conference. First, I created my own Vanilla JS web apps using d3. Eventually, I started using Angular and React, and my programs introduced additional complexity and somehow I spent much more time thinking about how to integrate certain visual elements rather than simply using it and having fun. Yet, that is only my subjective perspective — I am sure many developers out there may contradict me 😜.

Last year I came across Svelte and I liked its lightweight concept, also that the developers dumped the virtual DOM. This article is hopefully the first of many to come, where I will create a full-fledged visual web application step by step and from scratch.

Enough words, let’s get started! 🚀

In this article, I will cover the following steps towards our vis app:

  • The instantiation of a basic Svelte app using TypeScript
  • The integration of d3.js into the Svelte app
  • A first resizable d3.js scatterplot

Create a basic Svelte app

To get started, we will have to install Node on our system. Then, open our terminal and execute the following commands:

$ npx degit sveltejs/template svelte-d3
$ cd svelte-d3
$ npm install

The first line will create a new project called /svelte-d3 that is based on a template. Then, we change directory to the project folder and execute npm install, which will install all dependencies into the folder /node_modules (dependencies defined in package.json). The project folder should look like this:

Svelte project folder after installing all dependencies.

Finally, to use the template project as a TypeScript project, we run the following command:

$ node scripts/setupTypeScript.js
$ npm install. # run a second time to

We can now use TypeScript if we add the attribute lang="ts" to the <script></script> tags. Find additional documentation here.

We then run npm run dev in the terminal and open http://localhost:8080 in the browser. Here we go, our first Svelte website!

Install the d3.js dependency

To start with, we run the following two commands in the project folder:

$ npm install d3
$ npm install @types/d3

We will be able to start using d3 the following way in the Svelte app inside of the <script></script> tags:

A resizable scatterplot

For the sake of simplicity, we will use the main Svelte component App.svelte. In this example, we will create a random data array that contains 100 points with random x and y values in the range [0, 10]:

let data = [];
for (let i = 0; i < 100; ++i) {
data.push({x: Math.random() * 10, y: Math.random() * 10})
}

Also, we will have to create a set of initial variables:

let xScale = d3.scaleLinear().domain([0, 10]);
let yScale: = d3.scaleLinear().domain([0, 10]);
let width: number;
let height: number;
const margin = {
top: 20,
right: 20,
bottom: 30,
left: 30
};

Then, to enable resizing, we will add a redraw() function. If you are familiar with any 2D or 3D api, a redraw/repaint/… method is usually called when something changes in the frame, such as a different size. We will adapt the very same concept for this example.

onMount(() => {
redraw();
window.addEventListener('resize', redraw);
})

Note that onMount() is a Svelte specific method that is called automatically after the component has been rendered to the DOM. In addition to the initial redraw(), we added an event listener. This listener calls the redraw() method whenever the window is resized.

Let’s have a look at the redraw() method:

function redraw(): void {
// empty vis div
d3.select(vis).html(null);
// determine width & height of parent element minus the margin
width = d3.select(vis).node().getBoundingClientRect().width - margin.left - margin.right;
height = d3.select(vis).node().getBoundingClientRect().height - margin.top - margin.bottom;
// init scales according to new width & height
xScale.range([0, width]);
yScale.range([height, 0]);
// create svg and group that is translated by the margin
const svg = d3.select(vis)
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${[margin.left, margin.top]})`)
// draw x and y axes
svg.append("g")
.attr("transform", `translate(${[0, height]})`)
.call(d3.axisBottom(xScale));
svg.append("g")
.call(d3.axisLeft(yScale));
// draw data points
svg.append('g').selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx', function (d) {
return xScale(d.x);
})
.attr('cy', function (d) {
return yScale(d.y);
})
.attr('r', 7)
.style('fill', '#ff3e00')
.style('fill-opacity', '0.5')
.style('stroke', '#ff3e00');
}

Bringing all together, let’s connect a dedicated<div></div> with our visualization logic. Note that in the code above, the div is selected via d3.select(vis) .

<script lang=”ts”>
let vis;
</script>
<main>
<div id="vis" bind:this{vis}></div>
</main>
<style>
/* some styles */
</style>

The line bind:this{vis} creates a read only binding and gives a reference to this specific div element. An alternative to access this element would be to access the id via d3.select('#vis') .

Here is the code of the App.svelte component:

The end result should look similar to:

Next, check the window resize feature and observe how the scales adapt to the window size due to the redraw. You may wonder if this makes sense for a scatterplot in particular, and the answer is: probably not. However, there are several use cases out there, where you may want to scale the scales. Stay tuned, more to come soon ;)

References:

--

--

Dominik Jäckle
CodeX
Writer for

Data scientist with the BMW Group. PhD in computer science. Passionate about the intersection between HCI and ML/AI. http://dominikjaeckle.com