Bar chart race visualization for Svelte
A bar chart race is a horizontal bar chart that animates bars over an interval (usually time). The x-axis represents the bar chart values. The bars are sorted based on their value from highest to lowest as the interval changes.
This component library provides:
- BarChartRace.svelte: Parent component that accepts data, options and manages internal state
- Chart.svelte: Child component that displays the chart with the ability to customize the chart display and animation
- Slider.svelte: Child component that shows the range input to control the interval (current value)
# Yarn
yarn add -D svelte-bar-chart-race
# NPM
npm i -D svelte-bar-chart-race
# pnpm
pnpm i -D svelte-bar-chart-race
The examples use data sampled from StackOverflow's annual developer survey for "Most Loved Languages."
Sample data
// demo/data.ts
export const data = [
{
title: "Rust",
color: "#dea584",
values: [
{ year: 2021, value: 86.98 },
{ year: 2020, value: 86.1 },
{ year: 2019, value: 83.5 },
{ year: 2018, value: 78.9 },
{ year: 2017, value: 73.1 },
],
},
{
title: "TypeScript",
color: "#2b7489",
values: [
{ year: 2021, value: 72.73 },
{ year: 2020, value: 67.1 },
{ year: 2019, value: 73.1 },
{ year: 2018, value: 67.0 },
{ year: 2017, value: 64.1 },
],
},
{
title: "Python",
color: "#3572A5",
values: [
{ year: 2021, value: 67.83 },
{ year: 2020, value: 66.7 },
{ year: 2019, value: 73.1 },
{ year: 2018, value: 68.0 },
{ year: 2017, value: 62.7 },
],
},
{
title: "Go",
color: "#00ADD8",
values: [
{ year: 2021, value: 62.74 },
{ year: 2020, value: 62.3 },
{ year: 2019, value: 67.9 },
{ year: 2018, value: 65.6 },
{ year: 2017, value: 63.3 },
],
},
{
title: "Swift",
color: "#F05138",
values: [
{ year: 2021, value: 63.56 },
{ year: 2020, value: 59.5 },
{ year: 2019, value: 69.2 },
{ year: 2018, value: 65.1 },
{ year: 2017, value: 63.7 },
],
},
];
In the simplest example, all you need is to pass data
to BarChartRace
.
Then, pass Chart
and Slider
through its default slot.
<script>
import { BarChartRace, Slider, Chart } from "svelte-bar-chart-race";
import { data } from "./data";
</script>
<BarChartRace {data}>
<Chart />
<Slider />
</BarChartRace>
Source: demo/Simple.svelte
The currentValue
prop supports two-way binding.
In this example, the initial value is 2018
. Any change to the currentValue
via Slider
will be reflected to the bound value.
currentValue
can also be programmatically updated.
Finally, setting currentValue
to null
resets the value to the lowest value (in this case, 2017
).
<script>
import { BarChartRace, Slider, Chart } from "svelte-bar-chart-race";
import { data } from "./data";
let currentValue = 2018;
</script>
<BarChartRace {data} bind:currentValue>
<Slider />
<Chart />
</BarChartRace>
<button on:click={() => (currentValue = 2020)}> Set value to 2020 </button>
<button on:click={() => (currentValue = null)}> Set value to null </button>
Current value:
<strong>{currentValue}</strong>
Source: demo/TwoWayBinding.svelte
The kitchen sink showcases all available features.
An alternative to accessing the currentValue
is to destructure it from the BarChartRace
slot using the let:
directive.
Other slot props include:
- setMax (
() => void
): sets the current value to the maximum value defined indata
- setMin (
() => void
): sets the current value to the minimum value defined indata
- setValue (
(value: number) => void
): sets the current value to the provided value - play (
() => void
): function to animate the bar chart - pause (
() => void
): function to pause the animation - isPlaying (
boolean
):true
if the chart is currently animating
<script>
import { BarChartRace, Slider, Chart } from "svelte-bar-chart-race";
import { quadOut } from "svelte/easing";
import { data } from "./data";
</script>
<BarChartRace
{data}
interval={1_500}
options={{ key: "year" }}
let:currentValue={value}
let:setMax
let:setMin
let:setValue
let:play
let:pause
let:isPlaying
>
<Slider labelText="Year: {value}" />
<Chart
--bar-height="10px"
--bar-margin-bottom="12px"
animate={{
easing: quadOut,
duration: 400,
delay: 50,
}}
let:datum
>
<strong>{datum.title}</strong>
{datum.value.toFixed(1)}%
</Chart>
<button on:click={isPlaying ? pause : play}>
{isPlaying ? "Pause" : "Play"}
</button>
<button on:click={setMax}>Set max</button>
<button on:click={setMin}>Set min</button>
<button on:click={() => setValue(2019)}> Set value to 2019 </button>
</BarChartRace>
Source: demo/KitchenSink.svelte
Prop | Type | Default value |
---|---|---|
data | BarChartRaceDatum[] |
[] |
options | BarChartRaceOptions |
{ key: "", unit: "%", max: 100 } |
interval | number |
2_000 |
If no key
is explicitly provided to options
, BarChartRace
will infer the key by finding the first key that is not "value"
in data[0].values
.
interface BarChartRaceDatum {
/**
* Title must be unique because it's
* used as a key in `Chart.svelte`.
*/
title: string;
/**
* Specify the chart bar color.
* @default "#333"
* @example "red"
* @example "#f00"
*/
color?: string;
/**
* Tabular data used as values for each bar.
* @example
* [
{ key: 2021, value: 63.56 },
{ key: 2020, value: 59.5 },
// ...
]
*/
values: BarChartRaceValue[];
}
interface BarChartRaceOptions {
/**
* Specify the key used to normalize values in the `data.values` array.
* If no key is specified, it will be inferred.
* @example
* For a value object { year: 2021, value: 62.74 },
* the inferred key would be "year".
*/
key?: any;
/**
* Specify the unit.
* @default "%"
*/
unit?: string;
/**
* Specify the maximum value the
* chart value should be divided by.
* @default 100
*/
max?: number;
}
Use the default slot to pass through the Chart
and Slider
components or any other element.
<BarChartRace
{data}
let:currentValue
let:isPlaying
let:setMax
let:setMin
let:setValue
let:play
let:pause
>
<!-- Default slot -->
</BarChartRace>
Prop | Type | Default value |
---|---|---|
animate | FlipParams |
{ delay: 0, duration: d => Math.sqrt(d) * 120, easing: cubicOut } |
The Chart
component uses the flip function to animate the bar chart.
Svelte provides numerous easing functions you can use to customize the animation.
<script>
import { quadOut } from "svelte/easing";
</script>
<Chart
animate={{
easing: quadOut,
duration: 400,
delay: 50,
}}
/>
Customize the chart styles using --style-props.
Style prop | Description | Default value |
---|---|---|
--bar-height | Chart bar height | 6px |
--bar-margin-top | Chart bar margin top | 4px |
--bar-margin-bottom | Chart bar margin bottom | 10px |
--chart-padding | ol element padding |
0 |
<Chart
--bar-height="10px"
--margin-top="20px"
--margin-bottom="20px"
--chart-padding="40px"
/>
Use the default slot to customize the bar chart display value.
Access arbitrary values from data
values.
<Chart let:datum>
<strong>{datum.title}</strong>
{datum.value}%
{datum.additionalValue ?? ""}
</Chart>
Name | Type | Default value |
---|---|---|
id | string |
"slider-" + Math.random().toString(36) |
labelText | any |
"" |
The current value from BarChartRace
will be used as the labelText
.
Customize labelText
to override this behavior.
<Slider labelText="Year: {value}" />