use std::cmp::Ordering;
use super::model::TreemapModel;
use crate::js::math::{max, min, round};
#[derive(Clone, Debug)]
pub(crate) struct Box {
pub(crate) node: usize,
pub(crate) parent: Option<usize>,
pub(crate) series: usize,
pub(crate) value: f64,
pub(crate) children: Vec<usize>,
pub(crate) headed: bool,
pub(crate) header: f64,
pub(crate) x: f64,
pub(crate) y: f64,
pub(crate) w: f64,
pub(crate) h: f64,
}
#[derive(Clone, Copy)]
pub(crate) struct Insets {
pub(crate) gap: f64,
pub(crate) header: f64,
pub(crate) short: f64,
pub(crate) side: f64,
}
fn new_box(model: &TreemapModel, node: usize, parent: Option<usize>, series: usize) -> Box {
let n = &model.nodes[node];
let value = match n.value {
Some(v) if n.children.is_none() && v.is_finite() && v > 0.0 => v,
_ => 0.0,
};
Box {
node,
parent,
series,
value,
children: Vec::new(),
headed: false,
header: 0.0,
x: 0.0,
y: 0.0,
w: 0.0,
h: 0.0,
}
}
fn clamp(v: f64, low: f64, high: f64) -> f64 {
if v >= low { min(v, high) } else { low }
}
#[allow(clippy::too_many_arguments)]
fn squarify(
all: &mut [Box],
boxes: &[usize],
total: f64,
mut x: f64,
mut y: f64,
mut w: f64,
mut h: f64,
gap: f64,
) {
let mut rest = total;
let mut from = 0;
while from < boxes.len() {
let scale = (w * h) / rest;
let side = min(w, h);
let mut sum = all[boxes[from]].value * scale;
let largest = sum;
let mut worst = max(
(side * side * largest) / (sum * sum),
(sum * sum) / (side * side * largest),
);
let mut to = from + 1;
while to < boxes.len() {
let smallest = all[boxes[to]].value * scale;
let next = sum + smallest;
let ratio = max(
(side * side * largest) / (next * next),
(next * next) / (side * side * smallest),
);
if ratio > worst {
break;
}
worst = ratio;
sum = next;
to += 1;
}
let thickness = sum / side;
let mut offset = 0.0;
for i in from..to {
let b = &mut all[boxes[i]];
let length = if i == to - 1 {
side - offset
} else {
(b.value * scale) / thickness
};
let (bx, by, bw, bh) = if w >= h {
(x, y + offset, thickness, length)
} else {
(x + offset, y, length, thickness)
};
let gx = if round(bx + bw - gap / 2.0) > round(bx + gap / 2.0) {
gap / 2.0
} else {
0.0
};
let gy = if round(by + bh - gap / 2.0) > round(by + gap / 2.0) {
gap / 2.0
} else {
0.0
};
b.x = round(bx + gx);
b.y = round(by + gy);
b.w = round(bx + bw - gx) - b.x;
b.h = round(by + bh - gy) - b.y;
offset += length;
rest -= b.value;
}
if w >= h {
x += thickness;
w -= thickness;
} else {
y += thickness;
h -= thickness;
}
from = to;
}
}
#[allow(clippy::too_many_arguments)]
fn place(
all: &mut [Box],
placed: &mut Vec<usize>,
children: &[usize],
x: f64,
y: f64,
w: f64,
h: f64,
gap: f64,
) {
let mut shown: Vec<usize> = children
.iter()
.copied()
.filter(|&b| all[b].value > 0.0)
.collect();
shown.sort_by(|&a, &b| {
(all[b].value - all[a].value)
.partial_cmp(&0.0)
.unwrap_or(Ordering::Equal)
});
let mut total = 0.0;
for &b in &shown {
total += all[b].value;
}
if shown.is_empty() || !total.is_finite() {
return;
}
let w = max(0.0, w);
let h = max(0.0, h);
if w > 0.0 && h > 0.0 {
squarify(
all,
&shown,
total,
x - gap / 2.0,
y - gap / 2.0,
w + gap,
h + gap,
gap,
);
}
for &i in &shown {
let b = &mut all[i];
let x0 = clamp(b.x, x, x + w);
let y0 = clamp(b.y, y, y + h);
b.w = clamp(b.x + b.w, x0, x + w) - x0;
b.h = clamp(b.y + b.h, y0, y + h) - y0;
b.x = x0;
b.y = y0;
placed.push(i);
}
}
pub(crate) fn layout_treemap(
model: &TreemapModel,
width: f64,
height: f64,
insets: &Insets,
) -> (Vec<Box>, Vec<usize>) {
let mut all: Vec<Box> = model
.roots
.iter()
.enumerate()
.map(|(index, &node)| new_box(model, node, None, index))
.collect();
let top: Vec<usize> = (0..all.len()).collect();
let mut i = 0;
while i < all.len() {
let (node, series) = (all[i].node, all[i].series);
for &child in model.nodes[node].children.as_deref().unwrap_or(&[]) {
let k = all.len();
all.push(new_box(model, child, Some(i), series));
all[i].children.push(k);
}
i += 1;
}
for i in (0..all.len()).rev() {
if let Some(parent) = all[i].parent {
all[parent].value += all[i].value;
}
}
let Insets {
gap,
header,
short,
side,
} = *insets;
let mut placed = Vec::new();
place(&mut all, &mut placed, &top, 0.0, 0.0, width, height, gap);
let mut i = 0;
while i < placed.len() {
let k = placed[i];
i += 1;
if all[k].children.is_empty() {
continue;
}
let b = &mut all[k];
let top = if b.h - header - side >= 12.0 {
header
} else {
short
};
b.headed = b.w > max(24.0, 2.0 * side) && b.h - top - side >= 8.0;
b.header = if b.headed { top } else { 0.0 };
let children = std::mem::take(&mut b.children);
let (x, y, w, h) = (b.x, b.y, b.w, b.h);
if b.headed {
place(
&mut all,
&mut placed,
&children,
x + side,
y + top,
w - 2.0 * side,
h - top - side,
gap,
);
} else {
place(&mut all, &mut placed, &children, x, y, w, h, gap);
}
all[k].children = children;
}
(all, placed)
}