1use crate::render::Rect;
2
3use super::Panel;
4
5pub fn compute_wrap_panels(
7 levels: &[String],
8 ncol: Option<usize>,
9 total_area: &Rect,
10 strip_height: f64,
11) -> Vec<Panel> {
12 let n = levels.len();
13 if n == 0 {
14 return vec![];
15 }
16
17 let ncol = ncol.unwrap_or_else(|| {
18 let sqrt = (n as f64).sqrt().ceil() as usize;
19 sqrt.max(1)
20 });
21 let nrow = n.div_ceil(ncol);
22
23 let panel_width = total_area.width / ncol as f64;
24 let panel_height = total_area.height / nrow as f64;
25
26 levels
27 .iter()
28 .enumerate()
29 .map(|(i, label)| {
30 let row = i / ncol;
31 let col = i % ncol;
32
33 Panel {
34 row,
35 col,
36 label: label.clone(),
37 row_label: None,
38 col_label: Some(label.clone()),
39 rect: Rect {
40 x: total_area.x + col as f64 * panel_width,
41 y: total_area.y + row as f64 * panel_height + strip_height,
42 width: panel_width,
43 height: panel_height - strip_height,
44 },
45 }
46 })
47 .collect()
48}
49
50#[derive(Debug, Clone, Copy, PartialEq)]
53pub struct FacetGrid {
54 pub ncol: usize,
55 pub nrow: usize,
56 pub panel_w: f64,
57 pub panel_h: f64,
58}
59
60#[allow(clippy::too_many_arguments)]
67pub fn fit_grid(
68 n: usize,
69 ncol: Option<usize>,
70 aspect: f64,
71 area_w: f64,
72 area_h: f64,
73 strip_h: f64,
74 gap_x: f64,
75 gap_y: f64,
76) -> FacetGrid {
77 let n = n.max(1);
78 let aspect = if aspect.is_finite() && aspect > 0.0 {
79 aspect
80 } else {
81 1.0
82 };
83 let size = |c: usize| {
84 let r = n.div_ceil(c);
85 let pw = (area_w - gap_x * (c as f64 - 1.0)) / c as f64;
86 let ph = (area_h - r as f64 * strip_h - gap_y * (r as f64 - 1.0)) / r as f64;
87 (pw.min(ph / aspect)).max(0.0)
88 };
89 let c = match ncol {
90 Some(c) => c.clamp(1, n),
91 None => {
92 let mut best = (1, size(1));
93 for c in 2..=n {
94 let s = size(c);
95 if s > best.1 + 1e-9
97 || ((s - best.1).abs() <= 1e-9 && n.div_ceil(c) < n.div_ceil(best.0))
98 {
99 best = (c, s);
100 }
101 }
102 best.0
103 }
104 };
105 let s = size(c);
106 FacetGrid {
107 ncol: c,
108 nrow: n.div_ceil(c),
109 panel_w: s,
110 panel_h: s * aspect,
111 }
112}
113
114#[allow(clippy::too_many_arguments)]
120pub fn natural_height(
121 n: usize,
122 ncol: Option<usize>,
123 aspect: f64,
124 width: f64,
125 strip_h: f64,
126 gap_x: f64,
127 gap_y: f64,
128 min_panel_w: f64,
129) -> (usize, f64) {
130 let n = n.max(1);
131 let aspect = if aspect.is_finite() && aspect > 0.0 {
132 aspect
133 } else {
134 1.0
135 };
136 let c = match ncol {
137 Some(c) => c.clamp(1, n),
138 None => (((width + gap_x) / (min_panel_w + gap_x)).floor() as usize).clamp(1, n),
139 };
140 let r = n.div_ceil(c);
141 let pw = ((width - gap_x * (c as f64 - 1.0)) / c as f64).max(0.0);
142 (
143 c,
144 r as f64 * (pw * aspect + strip_h) + gap_y * (r as f64 - 1.0),
145 )
146}
147
148#[cfg(test)]
149mod grid_tests {
150 use super::*;
151
152 #[test]
153 fn fit_grid_picks_the_largest_panels() {
154 let g = fit_grid(4, None, 1.3, 1200.0, 400.0, 0.0, 0.0, 0.0);
156 assert_eq!((g.ncol, g.nrow), (4, 1));
157 let g = fit_grid(4, None, 1.0, 600.0, 600.0, 0.0, 0.0, 0.0);
159 assert_eq!((g.ncol, g.nrow), (2, 2));
160 assert!((g.panel_w - 300.0).abs() < 1e-9 && (g.panel_h - 300.0).abs() < 1e-9);
161 }
162
163 #[test]
164 fn fit_grid_respects_strips_gaps_and_a_given_ncol() {
165 let g = fit_grid(3, Some(3), 0.5, 620.0, 400.0, 20.0, 10.0, 10.0);
166 assert_eq!((g.ncol, g.nrow), (3, 1));
167 assert!((g.panel_w - 200.0).abs() < 1e-9);
168 assert!((g.panel_h - 100.0).abs() < 1e-9);
169 assert_eq!(
171 fit_grid(2, Some(9), 1.0, 100.0, 100.0, 0.0, 0.0, 0.0).ncol,
172 2
173 );
174 let g = fit_grid(2, Some(1), 1.0, 1000.0, 220.0, 10.0, 0.0, 0.0);
176 assert!((g.panel_h - 100.0).abs() < 1e-9 && (g.panel_w - 100.0).abs() < 1e-9);
177 }
178
179 #[test]
180 fn natural_height_fills_the_width() {
181 let (c, h) = natural_height(4, None, 1.0, 700.0, 20.0, 10.0, 10.0, 160.0);
183 assert_eq!(c, 4);
184 assert!((h - (167.5 + 20.0)).abs() < 1e-9);
185 let (c, h) = natural_height(4, None, 1.0, 390.0, 20.0, 10.0, 10.0, 160.0);
187 assert_eq!(c, 2);
188 assert!((h - (2.0 * (190.0 + 20.0) + 10.0)).abs() < 1e-9);
189 assert_eq!(
191 natural_height(4, Some(1), 1.0, 700.0, 0.0, 0.0, 0.0, 160.0).0,
192 1
193 );
194 }
195}