// Treemap with the squarified layout algorithm (Bruls, Huizing & van Wijk):
// rows of cells are laid greedily along the shorter side of the remaining
// rectangle, keeping cell aspect ratios as close to 1 as possible.
///|
/// Worst (most elongated) aspect ratio a row with total area `row_sum`,
/// largest cell `row_max` and smallest cell `row_min` would have when laid
/// along a side of length `side`.
fn worst_aspect(
row_sum : Double,
row_max : Double,
row_min : Double,
side : Double,
) -> Double {
let s2 = side * side
let sum2 = row_sum * row_sum
let a = s2 * row_max / sum2
let b = sum2 / (s2 * row_min)
if a > b {
a
} else {
b
}
}
///|
/// Squarified treemap layout: split the rectangle at `(x0, y0)` sized
/// `w0 × h0` into one cell per entry of `areas` (which must sum to
/// `w0 * h0`), returned in input order as `(x, y, width, height)`.
fn squarify(
areas : Array[Double],
x0 : Double,
y0 : Double,
w0 : Double,
h0 : Double,
) -> Array[(Double, Double, Double, Double)] {
let out : Array[(Double, Double, Double, Double)] = []
let mut x = x0
let mut y = y0
let mut w = w0
let mut h = h0
let n = areas.length()
let mut i = 0
while i < n {
// Grow the row while doing so improves the worst aspect ratio.
let side = if w < h { w } else { h }
let mut row_sum = areas[i]
let mut row_max = areas[i]
let mut row_min = areas[i]
let mut j = i + 1
let mut worst = worst_aspect(row_sum, row_max, row_min, side)
while j < n {
let a = areas[j]
let ns = row_sum + a
let nmax = if a > row_max { a } else { row_max }
let nmin = if a < row_min { a } else { row_min }
let nworst = worst_aspect(ns, nmax, nmin, side)
if nworst <= worst {
row_sum = ns
row_max = nmax
row_min = nmin
worst = nworst
j += 1
} else {
break
}
}
// Fix the row along the shorter side and shrink the remaining rectangle.
let t = row_sum / side
if w < h {
let mut cx = x
for k in i.. String {
let pad = 12.0
let top = if title == "" { pad } else { 40.0 }
let plot_w = width - pad * 2.0
let plot_h = height - top - pad
let body = StringBuilder::new()
body.write_string(background_rect(theme, width, height))
// Keep only positive entries; remember labels and values.
let items : Array[(String, Double)] = []
let mut total = 0.0
for d in data {
if d.1 > 0.0 {
items.push(d)
total += d.1
}
}
let n = items.length()
if n > 0 && total > 0.0 && plot_w > 0.0 && plot_h > 0.0 {
// Squarify expects descending areas: sort indices by value (insertion
// sort; treemaps are small).
let order : Array[Int] = []
for i in 0..= 0 && items[order[j]].1 < kv {
order[j + 1] = order[j]
j -= 1
}
order[j + 1] = key
}
let areas : Array[Double] = []
for k in 0.. 46.0 && rh > 20.0 {
body.write_string(
label(rx + 6.0, ry + 15.0, name, anchor="start", fill="#ffffff"),
)
if rh > 36.0 {
body.write_string(
label(
rx + 6.0,
ry + 30.0,
num(value),
anchor="start",
size=10,
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())
}