Skip to main content

ggplot_rs/guide/
config.rs

1/// Configuration for legend guides.
2/// Analogous to R's `guide_legend()` and `guides()`.
3#[derive(Clone, Debug, Default)]
4pub struct GuideLegend {
5    /// Override the legend title. None = use scale name.
6    pub title: Option<String>,
7    /// Number of columns in the legend layout.
8    pub ncol: Option<usize>,
9    /// Number of rows in the legend layout.
10    pub nrow: Option<usize>,
11    /// Reverse the order of legend keys.
12    pub reverse: bool,
13    /// Fill an explicit `nrow`/`ncol` grid row-major instead of ggplot2's
14    /// default column-major (B3-2; R's `guide_legend(byrow = TRUE)`).
15    pub byrow: bool,
16    /// Cap on the number of rows an auto-wrapped horizontal legend may grow
17    /// to (B3-2); entries beyond the cap collapse into a trailing "+N more"
18    /// label. `None` (the default) uses `1 + floor(total_height / 3 /
19    /// key_height)`. `Some(0)` is invalid (see `validated`, internal) and
20    /// is treated as `Some(1)`.
21    pub max_rows: Option<usize>,
22}
23
24impl GuideLegend {
25    pub fn new() -> Self {
26        Self::default()
27    }
28
29    pub fn with_title(mut self, title: &str) -> Self {
30        self.title = Some(title.to_string());
31        self
32    }
33
34    pub fn with_ncol(mut self, ncol: usize) -> Self {
35        self.ncol = Some(ncol);
36        self
37    }
38
39    pub fn with_nrow(mut self, nrow: usize) -> Self {
40        self.nrow = Some(nrow);
41        self
42    }
43
44    pub fn reverse(mut self) -> Self {
45        self.reverse = true;
46        self
47    }
48
49    /// Fill an explicit grid row-major (left-to-right, top-to-bottom)
50    /// instead of ggplot2's default column-major.
51    pub fn with_byrow(mut self, byrow: bool) -> Self {
52        self.byrow = byrow;
53        self
54    }
55
56    /// Cap the number of rows an auto-wrapped horizontal legend may grow to.
57    pub fn with_max_rows(mut self, max_rows: usize) -> Self {
58        self.max_rows = Some(max_rows);
59        self
60    }
61
62    /// Sanitize `nrow`/`ncol`/`max_rows` against `n` breaks (B3-2, D2):
63    /// `nrow`/`ncol` of `0` is treated as unset; an explicit grid smaller
64    /// than `n` drops `ncol` (falls back to an `nrow`-only grid, which
65    /// grows columns to fit); `max_rows` of `0` is treated as `1`. Each
66    /// correction pushes one warning into `warnings` and never panics.
67    pub(crate) fn validated(&self, n: usize, warnings: &mut Vec<String>) -> GuideLegend {
68        let mut out = self.clone();
69
70        if out.nrow == Some(0) {
71            warnings.push("guide_legend: nrow(0) is invalid; using the default".to_string());
72            out.nrow = None;
73        }
74        if out.ncol == Some(0) {
75            warnings.push("guide_legend: ncol(0) is invalid; using the default".to_string());
76            out.ncol = None;
77        }
78        if let (Some(nrow), Some(ncol)) = (out.nrow, out.ncol) {
79            if nrow.saturating_mul(ncol) < n {
80                warnings.push(format!(
81                    "guide_legend: nrow({nrow}) x ncol({ncol}) = {} is smaller than {n} keys; dropping ncol",
82                    nrow.saturating_mul(ncol)
83                ));
84                out.ncol = None;
85            }
86        }
87        if out.max_rows == Some(0) {
88            warnings.push("guide_legend: max_rows(0) is invalid; using 1".to_string());
89            out.max_rows = Some(1);
90        }
91        out
92    }
93}