Iterating over a data array
In this tutorial, we will be iterating over data using svelte {#each} {/each}
blocks. We don’t need D3.js at this stage.
Circles
Let’s use svg circle
element to place a few circles on our svg.
We are using $state()
rune
in Svelte 5 to make our data reactive.
<script>
// data which will be updated
let data = $state([
{ x: 180, y: 30, r: 5, fill: 'orange' },
{ x: 100, y: 40, r: 25, fill: 'red' },
{ x: 170, y: 160, r: 50, fill: 'cyan' },
{ x: 120, y: 220, r: 2, fill: 'yellow' },
{ x: 30, y: 100, r: 10, fill: 'fuchsia' }
]);
// I have added a setTimeout function which will update the data
setTimeout(() => {
data = [
{ x: 40, y: 70, r: 5, fill: 'red' },
{ x: 100, y: 210, r: 25, fill: 'orange' },
{ x: 120, y: 100, r: 50, fill: 'blue' },
{ x: 350, y: 220, r: 2, fill: 'red' },
{ x: 230, y: 190, r: 10, fill: 'white' }
];
}, 2000);
</script>
<div>
<svg viewBox="0 0 600 300">
{#each data as d}
<circle cx={d.x} cy={d.y} r={d.r} fill={d.fill} />
{/each}
</svg>
</div>
<style>
/* The circles will transition to their new state using css. */
circle {
transition: all 1s ease;
}
</style>
Rects
Similarly, we can use svg <rect>
elements to create bars.
<script>
// data which will be updated
let data = $state([
{ x: 10, y: 10, width: 5, height: 40 },
{ x: 10, y: 60, width: 5, height: 40 },
{ x: 10, y: 110, width: 5, height: 40 },
{ x: 10, y: 160, width: 5, height: 40 },
{ x: 10, y: 210, width: 5, height: 40 }
]);
setTimeout(() => {
data = [
{ x: 10, y: 10, width: 450, height: 40 },
{ x: 10, y: 60, width: 320, height: 40 },
{ x: 10, y: 110, width: 160, height: 40 },
{ x: 10, y: 160, width: 80, height: 40 },
{ x: 10, y: 210, width: 10, height: 40 }
];
}, 4000);
</script>
<div>
<svg viewBox="0 0 600 300">
{#each data as d}
<rect x={d.x} y={d.y} width={d.width} height={d.height} fill="white" />
{/each}
</svg>
</div>
<style>
rect {
transition: all 1s ease;
}
</style>
Text
Let’s add text labels to the bars we just created. In SVG, text elements are added by using the <text>
element, which can be positioned using x
and y
attributes. These attributes specify where the text will be rendered, and their values can be animated programmatically to achieve smooth transitions.
We will use Tween
from svelte/motion, a wrapper for values that smoothly interpolate (or “tween”) between their current state and a target value. This allows us to animate both the bar widths and their corresponding text labels to their final values.
<script>
import { Tween } from 'svelte/motion';
import { cubicOut } from 'svelte/easing';
let data = $state([
{ x: 10, y: 10, width: 5, height: 40 },
{ x: 10, y: 60, width: 5, height: 40 },
{ x: 10, y: 110, width: 5, height: 40 },
{ x: 10, y: 160, width: 5, height: 40 },
{ x: 10, y: 210, width: 5, height: 40 }
]);
// Animate widths using Tween
const widths = Tween.of(() => data.map((d) => d.width), {
duration: 1000,
easing: cubicOut
});
const handleClick = () => {
data = [
{ x: 10, y: 10, width: 450 * Math.random(), height: 40 },
{ x: 10, y: 60, width: 320 * Math.random(), height: 40 },
{ x: 10, y: 110, width: 160 * Math.random(), height: 40 },
{ x: 10, y: 160, width: 80 * Math.random(), height: 40 },
{ x: 10, y: 210, width: 10 * Math.random(), height: 40 }
];
};
</script>
<div>
<svg viewBox="0 0 600 300">
{#each data as d, i}
<rect
x={d.x}
y={d.y}
width={widths.current[i]}
height={d.height}
fill="white" />
<text
fill="white"
x={d.x + widths.current[i] + 10}
y={d.y + d.height / 2 + 5}
text-anchor="start">
{widths.current[i].toFixed(0)}
</text>
{/each}
</svg>
<button
onclick={handleClick}
class="m-2 rounded-lg bg-yellow-400 px-2 py-2 text-lg font-semibold text-gray-900 hover:bg-yellow-500"
>Animate</button>
</div>
Key Concepts
How Tween.of
Works
- Tween.of creates a reactive tween tied to a dependency-driven function.
- For example,
() => data.map((d) => d.width)
dynamically computes the current widths of the bars based on the data array. - When data is updated, the tween smoothly interpolates from its current value (current) to the new target value (target).
current Property
- current represents the interpolated value of the tween at any point in time.
- In this example,
widths.current[i]
is used to update the width of each bar and the position of the text label dynamically.
Animating Text and Bars
- The x and y attributes of the
element position the labels relative to the bars. - The width of each bar and the x position of the labels are updated in sync, ensuring smooth animations.
Clicking the “Animate” button updates the data array, causing the bars and labels to animate smoothly to their new widths and positions.
Additional Notes
The text-anchor property aligns the text relative to its x position:
- start: Aligns the start of the text at the specified x.
- middle: Centers the text on the specified x.
- end: Aligns the end of the text at the specified x.
Join our Discord to share your charts, ask questions, or collaborate with fellow developers!