// Heatmap: a matrix of values rendered as colored cells.
///|
/// Render a heatmap as a standalone SVG document string. `rows` and `cols`
/// label the grid and `values[r][c]` is the cell value; cell colors
/// interpolate between `low_color` and `high_color` across the value range,
/// and each cell prints its value with a contrast-aware text color. `title`,
/// `width`, `height`, the two scale colors and `theme` are optional.
pub fn heatmap(
rows : Array[String],
cols : Array[String],
values : Array[Array[Double]],
title? : String = "",
width? : Double = 480.0,
height? : Double = 340.0,
low_color? : String = "#e6f1fb",
high_color? : String = "#185fa5",
theme? : Theme = Theme::light(),
) -> String {
let left = 84.0
let right = 16.0
let top = if title == "" { 16.0 } else { 40.0 }
let bottom = 32.0
let plot_w = width - left - right
let plot_h = height - top - bottom
let n_r = rows.length()
let n_c = cols.length()
// Value range across the whole matrix.
let mut lo = 0.0
let mut hi = 0.0
let mut first = true
for row in values {
for v in row {
if first {
lo = v
hi = v
first = false
}
if v < lo {
lo = v
}
if v > hi {
hi = v
}
}
}
let span = if hi - lo <= 0.0 { 1.0 } else { hi - lo }
let body = StringBuilder::new()
body.write_string(background_rect(theme, width, height))
if n_r > 0 && n_c > 0 {
let cw = plot_w / n_c.to_double()
let ch = plot_h / n_r.to_double()
for r in 0.. 0.55 { "#ffffff" } else { "#333333" }
body.write_string(
label(x + cw / 2.0, y + ch / 2.0 + 4.0, num(v), fill=text_color),
)
}
// Row label on the left.
body.write_string(
label(
left - 8.0,
top + ch * r.to_double() + ch / 2.0 + 4.0,
rows[r],
anchor="end",
fill=theme.text,
),
)
}
// Column labels along the bottom.
for c in 0..