use crate::render::Rect;
use super::Panel;
pub fn compute_wrap_panels(
levels: &[String],
ncol: Option<usize>,
total_area: &Rect,
strip_height: f64,
) -> Vec<Panel> {
let n = levels.len();
if n == 0 {
return vec![];
}
let ncol = ncol.unwrap_or_else(|| {
let sqrt = (n as f64).sqrt().ceil() as usize;
sqrt.max(1)
});
let nrow = n.div_ceil(ncol);
let panel_width = total_area.width / ncol as f64;
let panel_height = total_area.height / nrow as f64;
levels
.iter()
.enumerate()
.map(|(i, label)| {
let row = i / ncol;
let col = i % ncol;
Panel {
row,
col,
label: label.clone(),
row_label: None,
col_label: Some(label.clone()),
rect: Rect {
x: total_area.x + col as f64 * panel_width,
y: total_area.y + row as f64 * panel_height + strip_height,
width: panel_width,
height: panel_height - strip_height,
},
}
})
.collect()
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FacetGrid {
pub ncol: usize,
pub nrow: usize,
pub panel_w: f64,
pub panel_h: f64,
}
#[allow(clippy::too_many_arguments)]
pub fn fit_grid(
n: usize,
ncol: Option<usize>,
aspect: f64,
area_w: f64,
area_h: f64,
strip_h: f64,
gap_x: f64,
gap_y: f64,
) -> FacetGrid {
let n = n.max(1);
let aspect = if aspect.is_finite() && aspect > 0.0 {
aspect
} else {
1.0
};
let size = |c: usize| {
let r = n.div_ceil(c);
let pw = (area_w - gap_x * (c as f64 - 1.0)) / c as f64;
let ph = (area_h - r as f64 * strip_h - gap_y * (r as f64 - 1.0)) / r as f64;
(pw.min(ph / aspect)).max(0.0)
};
let c = match ncol {
Some(c) => c.clamp(1, n),
None => {
let mut best = (1, size(1));
for c in 2..=n {
let s = size(c);
if s > best.1 + 1e-9
|| ((s - best.1).abs() <= 1e-9 && n.div_ceil(c) < n.div_ceil(best.0))
{
best = (c, s);
}
}
best.0
}
};
let s = size(c);
FacetGrid {
ncol: c,
nrow: n.div_ceil(c),
panel_w: s,
panel_h: s * aspect,
}
}
#[allow(clippy::too_many_arguments)]
pub fn natural_height(
n: usize,
ncol: Option<usize>,
aspect: f64,
width: f64,
strip_h: f64,
gap_x: f64,
gap_y: f64,
min_panel_w: f64,
) -> (usize, f64) {
let n = n.max(1);
let aspect = if aspect.is_finite() && aspect > 0.0 {
aspect
} else {
1.0
};
let c = match ncol {
Some(c) => c.clamp(1, n),
None => (((width + gap_x) / (min_panel_w + gap_x)).floor() as usize).clamp(1, n),
};
let r = n.div_ceil(c);
let pw = ((width - gap_x * (c as f64 - 1.0)) / c as f64).max(0.0);
(
c,
r as f64 * (pw * aspect + strip_h) + gap_y * (r as f64 - 1.0),
)
}
#[cfg(test)]
mod grid_tests {
use super::*;
#[test]
fn fit_grid_picks_the_largest_panels() {
let g = fit_grid(4, None, 1.3, 1200.0, 400.0, 0.0, 0.0, 0.0);
assert_eq!((g.ncol, g.nrow), (4, 1));
let g = fit_grid(4, None, 1.0, 600.0, 600.0, 0.0, 0.0, 0.0);
assert_eq!((g.ncol, g.nrow), (2, 2));
assert!((g.panel_w - 300.0).abs() < 1e-9 && (g.panel_h - 300.0).abs() < 1e-9);
}
#[test]
fn fit_grid_respects_strips_gaps_and_a_given_ncol() {
let g = fit_grid(3, Some(3), 0.5, 620.0, 400.0, 20.0, 10.0, 10.0);
assert_eq!((g.ncol, g.nrow), (3, 1));
assert!((g.panel_w - 200.0).abs() < 1e-9);
assert!((g.panel_h - 100.0).abs() < 1e-9);
assert_eq!(
fit_grid(2, Some(9), 1.0, 100.0, 100.0, 0.0, 0.0, 0.0).ncol,
2
);
let g = fit_grid(2, Some(1), 1.0, 1000.0, 220.0, 10.0, 0.0, 0.0);
assert!((g.panel_h - 100.0).abs() < 1e-9 && (g.panel_w - 100.0).abs() < 1e-9);
}
#[test]
fn natural_height_fills_the_width() {
let (c, h) = natural_height(4, None, 1.0, 700.0, 20.0, 10.0, 10.0, 160.0);
assert_eq!(c, 4);
assert!((h - (167.5 + 20.0)).abs() < 1e-9);
let (c, h) = natural_height(4, None, 1.0, 390.0, 20.0, 10.0, 10.0, 160.0);
assert_eq!(c, 2);
assert!((h - (2.0 * (190.0 + 20.0) + 10.0)).abs() < 1e-9);
assert_eq!(
natural_height(4, Some(1), 1.0, 700.0, 0.0, 0.0, 0.0, 160.0).0,
1
);
}
}