Line Chart with Svelte and D3
AnupJoseph
Posted on October 20, 2021
This blog is second in a series of (unofficial) course notes for the Data Visualization with React and D3 series by Curran Kelleher. Read the introductory blog post here.
The next chart type in the course is a line-chart based on San Francisco temperature data, available here. We will use the scatter plot code from the last chart here. Some interesting things about the dataset , its temporal in nature so the chart type must be able to show that change properly.
The row
function in the last post is note very useful here. Lets convert the temperature
column in to numbers. Also one of the columns is a timestamp so let's use the JS Date
constructor to handle that.
const row = function (data) {
data.temperature = +data.temperature;
data.timestamp = new Date(data.timestamp);
return data;
};
onMount(async () => {
dataset = await csv(
"https://gist.githubusercontent.com/curran/60b40877ef898f19aeb8/raw/9476be5bd15fb15a6d5c733dd79788fb679c9be9/week_temperature_sf.csv",
row).then((data) => {
return data;
});
});
I want to plot temperature on the Y-axis and timestamp on X-axis.Since X-axis is a timestamp we are going to use scaleTime
from D3. I am going to modify the scales accordingly
$: xScale = scaleTime()
.domain(extent(dataset, (d) => d.timestamp))
.range([0, innerWidth]);
$: yScale = scaleLinear()
.domain(extent(dataset, (d) => d.temperature))
.range([innerHeight, 0]);
Let's just pass the temperature and timestamp points to these scales in the scatter plot logic we had before. The Axis
doesn't need to be changed. Let's change the circle
logic as follows:
<circle
cx={xScale(data.timestamp)}
cy={yScale(data.temperature)}
r="5"
style="fill:firebrick"
/>
Now we just need to connect these points. And it just so happens that D3 has a function to do exactly that. The line
function in D3 gives a path provided a set of points. We can also specify the curveNatural
to the line function.
$: line_gen = line()
.curve(curveNatural)
.x((d) => xScale(d.timestamp))
.y((d) => yScale(d.temperature))(dataset);
Let's add this line as a path element to the svg <g>
element. And style it as well so that it shows up as a line and not a closed path.
<path d={line_gen} />
<style>
path {
fill: transparent;
stroke: rgb(18, 153, 90);
}
</style>
Yeah, I am not a fan of those red dots either. The line probably needs to be a bit thicker as well. Similarly we need to change the format of the ticks in the bottom axis. There are a few other stylistic changes as well and I am going to do them in a single sweep. This is the final chart produced:
And here's the code for the same:
If you want a simple line chart or use a line chart as base for something else, then this is what you probably want. I actually want to go a bit deeper and achieve a sort of hand drawing like effect.
The property we need is stroke-dasharray
and stroke-dashoffset
. Think of it like so, instead of drawing a continous path, we are drawing a dashed path. However the dashes themselves are pretty big, as big as the path required for this data or more infact. Okay, rather than reading through that terrible explanation you can read through Cassie Evans fantasic, interactive explanation here.
I am setting the stroke-dasharray
to a nice large number.stroke-dashoffset
is set to 0.
path {
fill: transparent;
stroke: rgb(18, 153, 90);
stroke-width: 2.5;
stroke-linejoin: round;
stroke-dasharray: 4400;
stroke-dashoffset: 0;
}
Visually there's absolutely no difference. However the underlying changes allow us to create the animation. Let's do just that:
path {
fill: transparent;
stroke: rgb(18, 153, 90);
stroke-width: 2.5;
stroke-linejoin: round;
stroke-dasharray: 4400;
stroke-dashoffset: 0;
animation: draw 8.5s ease;
}
@keyframes draw {
from {
stroke-dashoffset: 4400;
}
to {
stroke-dashoffset: 0;
}
}
Initially the stroke-dashoffset
is set at 4400. Hence the dash is outside the SVG as each dash is of 4400 as well. As the transition continues the dash slowly becomes visible in the SVG viewBox.
Now I actually wanted ever so slightly more than this. I wanted to have the circles we had earlier to appear on the path as the line moves ahead. I was unable to recreate the effect perfectly, but the effect is still pleasing so here goes. I am using the fly
transition from Svelte. Let's modify circle
accordingly.
<circle
cx={xScale(data.timestamp)}
cy={yScale(data.temperature)}
r="3"
in:fly={{ duration: 5000, delay: i * 15 }}
/>
And this is the chart produced by adding that code
Beautiful!! I really like the teasing effect of the dots, appearing seeming at once all together yet seperate.
Here's the final code for the chart -
That's all for today! Have a nice day!
Posted on October 20, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 26, 2024
November 26, 2024