1pub mod grid;
2pub mod wrap;
3
4use crate::render::Rect;
5
6#[derive(Clone, Debug)]
8pub enum FacetScales {
9 Fixed,
10 FreeX,
11 FreeY,
12 Free,
13}
14
15#[derive(Clone, Debug, Default, PartialEq)]
18pub enum FacetSpace {
19 #[default]
20 Fixed,
21 FreeX,
22 FreeY,
23 Free,
24}
25
26impl FacetSpace {
27 pub fn free_x(&self) -> bool {
28 matches!(self, FacetSpace::FreeX | FacetSpace::Free)
29 }
30 pub fn free_y(&self) -> bool {
31 matches!(self, FacetSpace::FreeY | FacetSpace::Free)
32 }
33}
34
35#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
38pub enum StripPosition {
39 #[default]
40 Top,
41 Bottom,
42 Left,
43 Right,
44}
45
46#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
50pub enum FacetSwitch {
51 #[default]
52 None,
53 X,
54 Y,
55 Both,
56}
57
58impl FacetSwitch {
59 pub fn x(self) -> bool {
60 matches!(self, FacetSwitch::X | FacetSwitch::Both)
61 }
62 pub fn y(self) -> bool {
63 matches!(self, FacetSwitch::Y | FacetSwitch::Both)
64 }
65}
66
67#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
70pub struct FacetAxes {
71 pub all_x: bool,
73 pub all_y: bool,
75 pub margin_labels_x: bool,
78 pub margin_labels_y: bool,
80}
81
82impl FacetAxes {
83 pub fn all() -> Self {
85 FacetAxes {
86 all_x: true,
87 all_y: true,
88 ..Self::default()
89 }
90 }
91
92 pub fn all_x() -> Self {
94 FacetAxes {
95 all_x: true,
96 ..Self::default()
97 }
98 }
99
100 pub fn all_y() -> Self {
102 FacetAxes {
103 all_y: true,
104 ..Self::default()
105 }
106 }
107
108 pub fn margin_labels(mut self) -> Self {
110 self.margin_labels_x = true;
111 self.margin_labels_y = true;
112 self
113 }
114}
115
116#[derive(Clone, Default)]
118pub enum FacetLabeller {
119 #[default]
121 Value,
122 Both,
124 Custom(fn(&str, &str) -> String),
126 Wrap(usize),
129 Template(String),
133 PerVar(Vec<(String, FacetLabeller)>),
136 Lookup(Vec<(String, String)>),
139 MultiLine,
142 Parsed,
146}
147
148pub fn plotmath_to_unicode(expr: &str) -> String {
150 const GREEK: [(&str, &str); 24] = [
151 ("alpha", "α"),
152 ("beta", "β"),
153 ("gamma", "γ"),
154 ("delta", "δ"),
155 ("epsilon", "ε"),
156 ("zeta", "ζ"),
157 ("eta", "η"),
158 ("theta", "θ"),
159 ("iota", "ι"),
160 ("kappa", "κ"),
161 ("lambda", "λ"),
162 ("mu", "μ"),
163 ("nu", "ν"),
164 ("xi", "ξ"),
165 ("pi", "π"),
166 ("rho", "ρ"),
167 ("sigma", "σ"),
168 ("tau", "τ"),
169 ("upsilon", "υ"),
170 ("phi", "φ"),
171 ("chi", "χ"),
172 ("psi", "ψ"),
173 ("omega", "ω"),
174 ("Delta", "Δ"),
175 ];
176 let sup = |c: char| -> Option<char> {
177 Some(match c {
178 '0' => '⁰',
179 '1' => '¹',
180 '2' => '²',
181 '3' => '³',
182 '4' => '⁴',
183 '5' => '⁵',
184 '6' => '⁶',
185 '7' => '⁷',
186 '8' => '⁸',
187 '9' => '⁹',
188 '+' => '⁺',
189 '-' => '⁻',
190 'n' => 'ⁿ',
191 'i' => 'ⁱ',
192 _ => return None,
193 })
194 };
195 let sub = |c: char| -> Option<char> {
196 Some(match c {
197 '0' => '₀',
198 '1' => '₁',
199 '2' => '₂',
200 '3' => '₃',
201 '4' => '₄',
202 '5' => '₅',
203 '6' => '₆',
204 '7' => '₇',
205 '8' => '₈',
206 '9' => '₉',
207 '+' => '₊',
208 '-' => '₋',
209 'a' => 'ₐ',
210 'e' => 'ₑ',
211 'i' => 'ᵢ',
212 'j' => 'ⱼ',
213 'n' => 'ₙ',
214 'o' => 'ₒ',
215 'x' => 'ₓ',
216 'k' => 'ₖ',
217 'm' => 'ₘ',
218 't' => 'ₜ',
219 _ => return None,
220 })
221 };
222 let s = expr
223 .replace("%*%", "×")
224 .replace("==", "=")
225 .replace("%+-%", "±");
226 let chars: Vec<char> = s.chars().collect();
227 let mut out = String::new();
228 let mut i = 0;
229 while i < chars.len() {
230 let c = chars[i];
231 if c.is_ascii_alphabetic() {
232 let start = i;
233 while i < chars.len() && chars[i].is_ascii_alphanumeric() {
234 i += 1;
235 }
236 let word: String = chars[start..i].iter().collect();
237 match GREEK.iter().find(|(n, _)| *n == word) {
238 Some((_, g)) => out.push_str(g),
239 None => out.push_str(&word),
240 }
241 continue;
242 }
243 if c == '^' || c == '[' {
244 let (close, map): (Option<char>, &dyn Fn(char) -> Option<char>) = if c == '^' {
245 (None, &sup)
246 } else {
247 (Some(']'), &sub)
248 };
249 i += 1;
250 let braced = c == '^' && chars.get(i) == Some(&'{');
251 if braced {
252 i += 1;
253 }
254 let mut buf = String::new();
255 while i < chars.len() {
256 let d = chars[i];
257 if Some(d) == close || (braced && d == '}') {
258 i += 1;
259 break;
260 }
261 if close.is_none()
262 && !braced
263 && !(d.is_ascii_alphanumeric() || d == '-' || d == '+')
264 {
265 break;
266 }
267 buf.push(d);
268 i += 1;
269 }
270 let mapped: Option<String> = buf.chars().map(map).collect();
271 match mapped {
272 Some(m) => out.push_str(&m),
273 None => {
274 out.push(if c == '^' { '^' } else { '_' });
275 out.push_str(&buf);
276 }
277 }
278 continue;
279 }
280 if c == '*' || c == '~' {
281 if c == '~' {
282 out.push(' ');
283 }
284 i += 1;
285 continue;
286 }
287 out.push(c);
288 i += 1;
289 }
290 out
291}
292
293pub fn wrap_label(text: &str, width: usize) -> String {
295 let mut lines: Vec<String> = Vec::new();
296 for word in text.split_whitespace() {
297 match lines.last_mut() {
298 Some(l) if l.chars().count() + 1 + word.chars().count() <= width.max(1) => {
299 l.push(' ');
300 l.push_str(word);
301 }
302 _ => lines.push(word.to_string()),
303 }
304 }
305 lines.join("\n")
306}
307
308impl FacetLabeller {
309 pub fn per_var(per: Vec<(&str, FacetLabeller)>) -> Self {
311 FacetLabeller::PerVar(per.into_iter().map(|(v, l)| (v.to_string(), l)).collect())
312 }
313
314 pub fn lookup(map: Vec<(&str, &str)>) -> Self {
316 FacetLabeller::Lookup(
317 map.into_iter()
318 .map(|(k, v)| (k.to_string(), v.to_string()))
319 .collect(),
320 )
321 }
322
323 pub fn format(&self, var: &str, value: &str) -> String {
324 match self {
325 FacetLabeller::Value => value.to_string(),
326 FacetLabeller::Both => format!("{var}: {value}"),
327 FacetLabeller::Custom(f) => f(var, value),
328 FacetLabeller::Wrap(w) => wrap_label(value, *w),
329 FacetLabeller::Template(t) => t.replace("{var}", var).replace("{value}", value),
330 FacetLabeller::PerVar(per) => per
331 .iter()
332 .find(|(v, _)| v == var)
333 .map_or_else(|| value.to_string(), |(_, l)| l.format(var, value)),
334 FacetLabeller::MultiLine => value.replace(" . ", "\n"),
335 FacetLabeller::Parsed => plotmath_to_unicode(value),
336 FacetLabeller::Lookup(map) => map
337 .iter()
338 .find(|(k, _)| k == value)
339 .map_or_else(|| value.to_string(), |(_, l)| l.clone()),
340 }
341 }
342}
343
344#[derive(Clone, Debug)]
346pub struct Panel {
347 pub row: usize,
348 pub col: usize,
349 pub label: String,
350 pub row_label: Option<String>,
351 pub col_label: Option<String>,
352 pub rect: Rect,
353}
354
355#[derive(Clone, Default)]
357pub enum Facet {
358 #[default]
359 None,
360 Wrap {
361 var: String,
362 ncol: Option<usize>,
363 scales: FacetScales,
364 labeller: FacetLabeller,
365 levels: Option<Vec<String>>,
370 vertical: bool,
372 as_table: bool,
374 strip_position: StripPosition,
376 space: FacetSpace,
379 },
380 Grid {
381 row_var: Option<String>,
382 col_var: Option<String>,
383 scales: FacetScales,
384 labeller: FacetLabeller,
385 #[doc = "Proportional panel sizing (R's `space =`)."]
386 space: FacetSpace,
387 switch: FacetSwitch,
390 row_levels: Option<Vec<String>>,
394 col_levels: Option<Vec<String>>,
395 margins: bool,
398 },
399}
400
401impl Facet {
402 pub fn is_none(&self) -> bool {
403 matches!(self, Facet::None)
404 }
405
406 pub fn labeller(&self) -> &FacetLabeller {
407 match self {
408 Facet::None => &FacetLabeller::Value,
409 Facet::Wrap { labeller, .. } => labeller,
410 Facet::Grid { labeller, .. } => labeller,
411 }
412 }
413}
414
415#[cfg(test)]
416mod labeller_tests {
417 use super::FacetLabeller;
418
419 #[test]
420 fn template_per_var_and_lookup() {
421 assert_eq!(
422 FacetLabeller::Template("{var} = {value}".into()).format("cyl", "4"),
423 "cyl = 4"
424 );
425 let l = FacetLabeller::per_var(vec![
426 ("cyl", FacetLabeller::Both),
427 (
428 "am",
429 FacetLabeller::lookup(vec![("0", "automatic"), ("1", "manual")]),
430 ),
431 ]);
432 assert_eq!(l.format("cyl", "6"), "cyl: 6");
433 assert_eq!(l.format("am", "1"), "manual");
434 assert_eq!(l.format("am", "2"), "2");
435 assert_eq!(l.format("gear", "3"), "3");
436 assert_eq!(FacetLabeller::MultiLine.format("v", "a . b"), "a\nb");
437 let p = |e: &str| FacetLabeller::Parsed.format("v", e);
438 assert_eq!(p("alpha^2"), "α²");
439 assert_eq!(p("x[i] == beta"), "xᵢ = β");
440 assert_eq!(p("10^{-3}~mu*g"), "10⁻³ μg");
441 assert_eq!(p("a %*% b"), "a × b");
442 }
443}