Bland-Altman chart
Check whether two ways of measuring the same thing agree. Each pair is plotted as its mean against its difference, with the average bias and the 95% limits of agreement.
Glucose meter against the lab
Each patient's two readings: their mean along the bottom, meter minus lab up the side.
| Pair | Meter | Lab | Mean | Difference |
|---|---|---|---|---|
| 1 | 4.1 | 4.2 | 4.1 | -0.1 |
| 2 | 8.0 | 7.9 | 7.9 | 0.1 |
| 3 | 11.9 | 11.6 | 11.7 | 0.3 |
| 4 | 5.8 | 5.6 | 5.7 | 0.2 |
| 5 | 9.7 | 9.3 | 9.5 | 0.4 |
| 6 | 13.6 | 13.0 | 13.3 | 0.6 |
| 7 | 7.6 | 7.0 | 7.3 | 0.6 |
| 8 | 11.5 | 10.7 | 11.1 | 0.8 |
| 9 | 5.4 | 4.7 | 5.0 | 0.7 |
| 10 | 8.5 | 8.4 | 8.4 | 0.1 |
| 11 | 12.4 | 12.1 | 12.2 | 0.3 |
| 12 | 6.3 | 6.1 | 6.2 | 0.2 |
| 13 | 10.2 | 9.8 | 10.0 | 0.4 |
| 14 | 14.1 | 13.5 | 13.8 | 0.6 |
| 15 | 8.0 | 7.5 | 7.8 | 0.5 |
| 16 | 11.9 | 11.2 | 11.6 | 0.7 |
| 17 | 5.9 | 5.2 | 5.5 | 0.7 |
| 18 | 8.9 | 8.9 | 8.9 | 0.0 |
| 19 | 12.8 | 12.6 | 12.7 | 0.2 |
| 20 | 6.7 | 6.6 | 6.7 | 0.1 |
| 21 | 10.7 | 10.3 | 10.5 | 0.4 |
| 22 | 4.6 | 4.3 | 4.4 | 0.3 |
| 23 | 8.5 | 8.0 | 8.2 | 0.5 |
| 24 | 12.4 | 11.7 | 12.1 | 0.7 |
| 25 | 6.3 | 5.7 | 6.0 | 0.6 |
| 26 | 10.2 | 9.4 | 9.8 | 0.8 |
| 27 | 13.3 | 13.1 | 13.2 | 0.2 |
| 28 | 7.2 | 7.1 | 7.2 | 0.1 |
| 29 | 11.1 | 10.8 | 11.0 | 0.3 |
| 30 | 5.0 | 4.8 | 4.9 | 0.2 |
| 31 | 9.0 | 8.5 | 8.7 | 0.5 |
| 32 | 12.9 | 12.2 | 12.5 | 0.7 |
| 33 | 6.8 | 6.2 | 6.5 | 0.6 |
| 34 | 10.7 | 9.9 | 10.3 | 0.8 |
| 35 | 13.8 | 13.6 | 13.7 | 0.2 |
| 36 | 7.7 | 7.6 | 7.6 | 0.1 |
| 37 | 11.6 | 11.3 | 11.4 | 0.3 |
| 38 | 5.5 | 5.3 | 5.4 | 0.2 |
| 39 | 9.4 | 9.0 | 9.2 | 0.4 |
| 40 | 13.3 | 12.7 | 13.0 | 0.6 |
Usage
use hydroxy_leptos::bland_altman_chart::*;
use hydroxy_leptos::chart_frame::ChartFrame;
use leptos::prelude::*;
pub fn demo() -> AnyView {
// Blood glucose in mmol/L for 40 patients: the clinic's lab analyser and a finger-prick
// meter. The meter reads a little high, and more so at high readings.
let lab: Vec<f64> = (0..40).map(|i| 4.2 + f64::from((i * 37) % 97) / 10.0).collect();
let meter: Vec<f64> =
lab.iter().zip(0..).map(|(v, i)| v * 1.03 + 0.15 + f64::from((i * 53) % 17) / 20.0 - 0.4).collect();
view! {
<ChartFrame title="Glucose meter against the lab" description="Each patient's two readings: their mean along the bottom, meter minus lab up the side.">
<BlandAltmanChart
a=meter
b=lab
a_label="Meter"
b_label="Lab"
label="Bland-Altman plot of meter and lab glucose"
show_ci=true
format=|v: f64| format!("{v:.1}")
/>
</ChartFrame>
}
.into_any()
}use dioxus::prelude::*;
use hydroxy_dioxus::bland_altman_chart::BlandAltmanChart;
use hydroxy_dioxus::chart_frame::ChartFrame;
#[component]
pub fn BlandAltmanChartDemo() -> Element {
// Blood glucose in mmol/L for 40 patients: the clinic's lab analyser and a finger-prick
// meter. The meter reads a little high, and more so at high readings.
let lab: Vec<f64> = (0..40).map(|i| 4.2 + f64::from((i * 37) % 97) / 10.0).collect();
let meter: Vec<f64> =
lab.iter().zip(0..).map(|(v, i)| v * 1.03 + 0.15 + f64::from((i * 53) % 17) / 20.0 - 0.4).collect();
rsx! {
ChartFrame { title: "Glucose meter against the lab", description: "Each patient's two readings: their mean along the bottom, meter minus lab up the side.",
BlandAltmanChart {
a: meter,
b: lab,
a_label: "Meter",
b_label: "Lab",
label: "Bland-Altman plot of meter and lab glucose",
show_ci: true,
format: move |v: f64| format!("{v:.1}"),
}
}
}
}
Anatomy
| Component | data-part | Renders |
|---|---|---|
BlandAltmanChart | root | figure with a screen-reader table of each pair, its mean and difference |
| dot | One per pair: mean across, difference (a − b) up |
| limit | The mean difference (data-kind=bias, solid) and the limits of agreement (lower, upper; dashed) |
| limit-band | 95% confidence interval of each line (show_ci) |
| limit-label | The value of each line |
| tooltip | Difference, mean and both readings |
Keyboard
| Key | Does |
|---|---|
ArrowLeft / ArrowRight | Previous or next pair |
Home / End | First or last pair |
Escape | Hide the tooltip |
Props
| Prop | Type | Notes |
|---|---|---|
a | Vec<f64> | Required. Readings from the first method |
b | Vec<f64> | Required. Readings from the second method, paired by position |
label | String | Required. What the chart shows; the accessible name |
a_label / b_label | String | Names of the two methods. Default "Method A" / "Method B" |
show_ci | bool | Shade the 95% confidence interval of the bias and each limit |
height | u16 | Plot height in px. Default 280 |
format | Fn(f64) -> String | Formats readings and differences |
annotations | Vec<Annotation> | x is a mean, y a difference |
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 |