Skip to main content
Td

D3 Svelte Bar Chart

D3 bar chart example, written in Svelte.


by Tony Dang on

This post is part of a series on converting the official D3 examples to Svelte.

D3 bar chart written in Svelte

E T A O I N S H R D L C U M W F G Y P B V K J X Q Zletter 0 1 2 3 4 5 6 7 8 9 10 11 12↑ frequency (%)

Original Plot | Svelte REPL | Github

Code

<script>
  import * as d3 from "d3";

  // Receive plot data as prop.
  export let data;

  // The chart dimensions and margins as optional props.
  export let width = 928;
  export let height = 500;
  export let marginTop = 30;
  export let marginRight = 0;
  export let marginBottom = 50;
  export let marginLeft = 40;

  // Create the x (horizontal position) scale.
  const xScale = d3
    .scaleBand()
    .domain(
      // Sort the data in descending frequency
      d3.groupSort(
        data,
        ([d]) => -d.frequency,
        (d) => d.letter
      )
    )
    .range([marginLeft, width - marginRight])
    .padding(0.1);

  // Create the y (vertical position) scale.
  const yScale = d3
    .scaleLinear()
    .domain([0, d3.max(data, (d) => d.frequency)])
    .range([height - marginBottom, marginTop]);
</script>

<svg
  {width}
  {height}
  viewBox="0 0 {width} {height}"
  style:max-width="100%"
  style:height="auto"
>
  <!-- Add a rect for each bar. -->
  <g fill="steelblue">
    {#each data as d}
      <rect
        x={xScale(d.letter)}
        y={yScale(d.frequency)}
        height={yScale(0) - yScale(d.frequency)}
        width={xScale.bandwidth()}
      />
    {/each}
  </g>

  <!-- X-Axis -->
  <g transform="translate(0,{height - marginBottom})">
    <line stroke="currentColor" x1={marginLeft - 6} x2={width} />

    {#each data as d}
      <!-- X-Axis Ticks -->
      <line
        stroke="currentColor"
        x1={xScale(d.letter) + xScale.bandwidth() / 2}
        x2={xScale(d.letter) + xScale.bandwidth() / 2}
        y1={0}
        y2={6}
      />

      <!-- X-Axis Tick Labels -->
      <text
        fill="currentColor"
        text-anchor="middle"
        x={xScale(d.letter) + xScale.bandwidth() / 2}
        y={22}
      >
        {d.letter}
      </text>
    {/each}

    <!-- X-Axis Label -->
    <text fill="currentColor" x={width / 2} y={50}>letter</text>
  </g>

  <!-- Y-Axis -->
  <g transform="translate({marginLeft},0)">
    {#each yScale.ticks() as tick}
      <!-- 
        Y-Axis Ticks. 
        Note: First tick is skipped since the x-axis already acts as a tick. 
      -->
      {#if tick !== 0}
        <line
          stroke="currentColor"
          x1={0}
          x2={-6}
          y1={yScale(tick)}
          y2={yScale(tick)}
        />
      {/if}

      <!-- Y-Axis Tick Labels -->
      <text
        fill="currentColor"
        text-anchor="end"
        dominant-baseline="middle"
        x={-9}
        y={yScale(tick)}
      >
        {Math.trunc(tick * 100)}
      </text>
    {/each}

    <!-- Y-Axis Label -->
    <text fill="currentColor" text-anchor="start" x={-marginLeft} y={15}>
      ↑ frequency (%)
    </text>
  </g>
</svg>

Things I learned

  • The official D3 Svelte example uses D3’s built-in axis generators combined with Svelte’s reactive statements for rendering axes. While this approach is concise, I found it less intuitive and it doesn’t allow for server-side rendering (SSR).

    In my implementation, I chose not to use D3’s axis generators in order to have faster page load times and avoid unnecessary hydration. However, I can see cases where using D3’s axis generators would be a better choice.