Skip to main content

ggplot_rs/theme/
presets.rs

1use super::elements::{ElementLine, ElementRect, ElementText};
2use super::{LegendPosition, Margin, Theme};
3
4/// Default base font size (matches ggplot2's default of 11pt).
5const DEFAULT_BASE_SIZE: f64 = 11.0;
6
7/// Compute text sizes relative to a base size, matching ggplot2 proportions.
8fn text_sizes(base_size: f64) -> (f64, f64, f64) {
9    let title = base_size * 1.2; // rel(1.2)
10    let axis_title = base_size; // inherits from text (rel(1.0))
11    let axis_text = base_size * 0.8; // rel(0.8)
12    (title, axis_title, axis_text)
13}
14
15// ─── theme_gray ──────────────────────────────────────────────────
16
17/// Classic ggplot2 gray theme with default base size.
18pub fn theme_gray() -> Theme {
19    theme_gray_base(DEFAULT_BASE_SIZE)
20}
21
22/// Classic ggplot2 gray theme with custom base font size.
23///
24/// Matches ggplot2's `theme_grey()` defaults:
25/// - Black text, grey30 axis text, grey20 ticks
26/// - Grey92 panel background with white gridlines
27/// - Axis lines blank (panel defined by background, not lines)
28/// - Grey85 strip background, grey10 strip text
29pub fn theme_gray_base(base_size: f64) -> Theme {
30    let (title_size, axis_title_size, axis_text_size) = text_sizes(base_size);
31    let half_line = base_size / 2.0;
32    Theme {
33        text: ElementText {
34            size: base_size,
35            color: (0, 0, 0),
36            ..Default::default()
37        },
38        title: ElementText {
39            size: title_size,
40            color: (0, 0, 0),
41            ..Default::default()
42        },
43        axis_text_x: ElementText {
44            size: axis_text_size,
45            color: (77, 77, 77), // grey30
46            ..Default::default()
47        },
48        axis_text_y: ElementText {
49            size: axis_text_size,
50            color: (77, 77, 77), // grey30
51            hjust: 1.0,
52            ..Default::default()
53        },
54        axis_title_x: ElementText {
55            size: axis_title_size,
56            color: (0, 0, 0),
57            ..Default::default()
58        },
59        axis_title_y: ElementText {
60            size: axis_title_size,
61            color: (0, 0, 0),
62            angle: 90.0,
63            ..Default::default()
64        },
65        axis_line: ElementLine::blank(), // ggplot2: element_blank()
66        axis_ticks: ElementLine {
67            color: (51, 51, 51), // grey20
68            width: 0.5,
69            visible: true,
70        },
71        panel_background: ElementRect {
72            fill: Some((235, 235, 235)), // grey92
73            color: None,
74            width: 0.0,
75            visible: true,
76        },
77        panel_grid_major: ElementLine {
78            color: (255, 255, 255), // white
79            width: 1.0,
80            visible: true,
81        },
82        panel_grid_minor: ElementLine {
83            color: (255, 255, 255), // white (same as major, just thinner)
84            width: 0.5,
85            visible: true,
86        },
87        plot_background: ElementRect {
88            fill: Some((255, 255, 255)),
89            color: None,
90            width: 0.0,
91            visible: true,
92        },
93        legend_position: LegendPosition::Right,
94        plot_margin: Margin {
95            top: half_line,
96            right: half_line,
97            bottom: half_line,
98            left: half_line,
99        },
100
101        // ── New text elements ──
102        subtitle: ElementText {
103            size: base_size,
104            color: (0, 0, 0),
105            ..Default::default()
106        },
107        caption: ElementText {
108            size: axis_text_size, // rel(0.8)
109            color: (0, 0, 0),
110            ..Default::default()
111        },
112        legend_title: ElementText {
113            size: base_size,
114            color: (0, 0, 0),
115            ..Default::default()
116        },
117        legend_text: ElementText {
118            size: axis_text_size, // rel(0.8)
119            color: (0, 0, 0),
120            ..Default::default()
121        },
122        strip_text: ElementText {
123            size: axis_text_size, // rel(0.8)
124            color: (26, 26, 26),  // grey10
125            ..Default::default()
126        },
127
128        // ── Per-axis overrides (None = inherit) ──
129        axis_line_x: None,
130        axis_line_y: None,
131        axis_ticks_x: None,
132        axis_ticks_y: None,
133        panel_grid_major_x: None,
134        panel_grid_major_y: None,
135        panel_grid_minor_x: None,
136        panel_grid_minor_y: None,
137
138        // ── New rect/line elements ──
139        panel_border: ElementLine::blank(),
140        legend_background: ElementRect {
141            fill: Some((255, 255, 255)),
142            color: None,
143            width: 0.0,
144            visible: true,
145        },
146        legend_key: ElementRect {
147            fill: Some((242, 242, 242)), // grey95
148            color: None,
149            width: 0.0,
150            visible: true,
151        },
152        strip_background: ElementRect {
153            fill: Some((217, 217, 217)), // grey85
154            color: None,                 // colour = NA
155            width: 0.0,
156            visible: true,
157        },
158
159        // ── Scalar spacing/sizing ──
160        axis_ticks_length: half_line / 2.0,
161        axis_text_x_dodge: 1,
162        legend_key_width: 12.0,
163        legend_key_height: 18.0,
164        legend_spacing: 4.0,
165        legend_margin: Margin {
166            top: 10.0,
167            right: 15.0,
168            bottom: 10.0,
169            left: 10.0,
170        },
171        panel_spacing: half_line,
172        panel_spacing_x: None,
173        panel_spacing_y: None,
174        primary: None,
175    }
176}
177
178// ─── theme_bw ────────────────────────────────────────────────────
179
180/// Black and white theme (default base size).
181pub fn theme_bw() -> Theme {
182    theme_bw_base(DEFAULT_BASE_SIZE)
183}
184
185/// Black and white theme with custom base font size.
186///
187/// Inherits from theme_grey. White panel with grey20 border,
188/// grey92 gridlines on white background.
189pub fn theme_bw_base(base_size: f64) -> Theme {
190    Theme {
191        panel_background: ElementRect {
192            fill: Some((255, 255, 255)),
193            color: None,
194            width: 0.0,
195            visible: true,
196        },
197        panel_border: ElementLine {
198            color: (51, 51, 51), // grey20
199            width: 1.0,
200            visible: true,
201        },
202        panel_grid_major: ElementLine {
203            color: (235, 235, 235), // grey92
204            width: 0.5,
205            visible: true,
206        },
207        panel_grid_minor: ElementLine {
208            color: (235, 235, 235), // grey92, thinner
209            width: 0.25,
210            visible: true,
211        },
212        strip_background: ElementRect {
213            fill: Some((217, 217, 217)), // grey85
214            color: Some((51, 51, 51)),   // grey20
215            width: 0.5,
216            visible: true,
217        },
218        legend_key: ElementRect {
219            fill: Some((255, 255, 255)), // white
220            color: None,
221            width: 0.0,
222            visible: true,
223        },
224        ..theme_gray_base(base_size)
225    }
226}
227
228// ─── theme_minimal ───────────────────────────────────────────────
229
230/// Minimal theme with no panel background (default base size).
231pub fn theme_minimal() -> Theme {
232    theme_minimal_base(DEFAULT_BASE_SIZE)
233}
234
235/// Minimal theme with no backgrounds. Inherits from theme_bw.
236pub fn theme_minimal_base(base_size: f64) -> Theme {
237    Theme {
238        axis_ticks: ElementLine::blank(),
239        panel_background: ElementRect::blank(),
240        panel_border: ElementLine::blank(),
241        panel_grid_major: ElementLine {
242            color: (235, 235, 235), // grey92 (from bw)
243            width: 0.5,
244            visible: true,
245        },
246        panel_grid_minor: ElementLine {
247            color: (235, 235, 235),
248            width: 0.25,
249            visible: true,
250        },
251        plot_background: ElementRect::blank(),
252        legend_background: ElementRect::blank(),
253        legend_key: ElementRect::blank(),
254        strip_background: ElementRect::blank(),
255        ..theme_bw_base(base_size)
256    }
257}
258
259// ─── theme_classic ───────────────────────────────────────────────
260
261/// Classic theme: white background, no gridlines, L-shaped axis lines only.
262/// Traditional academic/publication style. Inherits from theme_bw.
263pub fn theme_classic() -> Theme {
264    theme_classic_base(DEFAULT_BASE_SIZE)
265}
266
267/// Classic theme with custom base font size.
268pub fn theme_classic_base(base_size: f64) -> Theme {
269    Theme {
270        panel_border: ElementLine::blank(),
271        panel_grid_major: ElementLine::blank(),
272        panel_grid_minor: ElementLine::blank(),
273        axis_line: ElementLine {
274            color: (0, 0, 0),
275            width: 0.5,
276            visible: true,
277        },
278        axis_ticks: ElementLine {
279            color: (0, 0, 0),
280            width: 0.5,
281            visible: true,
282        },
283        strip_background: ElementRect {
284            fill: Some((255, 255, 255)),
285            color: Some((0, 0, 0)),
286            width: 1.0,
287            visible: true,
288        },
289        ..theme_bw_base(base_size)
290    }
291}
292
293// ─── theme_linedraw ──────────────────────────────────────────────
294
295/// Linedraw theme: white background, black panel border, very thin black gridlines.
296/// Technical drawing aesthetic. Inherits from theme_bw.
297pub fn theme_linedraw() -> Theme {
298    theme_linedraw_base(DEFAULT_BASE_SIZE)
299}
300
301/// Linedraw theme with custom base font size.
302pub fn theme_linedraw_base(base_size: f64) -> Theme {
303    Theme {
304        panel_border: ElementLine {
305            color: (0, 0, 0),
306            width: 1.0,
307            visible: true,
308        },
309        panel_grid_major: ElementLine {
310            color: (0, 0, 0), // black, very thin
311            width: 0.1,
312            visible: true,
313        },
314        panel_grid_minor: ElementLine {
315            color: (0, 0, 0), // black, extremely thin
316            width: 0.05,
317            visible: true,
318        },
319        axis_ticks: ElementLine {
320            color: (0, 0, 0),
321            width: 0.5,
322            visible: true,
323        },
324        strip_background: ElementRect {
325            fill: Some((0, 0, 0)), // black
326            color: None,
327            width: 0.0,
328            visible: true,
329        },
330        strip_text: ElementText {
331            size: base_size * 0.8,
332            color: (255, 255, 255), // white text on black strip
333            ..Default::default()
334        },
335        ..theme_bw_base(base_size)
336    }
337}
338
339// ─── theme_light ─────────────────────────────────────────────────
340
341/// Light theme: white background, light gray panel border and gridlines.
342/// Softer version with grey70 accents. Inherits from theme_grey.
343pub fn theme_light() -> Theme {
344    theme_light_base(DEFAULT_BASE_SIZE)
345}
346
347/// Light theme with custom base font size.
348pub fn theme_light_base(base_size: f64) -> Theme {
349    Theme {
350        panel_background: ElementRect {
351            fill: Some((255, 255, 255)),
352            color: None,
353            width: 0.0,
354            visible: true,
355        },
356        panel_border: ElementLine {
357            color: (179, 179, 179), // grey70
358            width: 1.0,
359            visible: true,
360        },
361        panel_grid_major: ElementLine {
362            color: (222, 222, 222), // grey87
363            width: 0.5,
364            visible: true,
365        },
366        panel_grid_minor: ElementLine {
367            color: (222, 222, 222), // grey87, thinner
368            width: 0.25,
369            visible: true,
370        },
371        axis_ticks: ElementLine {
372            color: (179, 179, 179), // grey70
373            width: 0.5,
374            visible: true,
375        },
376        legend_key: ElementRect {
377            fill: Some((255, 255, 255)),
378            color: None,
379            width: 0.0,
380            visible: true,
381        },
382        strip_background: ElementRect {
383            fill: Some((179, 179, 179)), // grey70
384            color: None,
385            width: 0.0,
386            visible: true,
387        },
388        strip_text: ElementText {
389            size: base_size * 0.8,
390            color: (255, 255, 255), // white text on grey70 strip
391            ..Default::default()
392        },
393        ..theme_gray_base(base_size)
394    }
395}
396
397// ─── theme_dark ──────────────────────────────────────────────────
398
399/// Dark theme: white plot background with dark grey50 panel.
400/// Makes colored data pop. Inherits from theme_grey.
401pub fn theme_dark() -> Theme {
402    theme_dark_base(DEFAULT_BASE_SIZE)
403}
404
405/// Dark theme with custom base font size.
406///
407/// Note: ggplot2's theme_dark has a white plot background but dark panel.
408/// Text remains black (inherited from theme_grey).
409pub fn theme_dark_base(base_size: f64) -> Theme {
410    Theme {
411        panel_background: ElementRect {
412            fill: Some((127, 127, 127)), // grey50
413            color: None,
414            width: 0.0,
415            visible: true,
416        },
417        panel_grid_major: ElementLine {
418            color: (107, 107, 107), // ~grey42
419            width: 0.5,
420            visible: true,
421        },
422        panel_grid_minor: ElementLine {
423            color: (107, 107, 107), // ~grey42, thinner
424            width: 0.25,
425            visible: true,
426        },
427        axis_ticks: ElementLine {
428            color: (51, 51, 51), // grey20
429            width: 0.5,
430            visible: true,
431        },
432        strip_background: ElementRect {
433            fill: Some((38, 38, 38)), // ~grey15
434            color: None,
435            width: 0.0,
436            visible: true,
437        },
438        strip_text: ElementText {
439            size: base_size * 0.8,
440            color: (230, 230, 230), // grey90
441            ..Default::default()
442        },
443        legend_key: ElementRect {
444            fill: Some((127, 127, 127)), // grey50, matches panel
445            color: None,
446            width: 0.0,
447            visible: true,
448        },
449        ..theme_gray_base(base_size)
450    }
451}
452
453// ─── theme_void ──────────────────────────────────────────────────
454
455/// Void theme: completely blank — no axes, ticks, gridlines, labels, or background.
456/// Canvas for maps or custom visualizations. Legend is retained.
457pub fn theme_void() -> Theme {
458    theme_void_base(DEFAULT_BASE_SIZE)
459}
460
461/// Void theme with custom base font size.
462pub fn theme_void_base(base_size: f64) -> Theme {
463    let (title_size, _, axis_text_size) = text_sizes(base_size);
464    Theme {
465        text: ElementText::blank(),
466        title: ElementText {
467            size: title_size,
468            ..Default::default()
469        },
470        axis_text_x: ElementText::blank(),
471        axis_text_y: ElementText::blank(),
472        axis_title_x: ElementText::blank(),
473        axis_title_y: ElementText::blank(),
474        axis_line: ElementLine::blank(),
475        axis_ticks: ElementLine::blank(),
476        panel_background: ElementRect::blank(),
477        panel_grid_major: ElementLine::blank(),
478        panel_grid_minor: ElementLine::blank(),
479        plot_background: ElementRect::blank(),
480        legend_position: LegendPosition::Right, // ggplot2 keeps legend in void
481        plot_margin: Margin {
482            top: 0.0,
483            right: 0.0,
484            bottom: 0.0,
485            left: 0.0,
486        },
487
488        subtitle: ElementText::blank(),
489        caption: ElementText::blank(),
490        legend_title: ElementText {
491            size: axis_text_size, // rel(0.8)
492            ..Default::default()
493        },
494        legend_text: ElementText {
495            size: axis_text_size, // rel(0.8)
496            ..Default::default()
497        },
498        strip_text: ElementText {
499            size: axis_text_size, // rel(0.8)
500            ..Default::default()
501        },
502
503        axis_line_x: None,
504        axis_line_y: None,
505        axis_ticks_x: None,
506        axis_ticks_y: None,
507        panel_grid_major_x: None,
508        panel_grid_major_y: None,
509        panel_grid_minor_x: None,
510        panel_grid_minor_y: None,
511
512        panel_border: ElementLine::blank(),
513        legend_background: ElementRect::blank(),
514        legend_key: ElementRect::blank(),
515        strip_background: ElementRect::blank(),
516
517        axis_ticks_length: 0.0,
518        axis_text_x_dodge: 1,
519        legend_key_width: 12.0,
520        legend_key_height: 18.0,
521        legend_spacing: 4.0,
522        legend_margin: Margin {
523            top: 0.0,
524            right: 0.0,
525            bottom: 0.0,
526            left: 0.0,
527        },
528        panel_spacing: 0.0,
529        panel_spacing_x: None,
530        panel_spacing_y: None,
531        primary: None,
532    }
533}