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        legend_key_width: 12.0,
162        legend_key_height: 18.0,
163        legend_spacing: 4.0,
164        legend_margin: Margin {
165            top: 10.0,
166            right: 15.0,
167            bottom: 10.0,
168            left: 10.0,
169        },
170        panel_spacing: half_line,
171        panel_spacing_x: None,
172        panel_spacing_y: None,
173        primary: None,
174    }
175}
176
177// ─── theme_bw ────────────────────────────────────────────────────
178
179/// Black and white theme (default base size).
180pub fn theme_bw() -> Theme {
181    theme_bw_base(DEFAULT_BASE_SIZE)
182}
183
184/// Black and white theme with custom base font size.
185///
186/// Inherits from theme_grey. White panel with grey20 border,
187/// grey92 gridlines on white background.
188pub fn theme_bw_base(base_size: f64) -> Theme {
189    Theme {
190        panel_background: ElementRect {
191            fill: Some((255, 255, 255)),
192            color: None,
193            width: 0.0,
194            visible: true,
195        },
196        panel_border: ElementLine {
197            color: (51, 51, 51), // grey20
198            width: 1.0,
199            visible: true,
200        },
201        panel_grid_major: ElementLine {
202            color: (235, 235, 235), // grey92
203            width: 0.5,
204            visible: true,
205        },
206        panel_grid_minor: ElementLine {
207            color: (235, 235, 235), // grey92, thinner
208            width: 0.25,
209            visible: true,
210        },
211        strip_background: ElementRect {
212            fill: Some((217, 217, 217)), // grey85
213            color: Some((51, 51, 51)),   // grey20
214            width: 0.5,
215            visible: true,
216        },
217        legend_key: ElementRect {
218            fill: Some((255, 255, 255)), // white
219            color: None,
220            width: 0.0,
221            visible: true,
222        },
223        ..theme_gray_base(base_size)
224    }
225}
226
227// ─── theme_minimal ───────────────────────────────────────────────
228
229/// Minimal theme with no panel background (default base size).
230pub fn theme_minimal() -> Theme {
231    theme_minimal_base(DEFAULT_BASE_SIZE)
232}
233
234/// Minimal theme with no backgrounds. Inherits from theme_bw.
235pub fn theme_minimal_base(base_size: f64) -> Theme {
236    Theme {
237        axis_ticks: ElementLine::blank(),
238        panel_background: ElementRect::blank(),
239        panel_border: ElementLine::blank(),
240        panel_grid_major: ElementLine {
241            color: (235, 235, 235), // grey92 (from bw)
242            width: 0.5,
243            visible: true,
244        },
245        panel_grid_minor: ElementLine {
246            color: (235, 235, 235),
247            width: 0.25,
248            visible: true,
249        },
250        plot_background: ElementRect::blank(),
251        legend_background: ElementRect::blank(),
252        legend_key: ElementRect::blank(),
253        strip_background: ElementRect::blank(),
254        ..theme_bw_base(base_size)
255    }
256}
257
258// ─── theme_classic ───────────────────────────────────────────────
259
260/// Classic theme: white background, no gridlines, L-shaped axis lines only.
261/// Traditional academic/publication style. Inherits from theme_bw.
262pub fn theme_classic() -> Theme {
263    theme_classic_base(DEFAULT_BASE_SIZE)
264}
265
266/// Classic theme with custom base font size.
267pub fn theme_classic_base(base_size: f64) -> Theme {
268    Theme {
269        panel_border: ElementLine::blank(),
270        panel_grid_major: ElementLine::blank(),
271        panel_grid_minor: ElementLine::blank(),
272        axis_line: ElementLine {
273            color: (0, 0, 0),
274            width: 0.5,
275            visible: true,
276        },
277        axis_ticks: ElementLine {
278            color: (0, 0, 0),
279            width: 0.5,
280            visible: true,
281        },
282        strip_background: ElementRect {
283            fill: Some((255, 255, 255)),
284            color: Some((0, 0, 0)),
285            width: 1.0,
286            visible: true,
287        },
288        ..theme_bw_base(base_size)
289    }
290}
291
292// ─── theme_linedraw ──────────────────────────────────────────────
293
294/// Linedraw theme: white background, black panel border, very thin black gridlines.
295/// Technical drawing aesthetic. Inherits from theme_bw.
296pub fn theme_linedraw() -> Theme {
297    theme_linedraw_base(DEFAULT_BASE_SIZE)
298}
299
300/// Linedraw theme with custom base font size.
301pub fn theme_linedraw_base(base_size: f64) -> Theme {
302    Theme {
303        panel_border: ElementLine {
304            color: (0, 0, 0),
305            width: 1.0,
306            visible: true,
307        },
308        panel_grid_major: ElementLine {
309            color: (0, 0, 0), // black, very thin
310            width: 0.1,
311            visible: true,
312        },
313        panel_grid_minor: ElementLine {
314            color: (0, 0, 0), // black, extremely thin
315            width: 0.05,
316            visible: true,
317        },
318        axis_ticks: ElementLine {
319            color: (0, 0, 0),
320            width: 0.5,
321            visible: true,
322        },
323        strip_background: ElementRect {
324            fill: Some((0, 0, 0)), // black
325            color: None,
326            width: 0.0,
327            visible: true,
328        },
329        strip_text: ElementText {
330            size: base_size * 0.8,
331            color: (255, 255, 255), // white text on black strip
332            ..Default::default()
333        },
334        ..theme_bw_base(base_size)
335    }
336}
337
338// ─── theme_light ─────────────────────────────────────────────────
339
340/// Light theme: white background, light gray panel border and gridlines.
341/// Softer version with grey70 accents. Inherits from theme_grey.
342pub fn theme_light() -> Theme {
343    theme_light_base(DEFAULT_BASE_SIZE)
344}
345
346/// Light theme with custom base font size.
347pub fn theme_light_base(base_size: f64) -> Theme {
348    Theme {
349        panel_background: ElementRect {
350            fill: Some((255, 255, 255)),
351            color: None,
352            width: 0.0,
353            visible: true,
354        },
355        panel_border: ElementLine {
356            color: (179, 179, 179), // grey70
357            width: 1.0,
358            visible: true,
359        },
360        panel_grid_major: ElementLine {
361            color: (222, 222, 222), // grey87
362            width: 0.5,
363            visible: true,
364        },
365        panel_grid_minor: ElementLine {
366            color: (222, 222, 222), // grey87, thinner
367            width: 0.25,
368            visible: true,
369        },
370        axis_ticks: ElementLine {
371            color: (179, 179, 179), // grey70
372            width: 0.5,
373            visible: true,
374        },
375        legend_key: ElementRect {
376            fill: Some((255, 255, 255)),
377            color: None,
378            width: 0.0,
379            visible: true,
380        },
381        strip_background: ElementRect {
382            fill: Some((179, 179, 179)), // grey70
383            color: None,
384            width: 0.0,
385            visible: true,
386        },
387        strip_text: ElementText {
388            size: base_size * 0.8,
389            color: (255, 255, 255), // white text on grey70 strip
390            ..Default::default()
391        },
392        ..theme_gray_base(base_size)
393    }
394}
395
396// ─── theme_dark ──────────────────────────────────────────────────
397
398/// Dark theme: white plot background with dark grey50 panel.
399/// Makes colored data pop. Inherits from theme_grey.
400pub fn theme_dark() -> Theme {
401    theme_dark_base(DEFAULT_BASE_SIZE)
402}
403
404/// Dark theme with custom base font size.
405///
406/// Note: ggplot2's theme_dark has a white plot background but dark panel.
407/// Text remains black (inherited from theme_grey).
408pub fn theme_dark_base(base_size: f64) -> Theme {
409    Theme {
410        panel_background: ElementRect {
411            fill: Some((127, 127, 127)), // grey50
412            color: None,
413            width: 0.0,
414            visible: true,
415        },
416        panel_grid_major: ElementLine {
417            color: (107, 107, 107), // ~grey42
418            width: 0.5,
419            visible: true,
420        },
421        panel_grid_minor: ElementLine {
422            color: (107, 107, 107), // ~grey42, thinner
423            width: 0.25,
424            visible: true,
425        },
426        axis_ticks: ElementLine {
427            color: (51, 51, 51), // grey20
428            width: 0.5,
429            visible: true,
430        },
431        strip_background: ElementRect {
432            fill: Some((38, 38, 38)), // ~grey15
433            color: None,
434            width: 0.0,
435            visible: true,
436        },
437        strip_text: ElementText {
438            size: base_size * 0.8,
439            color: (230, 230, 230), // grey90
440            ..Default::default()
441        },
442        legend_key: ElementRect {
443            fill: Some((127, 127, 127)), // grey50, matches panel
444            color: None,
445            width: 0.0,
446            visible: true,
447        },
448        ..theme_gray_base(base_size)
449    }
450}
451
452// ─── theme_void ──────────────────────────────────────────────────
453
454/// Void theme: completely blank — no axes, ticks, gridlines, labels, or background.
455/// Canvas for maps or custom visualizations. Legend is retained.
456pub fn theme_void() -> Theme {
457    theme_void_base(DEFAULT_BASE_SIZE)
458}
459
460/// Void theme with custom base font size.
461pub fn theme_void_base(base_size: f64) -> Theme {
462    let (title_size, _, axis_text_size) = text_sizes(base_size);
463    Theme {
464        text: ElementText::blank(),
465        title: ElementText {
466            size: title_size,
467            ..Default::default()
468        },
469        axis_text_x: ElementText::blank(),
470        axis_text_y: ElementText::blank(),
471        axis_title_x: ElementText::blank(),
472        axis_title_y: ElementText::blank(),
473        axis_line: ElementLine::blank(),
474        axis_ticks: ElementLine::blank(),
475        panel_background: ElementRect::blank(),
476        panel_grid_major: ElementLine::blank(),
477        panel_grid_minor: ElementLine::blank(),
478        plot_background: ElementRect::blank(),
479        legend_position: LegendPosition::Right, // ggplot2 keeps legend in void
480        plot_margin: Margin {
481            top: 0.0,
482            right: 0.0,
483            bottom: 0.0,
484            left: 0.0,
485        },
486
487        subtitle: ElementText::blank(),
488        caption: ElementText::blank(),
489        legend_title: ElementText {
490            size: axis_text_size, // rel(0.8)
491            ..Default::default()
492        },
493        legend_text: ElementText {
494            size: axis_text_size, // rel(0.8)
495            ..Default::default()
496        },
497        strip_text: ElementText {
498            size: axis_text_size, // rel(0.8)
499            ..Default::default()
500        },
501
502        axis_line_x: None,
503        axis_line_y: None,
504        axis_ticks_x: None,
505        axis_ticks_y: None,
506        panel_grid_major_x: None,
507        panel_grid_major_y: None,
508        panel_grid_minor_x: None,
509        panel_grid_minor_y: None,
510
511        panel_border: ElementLine::blank(),
512        legend_background: ElementRect::blank(),
513        legend_key: ElementRect::blank(),
514        strip_background: ElementRect::blank(),
515
516        axis_ticks_length: 0.0,
517        legend_key_width: 12.0,
518        legend_key_height: 18.0,
519        legend_spacing: 4.0,
520        legend_margin: Margin {
521            top: 0.0,
522            right: 0.0,
523            bottom: 0.0,
524            left: 0.0,
525        },
526        panel_spacing: 0.0,
527        panel_spacing_x: None,
528        panel_spacing_y: None,
529        primary: None,
530    }
531}