{% props label: String, values: Vec<(String, f64)>, unit: String = "", total: String = "", variant: String = "" %}
{#- values: (name, value), largest first, at most five, "Other" last. A donut with the total (or `total`, formatted) and `unit` in the centre; `variant="pie"` has no hole. -#}
{%- let sum: f64 = values.iter().map(|v| v.1).sum() %}
{%- let share = |v: f64| if sum > 0.0 { (v / sum * 100.0).round() } else { 0.0 } %}
{%- let total = if total.is_empty() { format!("{sum}") } else { total } %}
{%- let slices = {
use std::f64::consts::{PI, TAU};
let (cx, cy, outer) = (110.0, 110.0, 96.0);
let inner = if variant == "pie" { 0.0 } else { 62.0 };
let point = |r: f64, a: f64| (cx + r * a.sin(), cy - r * a.cos());
let mut start = 0.0;
values.iter().enumerate().map(|(i, (name, v))| {
let sweep = if sum > 0.0 { (v / sum * TAU).min(TAU - 0.0001) } else { 0.0 };
let end = start + sweep;
// A 2px gap between slices: trim each edge by 1px of arc.
let gap = |r: f64| if values.len() > 1 && r > 0.0 { 1.0 / r } else { 0.0 };
let large = if sweep - 2.0 * gap(outer) > PI { 1 } else { 0 };
let (x1, y1) = point(outer, start + gap(outer));
let (x2, y2) = point(outer, end - gap(outer));
let d = if inner > 0.0 {
let (x3, y3) = point(inner, end - gap(inner));
let (x4, y4) = point(inner, start + gap(inner));
format!("M{x1:.2},{y1:.2} A{outer},{outer} 0 {large} 1 {x2:.2},{y2:.2} L{x3:.2},{y3:.2} A{inner},{inner} 0 {large} 0 {x4:.2},{y4:.2} Z")
} else {
format!("M{cx},{cy} L{x1:.2},{y1:.2} A{outer},{outer} 0 {large} 1 {x2:.2},{y2:.2} Z")
};
start = end;
(i + 1, name.clone(), d, share(*v))
}).collect::<Vec<_>>()
} %}
<div style="display:grid;grid-template-columns:180px 1fr;gap:24px;align-items:center">
<div class="ui-chart">
<svg viewBox="0 0 220 220" role="img" aria-label="{{ label }}: {% for (i, (_, name, _, pct)) in slices.iter().enumerate() %}{% if i > 0 %}, {% end %}{{ name }} {{ pct }}%{% end %}">
{%- for (class, name, d, pct) in slices.iter() %}
<g class="s{{ class }}"><path class="bar" d="{{ d }}" aria-label="{{ name }}: {{ pct }}%"/></g>
{%- end %}
{%- if variant != "pie" %}
<text x="110" y="106" text-anchor="middle" style="font-size:24px;font-weight:600;fill:var(--foreground);font-family:var(--font-sans)">{{ total }}</text>
{%- if !unit.is_empty() %}
<text x="110" y="126" text-anchor="middle" style="font-size:12px;fill:var(--muted-foreground);font-family:var(--font-sans)">{{ unit }}</text>
{%- end %}
{%- end %}
</svg>
</div>
<ul class="ui-chart-legend" style="flex-direction:column;gap:10px;font-size:13px">
{%- for (class, name, _, pct) in slices.iter() %}
<li class="s{{ class }}" style="display:flex;justify-content:space-between;width:100%"><span style="display:inline-flex;align-items:center;gap:6px"><span class="ui-chart-swatch"></span>{{ name }}</span><span style="color:var(--foreground);font-weight:500;font-variant-numeric:tabular-nums">{{ pct }}%</span></li>
{%- end %}
</ul>
</div>