Examples: basic barchart

Author

James Goldie

Published

June 15, 2025

Let’s start with a simple barchart.

This isn’t going to be one of those barcharts you’ve seen on TV, what with tick marks and hover effects and such. This one’s just some bars, some labels and a baseline. It will, however, come with some useful features to help us reuse it.

If you’d like to see how the bar chart works internally, the code for it is shown at the bottom of this page.

But for the purposes of this example, all you need to know about is the chart’s props: updateable options to customise the chart. We’ll cover those in step 3.

Step 1: import

First, we need to add quarto-svelte and the bar chart’s .svelte file to our document frontmatter:

---
filters:
  - quarto-svelte
quarto-svelte:
  use:
    - BarChart.svelte
---

Step 2: place

To place the component on the page, we use its custom element name (found at the top of the Svelte file) and create it like an HTML tag.

```{ojs}
//| echo: fenced
myBarChart =
  html`
    <bar-chart>
    </bar-chart>`
```

We made this barchart with Svelte!

If you’re reading this on a desktop, I’ve placed the barchart in the page margin so we can keep an eye on it as it changes!

We’ve also assigned the barchart to a variable in OJS, myBarChart. You don’t have to this—unless you want to change your chart over time!

Step 3: react

The real power of quarto-svelte is that we can customise aspects of our charts and have them update in response to other things.

In Svelte, these customisable options are called props.

This Svelte component accepts a few props:

  • data: a simple array of (up to) 5 numbers
  • height: the height of the chart in pixels
  • width: the width of the chart in pixels
  • barWidth: the width of each bar
  • barColor: the colour of the bars and labels (note the US spelling here) in res

We can update any of those props in OJS code using myBarChart.propName. We can make them values that are fixed and unchanging:

```{ojs}
//| echo: fenced
myBarChart.width = 200
```

But we can also tie them to things that change, like our data!

Let’s make some controls so that users can animate the chart’s data and colour themselves:

Code
```{ojs}
//| echo: fenced
//| code-fold: true

viewof userData = Inputs.form(
  [
    Inputs.number({ value: 25, width: 20}),
    Inputs.number({ value: 35, width: 20}),
    Inputs.number({ value: 65, width: 20}),
    Inputs.number({ value: 5, width: 20}),
    Inputs.number({ value: 50, width: 20})
  ]);

viewof userColor = Inputs.color({ value: "#36a7e9" });
```

Now, we update the props to the value of these controls:

```{ojs}
//| echo: fenced
//| 
myBarChart.data = [...userData];
myBarChart.barColor =  userColor;
```

And we’re done!

Yup! The props in this Svelte component (and many that you’ll use) have default values that are used if you don’t provide one. Keep in mind that your Svelte component will start with those defaults on page load and then switch to the values you provide in OJS.

You can also include default props when you place the component — for example, <bar-chart barColour="pink"></bar-chart>.

By default, default prop values are interpreted as strings. If you want to pass other types of data as default props, you’ll need to specify that at the top of your Svelte file.

Summary

How did we get this chart going again?

  1. Add filters: ["quarto-svelte"] to our frontmatter, plus the name of our Svelte file to quarto-svelte.use
  2. Created the chart with myBarChart = html`<bar-chart></bar-chart>`
  3. We updated the bar chart by assigning to myBarChart.propName

Challenges

Here’s the code in the Svelte file:

<!-- this line is needed to add your svelte component to quarto! -->
<svelte:options customElement={{
  tag: "bar-chart",
  props: {
    data: { type: "Array" }
  }
}} />

<script>

  /* let's borrow svelte's fly transitions for the circles that need to be
     created or destroyed
     https://svelte.dev/tutorial/transition */
  import { fly } from "svelte/transition";

  let {
    data = [50, 45, 15, 25, 30],
    height = 100,
    width = 400,
    barWidth = 25,
    barColor = "black"
  } = $props();

  /* unlike ojs, svelte code isn't "naturally" reactive (except down below in
     the rendered html or svg), but you can prefix a statement with `$:` to
     make it reactive (so it runs every time `data` changes).
     here we're going to normalise our data to 100, and we'll use the normalised
     data in our barchart instead
     https://svelte.dev/tutorial/reactive-statements */
  let normalisedData = $derived(data.map(d => ({
    y: d,
    h: d / Math.max(...data) * height
  })));

</script>

<!-- these css styles just apply to our component -->
<style>
  text {
    font-size: smaller;
  }
</style>

<!-- this bar chart is an svg that just includes bars and labels, with a
     single, unlabelled baseline.
     it does, however, use the `height`, `width` and `barWidth`
     props to scale the element sizes, so this can be dynamically
     resized easily. it also uses the `normalisedData`, which is recalculated
     every time the `data` prop changes because it starts with `$:` above -->

<svg width={width} height={height}>
  <!-- for each data element, draw a rectangle and a label -->
  {#each normalisedData as d, i (i)}
  <rect
    in:fly="{{x: 10}}" out:fly="{{x: 10}}"
    style={"transition: all 1s ease-in-out"}
    x="{width / 6 * (i + 0.5) - (barWidth / 2)}px"
    y="{height - d.h}px"
    width="{barWidth}px"
    height="{d.h}px"
    fill="{barColor}"
    >
  </rect>
    <!-- place label either above or below bar,
         depending on its height -->
    <text
      in:fly="{{x: 10}}" out:fly="{{x: 10}}"
      style={"transition: all 1s ease-in-out"}
      text-anchor="middle"
      x="{width / 6 * (i + 0.5)}px"
      y="{d.h > 35 ?
        height - d.h + 16 :
        height - d.h - 6}px"
      fill="{d.h > 35 ? "white" : barColor}"
    >{d.y}</text>
  {/each}
  <!-- and a single x axis baseline -->
  <line x1="0" x2="{width * 5 / 6}" y1="{height}" y2="{height}" stroke="black"></line>
</svg>

If you’d like to start practising your Svelte, start with the official tutorial. quarto-svelte is designed to make using Svelte components in Quarto as easy as working in the tutorial.

This Svelte component’s pretty basic, though. What else is it missing?

Resizing

The height and the width of the chart are configurable using props, and the bars resize in response to them, but the CSS transitions that grow and shrink them are slow to catch up.

Ideally we’d turn those transitions off when the chart as a whole is resizing!

Other barchart elements

We have no axes other than the baseline. That’s fine for a lot of uses, but we might want to add those elements for other uses.

We could add those elements manually, but the d3-axis package has some helpers for creating axes quickly!

Colour scales

The bars are all the same colour. We could write a function that converts each bar’s data value to a colour, and use it for the fill attribute of the <rect>, but the d3-scale-chromatic also has some helpers to do this quickly!

d3 is included with OJS, but if you want to use d3-scale-chromatic (or any other part of d3) in your Svelte components, you’ll have to add it yourself by:

  • running npm install d3-scale-chromatic in the terminal, then
  • adding import XXXX from "d3-scale-chromatic", where XXXX is the name of the thing you want to import (or *).

A more complex example

If you’d like to see an example that addresses some of these shortcomings, check out the time series chart example, which automatically resizes and adds axes that transition!

See the time series chart →