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.

55555

<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!