Skip to main content

ggplot_rs/theme/
mod.rs

1pub mod elements;
2pub mod presets;
3
4pub use elements::{ElementLine, ElementRect, ElementText};
5
6/// Position of the legend.
7#[derive(Clone, Debug)]
8pub enum LegendPosition {
9    Right,
10    Left,
11    Top,
12    Bottom,
13    None,
14    /// Inside the panel at panel-relative coordinates (0..1, 0..1), like R's
15    /// `legend.position = c(x, y)`. `(0, 0)` is bottom-left, `(1, 1)` top-right.
16    Inside(f64, f64),
17}
18
19/// Plot margins (in pixels).
20#[derive(Clone, Debug)]
21pub struct Margin {
22    pub top: f64,
23    pub right: f64,
24    pub bottom: f64,
25    pub left: f64,
26}
27
28impl Default for Margin {
29    fn default() -> Self {
30        Margin {
31            top: 10.0,
32            right: 10.0,
33            bottom: 10.0,
34            left: 10.0,
35        }
36    }
37}
38
39/// Complete theme specification for a plot.
40#[derive(Clone, Debug)]
41pub struct Theme {
42    // ── Existing fields ──
43    pub text: ElementText,
44    pub title: ElementText,
45    pub axis_text_x: ElementText,
46    pub axis_text_y: ElementText,
47    pub axis_title_x: ElementText,
48    pub axis_title_y: ElementText,
49    pub axis_line: ElementLine,
50    pub axis_ticks: ElementLine,
51    pub panel_background: ElementRect,
52    pub panel_grid_major: ElementLine,
53    pub panel_grid_minor: ElementLine,
54    pub plot_background: ElementRect,
55    pub legend_position: LegendPosition,
56    pub plot_margin: Margin,
57
58    // ── New text elements ──
59    pub subtitle: ElementText,
60    pub caption: ElementText,
61    pub legend_title: ElementText,
62    pub legend_text: ElementText,
63    pub strip_text: ElementText,
64
65    // ── Per-axis optional overrides (None = inherit from parent) ──
66    pub axis_line_x: Option<ElementLine>,
67    pub axis_line_y: Option<ElementLine>,
68    pub axis_ticks_x: Option<ElementLine>,
69    pub axis_ticks_y: Option<ElementLine>,
70    pub panel_grid_major_x: Option<ElementLine>,
71    pub panel_grid_major_y: Option<ElementLine>,
72    pub panel_grid_minor_x: Option<ElementLine>,
73    pub panel_grid_minor_y: Option<ElementLine>,
74
75    // ── New rect/line elements ──
76    pub panel_border: ElementLine,
77    pub legend_background: ElementRect,
78    pub legend_key: ElementRect,
79    pub strip_background: ElementRect,
80
81    // ── Scalar spacing/sizing ──
82    pub axis_ticks_length: f64,
83    /// Number of rows to stagger x-axis tick labels across (R's
84    /// `guide_axis(n.dodge = ...)`); 1 = no dodging.
85    pub axis_text_x_dodge: usize,
86    pub legend_key_width: f64,
87    pub legend_key_height: f64,
88    pub legend_spacing: f64,
89    pub legend_margin: Margin,
90    pub panel_spacing: f64,
91    pub panel_spacing_x: Option<f64>,
92    pub panel_spacing_y: Option<f64>,
93
94    // ── Brand / primary color ──
95    /// Optional brand color. When set, geoms that draw a single un-mapped series
96    /// (no color/fill aesthetic) use it as their default instead of the geom's
97    /// built-in color. Lets one render process serve multiple tenants' brands.
98    pub primary: Option<(u8, u8, u8)>,
99}
100
101impl Theme {
102    /// Resolve the effective series color: the theme's brand color if set,
103    /// otherwise the geom's own default.
104    pub fn primary_or(&self, fallback: (u8, u8, u8)) -> (u8, u8, u8) {
105        self.primary.unwrap_or(fallback)
106    }
107
108    /// Set the brand/primary color (builder style).
109    pub fn with_primary(mut self, color: (u8, u8, u8)) -> Self {
110        self.primary = Some(color);
111        self
112    }
113
114    // ── Fallback accessors for Option fields ──
115
116    pub fn get_axis_line_x(&self) -> &ElementLine {
117        self.axis_line_x.as_ref().unwrap_or(&self.axis_line)
118    }
119
120    pub fn get_axis_line_y(&self) -> &ElementLine {
121        self.axis_line_y.as_ref().unwrap_or(&self.axis_line)
122    }
123
124    pub fn get_axis_ticks_x(&self) -> &ElementLine {
125        self.axis_ticks_x.as_ref().unwrap_or(&self.axis_ticks)
126    }
127
128    pub fn get_axis_ticks_y(&self) -> &ElementLine {
129        self.axis_ticks_y.as_ref().unwrap_or(&self.axis_ticks)
130    }
131
132    pub fn get_panel_grid_major_x(&self) -> &ElementLine {
133        self.panel_grid_major_x
134            .as_ref()
135            .unwrap_or(&self.panel_grid_major)
136    }
137
138    pub fn get_panel_grid_major_y(&self) -> &ElementLine {
139        self.panel_grid_major_y
140            .as_ref()
141            .unwrap_or(&self.panel_grid_major)
142    }
143
144    pub fn get_panel_grid_minor_x(&self) -> &ElementLine {
145        self.panel_grid_minor_x
146            .as_ref()
147            .unwrap_or(&self.panel_grid_minor)
148    }
149
150    pub fn get_panel_grid_minor_y(&self) -> &ElementLine {
151        self.panel_grid_minor_y
152            .as_ref()
153            .unwrap_or(&self.panel_grid_minor)
154    }
155
156    pub fn get_panel_spacing_x(&self) -> f64 {
157        self.panel_spacing_x.unwrap_or(self.panel_spacing)
158    }
159
160    pub fn get_panel_spacing_y(&self) -> f64 {
161        self.panel_spacing_y.unwrap_or(self.panel_spacing)
162    }
163
164    // ── Existing setters ──
165
166    pub fn set_axis_text_x(mut self, el: ElementText) -> Self {
167        self.axis_text_x = el;
168        self
169    }
170
171    pub fn set_axis_text_y(mut self, el: ElementText) -> Self {
172        self.axis_text_y = el;
173        self
174    }
175
176    pub fn set_axis_title_x(mut self, el: ElementText) -> Self {
177        self.axis_title_x = el;
178        self
179    }
180
181    pub fn set_axis_title_y(mut self, el: ElementText) -> Self {
182        self.axis_title_y = el;
183        self
184    }
185
186    pub fn set_axis_line(mut self, el: ElementLine) -> Self {
187        self.axis_line = el;
188        self
189    }
190
191    pub fn set_axis_ticks(mut self, el: ElementLine) -> Self {
192        self.axis_ticks = el;
193        self
194    }
195
196    pub fn set_panel_background(mut self, el: ElementRect) -> Self {
197        self.panel_background = el;
198        self
199    }
200
201    pub fn set_panel_grid_major(mut self, el: ElementLine) -> Self {
202        self.panel_grid_major = el;
203        self
204    }
205
206    pub fn set_panel_grid_minor(mut self, el: ElementLine) -> Self {
207        self.panel_grid_minor = el;
208        self
209    }
210
211    pub fn set_plot_background(mut self, el: ElementRect) -> Self {
212        self.plot_background = el;
213        self
214    }
215
216    pub fn set_legend_position(mut self, pos: LegendPosition) -> Self {
217        self.legend_position = pos;
218        self
219    }
220
221    pub fn set_plot_margin(mut self, margin: Margin) -> Self {
222        self.plot_margin = margin;
223        self
224    }
225
226    pub fn set_title(mut self, el: ElementText) -> Self {
227        self.title = el;
228        self
229    }
230
231    pub fn set_text(mut self, el: ElementText) -> Self {
232        self.text = el;
233        self
234    }
235
236    // ── New setters ──
237
238    pub fn set_subtitle(mut self, el: ElementText) -> Self {
239        self.subtitle = el;
240        self
241    }
242
243    pub fn set_caption(mut self, el: ElementText) -> Self {
244        self.caption = el;
245        self
246    }
247
248    pub fn set_legend_title(mut self, el: ElementText) -> Self {
249        self.legend_title = el;
250        self
251    }
252
253    pub fn set_legend_text(mut self, el: ElementText) -> Self {
254        self.legend_text = el;
255        self
256    }
257
258    pub fn set_strip_text(mut self, el: ElementText) -> Self {
259        self.strip_text = el;
260        self
261    }
262
263    pub fn set_axis_line_x(mut self, el: Option<ElementLine>) -> Self {
264        self.axis_line_x = el;
265        self
266    }
267
268    pub fn set_axis_line_y(mut self, el: Option<ElementLine>) -> Self {
269        self.axis_line_y = el;
270        self
271    }
272
273    pub fn set_axis_ticks_x(mut self, el: Option<ElementLine>) -> Self {
274        self.axis_ticks_x = el;
275        self
276    }
277
278    pub fn set_axis_ticks_y(mut self, el: Option<ElementLine>) -> Self {
279        self.axis_ticks_y = el;
280        self
281    }
282
283    pub fn set_panel_grid_major_x(mut self, el: Option<ElementLine>) -> Self {
284        self.panel_grid_major_x = el;
285        self
286    }
287
288    pub fn set_panel_grid_major_y(mut self, el: Option<ElementLine>) -> Self {
289        self.panel_grid_major_y = el;
290        self
291    }
292
293    pub fn set_panel_grid_minor_x(mut self, el: Option<ElementLine>) -> Self {
294        self.panel_grid_minor_x = el;
295        self
296    }
297
298    pub fn set_panel_grid_minor_y(mut self, el: Option<ElementLine>) -> Self {
299        self.panel_grid_minor_y = el;
300        self
301    }
302
303    pub fn set_panel_border(mut self, el: ElementLine) -> Self {
304        self.panel_border = el;
305        self
306    }
307
308    pub fn set_legend_background(mut self, el: ElementRect) -> Self {
309        self.legend_background = el;
310        self
311    }
312
313    pub fn set_legend_key(mut self, el: ElementRect) -> Self {
314        self.legend_key = el;
315        self
316    }
317
318    pub fn set_strip_background(mut self, el: ElementRect) -> Self {
319        self.strip_background = el;
320        self
321    }
322
323    pub fn set_axis_ticks_length(mut self, val: f64) -> Self {
324        self.axis_ticks_length = val;
325        self
326    }
327
328    pub fn set_legend_key_width(mut self, val: f64) -> Self {
329        self.legend_key_width = val;
330        self
331    }
332
333    pub fn set_legend_key_height(mut self, val: f64) -> Self {
334        self.legend_key_height = val;
335        self
336    }
337
338    pub fn set_legend_spacing(mut self, val: f64) -> Self {
339        self.legend_spacing = val;
340        self
341    }
342
343    pub fn set_legend_margin(mut self, margin: Margin) -> Self {
344        self.legend_margin = margin;
345        self
346    }
347
348    pub fn set_panel_spacing(mut self, val: f64) -> Self {
349        self.panel_spacing = val;
350        self
351    }
352
353    pub fn set_panel_spacing_x(mut self, val: Option<f64>) -> Self {
354        self.panel_spacing_x = val;
355        self
356    }
357
358    pub fn set_panel_spacing_y(mut self, val: Option<f64>) -> Self {
359        self.panel_spacing_y = val;
360        self
361    }
362
363    /// Apply incremental theme modifications.
364    /// Only fields that are `Some` in the update are applied.
365    pub fn update(mut self, upd: ThemeUpdate) -> Self {
366        if let Some(v) = upd.text {
367            self.text = v;
368        }
369        if let Some(v) = upd.title {
370            self.title = v;
371        }
372        if let Some(v) = upd.subtitle {
373            self.subtitle = v;
374        }
375        if let Some(v) = upd.caption {
376            self.caption = v;
377        }
378        if let Some(v) = upd.axis_text_x {
379            self.axis_text_x = v;
380        }
381        if let Some(v) = upd.axis_text_y {
382            self.axis_text_y = v;
383        }
384        if let Some(v) = upd.axis_title_x {
385            self.axis_title_x = v;
386        }
387        if let Some(v) = upd.axis_title_y {
388            self.axis_title_y = v;
389        }
390        if let Some(v) = upd.axis_line {
391            self.axis_line = v;
392        }
393        if let Some(v) = upd.axis_ticks {
394            self.axis_ticks = v;
395        }
396        if let Some(v) = upd.panel_background {
397            self.panel_background = v;
398        }
399        if let Some(v) = upd.panel_grid_major {
400            self.panel_grid_major = v;
401        }
402        if let Some(v) = upd.panel_grid_minor {
403            self.panel_grid_minor = v;
404        }
405        if let Some(v) = upd.panel_border {
406            self.panel_border = v;
407        }
408        if let Some(v) = upd.plot_background {
409            self.plot_background = v;
410        }
411        if let Some(v) = upd.legend_position {
412            self.legend_position = v;
413        }
414        if let Some(v) = upd.legend_title {
415            self.legend_title = v;
416        }
417        if let Some(v) = upd.legend_text {
418            self.legend_text = v;
419        }
420        if let Some(v) = upd.legend_background {
421            self.legend_background = v;
422        }
423        if let Some(v) = upd.strip_text {
424            self.strip_text = v;
425        }
426        if let Some(v) = upd.strip_background {
427            self.strip_background = v;
428        }
429        if let Some(v) = upd.plot_margin {
430            self.plot_margin = v;
431        }
432        self
433    }
434}
435
436/// Incremental theme modifications. All fields are optional — only `Some` values are applied.
437/// Like R's `theme(axis.text.x = element_text(...))`.
438#[derive(Clone, Debug, Default)]
439pub struct ThemeUpdate {
440    pub text: Option<ElementText>,
441    pub title: Option<ElementText>,
442    pub subtitle: Option<ElementText>,
443    pub caption: Option<ElementText>,
444    pub axis_text_x: Option<ElementText>,
445    pub axis_text_y: Option<ElementText>,
446    pub axis_title_x: Option<ElementText>,
447    pub axis_title_y: Option<ElementText>,
448    pub axis_line: Option<ElementLine>,
449    pub axis_ticks: Option<ElementLine>,
450    pub panel_background: Option<ElementRect>,
451    pub panel_grid_major: Option<ElementLine>,
452    pub panel_grid_minor: Option<ElementLine>,
453    pub panel_border: Option<ElementLine>,
454    pub plot_background: Option<ElementRect>,
455    pub legend_position: Option<LegendPosition>,
456    pub legend_title: Option<ElementText>,
457    pub legend_text: Option<ElementText>,
458    pub legend_background: Option<ElementRect>,
459    pub strip_text: Option<ElementText>,
460    pub strip_background: Option<ElementRect>,
461    pub plot_margin: Option<Margin>,
462}
463
464impl Default for Theme {
465    fn default() -> Self {
466        presets::theme_gray()
467    }
468}