{ojs}
```//| echo: fenced
=
myBarChart
html`<bar-chart>
</bar-chart>`
```
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.
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 numbersheight
: the height of the chart in pixelswidth
: the width of the chart in pixelsbarWidth
: the width of each barbarColor
: 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
.width = 200
myBarChart ```
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
= Inputs.form(
viewof userData [
.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})
Inputs]);
= Inputs.color({ value: "#36a7e9" });
viewof userColor ```
Now, we update the props to the value of these controls:
{ojs}
```//| echo: fenced
//|
.data = [...userData];
myBarChart.barColor = userColor;
myBarChart ```
And we’re done!
height
and barWidth
?
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?
- Add
filters: ["quarto-svelte"]
to our frontmatter, plus the name of our Svelte file toquarto-svelte.use
- Created the chart with
myBarChart = html`<bar-chart></bar-chart>`
- 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 {
= [50, 45, 15, 25, 30],
data = 100,
height = 400,
width = 25,
barWidth = "black"
barColor = $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"
, whereXXXX
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!