// Funnel chart: successive stages drawn as centered trapezoids.
///|
/// Render a funnel chart as a standalone SVG document string. Each
/// `(label, value)` becomes a horizontal band whose width is proportional to
/// its value; consecutive bands are joined into trapezoids so the funnel
/// tapers from the largest stage down. A conversion percentage relative to the
/// first stage is printed on each band. `title`, `width`, `height` and `theme`
/// are optional.
pub fn funnel_chart(
data : Array[(String, Double)],
title? : String = "",
width? : Double = 460.0,
height? : Double = 340.0,
theme? : Theme = Theme::light(),
) -> String {
let left = 110.0
let right = 16.0
let top = if title == "" { 16.0 } else { 40.0 }
let bottom = 16.0
let plot_w = width - left - right
let plot_h = height - top - bottom
let cx = left + plot_w / 2.0
let n = data.length()
let body = StringBuilder::new()
body.write_string(background_rect(theme, width, height))
if n > 0 {
// The first stage is the widest reference.
let mut vmax = 0.0
for d in data {
if d.1 > vmax {
vmax = d.1
}
}
if vmax <= 0.0 {
vmax = 1.0
}
let first = if data[0].1 > 0.0 { data[0].1 } else { vmax }
let band_h = plot_h / n.to_double()
let half = fn(v : Double) -> Double { v / vmax * plot_w / 2.0 }
for i in 0.. 0.0 { (value / first * 100.0).to_int() } else { 0 }
body.write_string(
label(
cx,
y + band_h / 2.0 + 4.0,
num(value) + " (" + pct.to_string() + "%)",
fill="#ffffff",
),
)
}
}
if title != "" {
body.write_string(
label(width / 2.0, 24.0, title, size=16, weight="bold", fill=theme.title),
)
}
document(width, height, body.to_string())
}