{ojs}
```//| echo: fenced
= new BarChart.default({
myBarChart : document.querySelector("#mybarchart")
target})
```
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 2.
Step 1: initialise
First, we need to add Sverto and the bar chart in our document frontmatter:
---
filters:
- sverto
sverto:
use:
- BarChart.svelte
---
Then, the following OJS code initialises our bar chart.
We use the name of the Svelte file (without the file extension), then .default()
, to create the component. So for BarChart.svelte
, we use BarChart.default()
. This function is called a constructor.
The constructor needs at least one thing to, uhh, construct the chart: a target
, or place to put the chart. You can use document.querySelector()
to tell it a place that will exist in the document, and then create that place like this:
:::{#mybarchart} :::
Sverto will put the chart inside it.
We’ve also named our chart myBarChart
. This is important so that we can customise it!
Step 2: react
The real power of Sverto 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
To specify props, use the name of the constructed chart (myBarChart
, remember?), then the name of the prop.
Those props can be fixed and unchanging:
{ojs}
```//| echo: fenced
.width = 200
myBarChart ```
But with OJS, we can also tie them to things that change, like our data!
We can update any of those values up to OJS code using myBarChart.propName
.
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 [
.text({type: "number", value: 25, width: 20}),
Inputs.text({type: "number", value: 35, width: 20}),
Inputs.text({type: "number", value: 65, width: 20}),
Inputs.text({type: "number", value: 5, width: 20}),
Inputs.text({type: "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!
Weren’t there other props, like 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.
You can also include default props
as an option when you create the chart, but I generally find it simpler and easier to keep the props away from the constructor.
Summary
How did we get this chart going again?
- Add
filters: ["sverto"]
to our frontmatter, plus the name of our Svelte file tosverto.use
- Created the chart with
myBarChart = new BarChart.default()
, giving it atarget
to place the chart in - We created the
target
with a Pandoc div::::{#mybarchart}
- We customised the bar chart by assigning to
myBarChart.propName
Challenges
Here’s the code in the Svelte file:
<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";
/* these are our props and their default values (starting with `export let`).
in particular, note that data is an array of numbers
https://svelte.dev/tutorial/declaring-props */
export let data = [50, 45, 15, 25, 30];
export let height = 100;
export let width = 400;
export let barWidth = 25;
export let barColor = "black"
/* 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 */
: normalisedData = data.map(d => ({
$y: d,
h: d / Math.max(...data) * height
;
})): console.log(normalisedData);
$
</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. Sverto 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!