Bar race chart
A ranking played through time: the top entries as bars that grow and glide past each other as the order changes, with play, pause and a scrubber.
Monthly active users by city
The top eight cities each quarter, in thousands. Press play, or drag to scrub.
Q1 2022: 1. London 182k, 2. Paris 140k, 3. Berlin 121k
| Q1 2022 | Q2 2022 | Q3 2022 | Q4 2022 | Q1 2023 | Q2 2023 | Q3 2023 | Q4 2023 | Q1 2024 | Q2 2024 | Q3 2024 | Q4 2024 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| London | 182k | 189k | 197k | 205k | 213k | 221k | 230k | 239k | 249k | 259k | 269k | 280k |
| Berlin | 121k | 132k | 144k | 157k | 171k | 186k | 203k | 221k | 241k | 263k | 286k | 312k |
| Paris | 140k | 148k | 157k | 167k | 177k | 187k | 199k | 211k | 223k | 237k | 251k | 266k |
| Madrid | 64k | 72k | 80k | 90k | 101k | 113k | 126k | 141k | 158k | 177k | 199k | 223k |
| Amsterdam | 88k | 94k | 101k | 108k | 115k | 123k | 132k | 141k | 151k | 162k | 173k | 185k |
| Lisbon | 22k | 27k | 34k | 42k | 52k | 64k | 80k | 99k | 123k | 152k | 189k | 234k |
| Dublin | 51k | 55k | 59k | 64k | 69k | 75k | 81k | 87k | 94k | 102k | 110k | 119k |
| Stockholm | 47k | 52k | 57k | 63k | 69k | 76k | 83k | 92k | 101k | 111k | 122k | 134k |
| Warsaw | 18k | 23k | 29k | 36k | 45k | 57k | 72k | 91k | 114k | 144k | 182k | 229k |
| Milan | 70k | 74k | 77k | 81k | 85k | 89k | 94k | 98k | 103k | 109k | 114k | 120k |
| Prague | 29k | 33k | 38k | 44k | 51k | 58k | 67k | 77k | 89k | 102k | 117k | 135k |
| Vienna | 39k | 43k | 46k | 51k | 55k | 60k | 65k | 71k | 78k | 85k | 92k | 101k |
Usage
use hydroxy_leptos::bar_race_chart::*;
use hydroxy_leptos::chart_frame::ChartFrame;
use leptos::prelude::*;
#[component]
pub fn Demo() -> impl IntoView {
let quarters: Vec<String> = (2022..=2024).flat_map(|y| (1..=4).map(move |q| format!("Q{q} {y}"))).collect();
// Monthly active users in thousands: each city starts somewhere and grows at its own pace.
let grow = |start: f64, rate: f64| (0..12).map(|q| (start * rate.powi(q)).round()).collect::<Vec<f64>>();
let cities = vec![
Series::new("London", grow(182.0, 1.04)),
Series::new("Berlin", grow(121.0, 1.09)),
Series::new("Paris", grow(140.0, 1.06)),
Series::new("Madrid", grow(64.0, 1.12)),
Series::new("Amsterdam", grow(88.0, 1.07)),
Series::new("Lisbon", grow(22.0, 1.24)),
Series::new("Dublin", grow(51.0, 1.08)),
Series::new("Stockholm", grow(47.0, 1.10)),
Series::new("Warsaw", grow(18.0, 1.26)),
Series::new("Milan", grow(70.0, 1.05)),
Series::new("Prague", grow(29.0, 1.15)),
Series::new("Vienna", grow(39.0, 1.09)),
];
view! {
<ChartFrame title="Monthly active users by city" description="The top eight cities each quarter, in thousands. Press play, or drag to scrub.">
<BarRaceChart frames=quarters series=cities label="Monthly active users by city, 2022 to 2024" top=8 format=|v: f64| format!("{v:.0}k") />
</ChartFrame>
}
}use dioxus::prelude::*;
use hydroxy_dioxus::bar_race_chart::{BarRaceChart, Series};
use hydroxy_dioxus::chart_frame::ChartFrame;
#[component]
pub fn BarRaceChartDemo() -> Element {
let quarters: Vec<String> = (2022..=2024).flat_map(|y| (1..=4).map(move |q| format!("Q{q} {y}"))).collect();
// Monthly active users in thousands: each city starts somewhere and grows at its own pace.
let grow = |start: f64, rate: f64| (0..12).map(|q| (start * rate.powi(q)).round()).collect::<Vec<f64>>();
let cities = vec![
Series::new("London", grow(182.0, 1.04)),
Series::new("Berlin", grow(121.0, 1.09)),
Series::new("Paris", grow(140.0, 1.06)),
Series::new("Madrid", grow(64.0, 1.12)),
Series::new("Amsterdam", grow(88.0, 1.07)),
Series::new("Lisbon", grow(22.0, 1.24)),
Series::new("Dublin", grow(51.0, 1.08)),
Series::new("Stockholm", grow(47.0, 1.10)),
Series::new("Warsaw", grow(18.0, 1.26)),
Series::new("Milan", grow(70.0, 1.05)),
Series::new("Prague", grow(29.0, 1.15)),
Series::new("Vienna", grow(39.0, 1.09)),
];
rsx! {
ChartFrame { title: "Monthly active users by city", description: "The top eight cities each quarter, in thousands. Press play, or drag to scrub.",
BarRaceChart {
frames: quarters,
series: cities,
label: "Monthly active users by city, 2022 to 2024",
top: 8,
format: move |v: f64| format!("{v:.0}k"),
}
}
}
}
Anatomy
| Component | data-part | Renders |
|---|---|---|
BarRaceChart | root | figure with a screen-reader table of every value in every frame; data-playing while it runs |
| row | One entry: name, bar and value, placed by rank; data-out when outside the top |
| bar | Bar length, as a share of the leader's value |
| value | The value, counting as the race plays |
| frame | The current frame, large in the corner |
| play | Play or pause button |
| scrubber | Range input over the frames |
Keyboard
| Key | Does |
|---|---|
Space / Enter | Play or pause (on the button) |
ArrowLeft / ArrowRight | Previous or next frame (on the scrubber) |
Home / End | First or last frame (on the scrubber) |
Props
| Prop | Type | Notes |
|---|---|---|
frames | Vec<String> | Required. One label per frame |
series | Vec<Series> | Required. Series::new(name, one value per frame) |
label | String | Required. What the chart shows; the accessible name |
top | usize | Bars shown at once. Default 10 |
frame_ms | u32 | How long each frame lasts while playing. Default 1200 |
autoplay | bool | Start once mounted; never under reduced motion |
row_height | u16 | Row height in px. Default 30 |
format | Fn(f64) -> String | Formats values, ticks and table |
Events
| Prop | Type | Notes |
|---|---|---|
on_hover | Callback<Option<Datum>> | The mark under the pointer or keyboard focus changes, or is left. Fires only on a change |
on_select | Callback<Datum> | Click, tap or Enter on a mark |
frame | Signal<usize> | Controlled frame index. Uncontrolled when left off |
on_frame_change | Callback<usize> | Called when the shown frame changes |