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