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