Skip to main content

ggplot_rs/facet/
mod.rs

1pub mod grid;
2pub mod wrap;
3
4use crate::render::Rect;
5
6/// Whether facet panels share axes.
7#[derive(Clone, Debug)]
8pub enum FacetScales {
9    Fixed,
10    FreeX,
11    FreeY,
12    Free,
13}
14
15/// Whether facet panels are sized proportionally to their data range
16/// (R's `space =`). `Fixed` = equal-size panels (default).
17#[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/// How facet strip labels are formatted.
36#[derive(Clone, Default)]
37pub enum FacetLabeller {
38    /// Show just the value (default).
39    #[default]
40    Value,
41    /// Show "var: value".
42    Both,
43    /// Custom formatting function: fn(variable_name, value) -> label.
44    Custom(fn(&str, &str) -> String),
45}
46
47impl FacetLabeller {
48    pub fn format(&self, var: &str, value: &str) -> String {
49        match self {
50            FacetLabeller::Value => value.to_string(),
51            FacetLabeller::Both => format!("{var}: {value}"),
52            FacetLabeller::Custom(f) => f(var, value),
53        }
54    }
55}
56
57/// A single panel in a faceted layout.
58#[derive(Clone, Debug)]
59pub struct Panel {
60    pub row: usize,
61    pub col: usize,
62    pub label: String,
63    pub row_label: Option<String>,
64    pub col_label: Option<String>,
65    pub rect: Rect,
66}
67
68/// Facet specification.
69#[derive(Clone, Default)]
70pub enum Facet {
71    #[default]
72    None,
73    Wrap {
74        var: String,
75        ncol: Option<usize>,
76        scales: FacetScales,
77        labeller: FacetLabeller,
78        /// Explicit panel order (ggplot2's factor levels): every level gets
79        /// a panel, even with no data (`drop = FALSE`), and rows whose value
80        /// isn't listed are dropped. `None` = values in first-appearance
81        /// order. Set with [`GGPlot::facet_levels`](crate::GGPlot::facet_levels).
82        levels: Option<Vec<String>>,
83    },
84    Grid {
85        row_var: Option<String>,
86        col_var: Option<String>,
87        scales: FacetScales,
88        labeller: FacetLabeller,
89        #[doc = "Proportional panel sizing (R's `space =`)."]
90        space: FacetSpace,
91    },
92}
93
94impl Facet {
95    pub fn is_none(&self) -> bool {
96        matches!(self, Facet::None)
97    }
98
99    pub fn labeller(&self) -> &FacetLabeller {
100        match self {
101            Facet::None => &FacetLabeller::Value,
102            Facet::Wrap { labeller, .. } => labeller,
103            Facet::Grid { labeller, .. } => labeller,
104        }
105    }
106}