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 /// Filled by the build (#410): every layer's legend glyph and the legend
23 /// aesthetics it shows, so keys draw each layer's own picture.
24 #[doc(hidden)]
25 pub layer_keys: Vec<crate::guide::key::LayerKey>,
26}
27
28impl GuideLegend {
29 pub fn new() -> Self {
30 Self::default()
31 }
32
33 pub fn with_title(mut self, title: &str) -> Self {
34 self.title = Some(title.to_string());
35 self
36 }
37
38 pub fn with_ncol(mut self, ncol: usize) -> Self {
39 self.ncol = Some(ncol);
40 self
41 }
42
43 pub fn with_nrow(mut self, nrow: usize) -> Self {
44 self.nrow = Some(nrow);
45 self
46 }
47
48 pub fn reverse(mut self) -> Self {
49 self.reverse = true;
50 self
51 }
52
53 /// Fill an explicit grid row-major (left-to-right, top-to-bottom)
54 /// instead of ggplot2's default column-major.
55 pub fn with_byrow(mut self, byrow: bool) -> Self {
56 self.byrow = byrow;
57 self
58 }
59
60 /// Cap the number of rows an auto-wrapped horizontal legend may grow to.
61 pub fn with_max_rows(mut self, max_rows: usize) -> Self {
62 self.max_rows = Some(max_rows);
63 self
64 }
65
66 /// Sanitize `nrow`/`ncol`/`max_rows` against `n` breaks (B3-2, D2):
67 /// `nrow`/`ncol` of `0` is treated as unset; an explicit grid smaller
68 /// than `n` drops `ncol` (falls back to an `nrow`-only grid, which
69 /// grows columns to fit); `max_rows` of `0` is treated as `1`. Each
70 /// correction pushes one warning into `warnings` and never panics.
71 pub(crate) fn validated(&self, n: usize, warnings: &mut Vec<String>) -> GuideLegend {
72 let mut out = self.clone();
73
74 if out.nrow == Some(0) {
75 warnings.push("guide_legend: nrow(0) is invalid; using the default".to_string());
76 out.nrow = None;
77 }
78 if out.ncol == Some(0) {
79 warnings.push("guide_legend: ncol(0) is invalid; using the default".to_string());
80 out.ncol = None;
81 }
82 if let (Some(nrow), Some(ncol)) = (out.nrow, out.ncol) {
83 if nrow.saturating_mul(ncol) < n {
84 warnings.push(format!(
85 "guide_legend: nrow({nrow}) x ncol({ncol}) = {} is smaller than {n} keys; dropping ncol",
86 nrow.saturating_mul(ncol)
87 ));
88 out.ncol = None;
89 }
90 }
91 if out.max_rows == Some(0) {
92 warnings.push("guide_legend: max_rows(0) is invalid; using 1".to_string());
93 out.max_rows = Some(1);
94 }
95 out
96 }
97}