ggplot_rs/theme/elements.rs
1/// Text element styling.
2#[derive(Clone, Debug)]
3pub struct ElementText {
4 pub family: String,
5 /// Font face (R's `element_text(face = ...)`).
6 pub face: crate::render::backend::FontFace,
7 pub size: f64,
8 pub color: (u8, u8, u8),
9 pub angle: f64,
10 pub hjust: f64,
11 pub vjust: f64,
12 pub visible: bool,
13}
14
15impl ElementText {
16 /// Create an invisible (blank) text element.
17 pub fn blank() -> Self {
18 ElementText {
19 visible: false,
20 ..Default::default()
21 }
22 }
23}
24
25impl Default for ElementText {
26 fn default() -> Self {
27 ElementText {
28 family: "sans-serif".to_string(),
29 face: crate::render::backend::FontFace::Plain,
30 size: 12.0,
31 color: (0, 0, 0),
32 angle: 0.0,
33 hjust: 0.5,
34 vjust: 0.5,
35 visible: true,
36 }
37 }
38}
39
40/// Line element styling.
41#[derive(Clone, Debug)]
42pub struct ElementLine {
43 pub color: (u8, u8, u8),
44 pub width: f64,
45 pub visible: bool,
46 /// Line style (R's `element_line(linetype = ...)`).
47 pub linetype: crate::render::backend::Linetype,
48}
49
50impl ElementLine {
51 /// Create an invisible (blank) line element.
52 pub fn blank() -> Self {
53 ElementLine {
54 visible: false,
55 ..Default::default()
56 }
57 }
58}
59
60impl Default for ElementLine {
61 fn default() -> Self {
62 ElementLine {
63 color: (0, 0, 0),
64 width: 1.0,
65 visible: true,
66 linetype: crate::render::backend::Linetype::Solid,
67 }
68 }
69}
70
71/// Rectangle element styling.
72#[derive(Clone, Debug)]
73pub struct ElementRect {
74 pub fill: Option<(u8, u8, u8)>,
75 pub color: Option<(u8, u8, u8)>,
76 pub width: f64,
77 pub visible: bool,
78}
79
80impl ElementRect {
81 /// Create an invisible (blank) rect element.
82 pub fn blank() -> Self {
83 ElementRect {
84 visible: false,
85 ..Default::default()
86 }
87 }
88}
89
90impl Default for ElementRect {
91 fn default() -> Self {
92 ElementRect {
93 fill: Some((255, 255, 255)),
94 color: None,
95 width: 0.0,
96 visible: true,
97 }
98 }
99}
100
101/// Geom "ink"/"paper"/"accent" palette (ggplot2 4.0's `element_geom`,
102/// B2-7): recolours a geom's *built-in default* colour without touching
103/// every geom's own color/fill field or needing a mapped/explicit colour
104/// to be set first. `None` everywhere (the default) changes nothing —
105/// byte-identical output. Marked `#[non_exhaustive]` so a later field can
106/// be added without an `..Default::default()` break.
107///
108/// A default [`Geom::apply_element_geom`](crate::geom::Geom::apply_element_geom)
109/// is a no-op; a geom that honours this overrides it, replacing only
110/// fields still at their own built-in default — called once per layer,
111/// before the brand [`Theme::primary`](super::Theme::primary) colour is
112/// applied, and (like the brand colour) only for a layer that was *not*
113/// configured explicitly via `geom_*_with(...)`: that is the actual guard
114/// against touching an explicit colour (an `explicit_style` gate in the
115/// build pipeline), not a comparison of the field's *value* against the
116/// hard-coded default, which can't otherwise tell an explicitly-set black
117/// apart from a black nobody touched.
118///
119/// **Geoms covered (0.21.0):** `GeomPoint`, `GeomLine`, `GeomPath`,
120/// `GeomText`, `GeomLabel` and `GeomBoxplot` (`ink` for a black/
121/// `(50, 50, 50)` line, text or outline; `paper` for a white fill) and
122/// `GeomSmooth` (`accent` for its default blue line and ribbon fill) — plus
123/// the legend key swatch colour, which reads `ink` directly rather than
124/// going through this trait method. Every other geom (segment, curve,
125/// reference lines, errorbar/linerange/pointrange, step, rect/tile
126/// outlines, density/violin/area outlines, crossbar, …) does not yet
127/// honour `ElementGeom` and keeps its hard-coded colour regardless —
128/// tracked at sipemu/ggplot-rs#142.
129#[non_exhaustive]
130#[derive(Clone, Debug, Default)]
131pub struct ElementGeom {
132 /// Replaces a geom's built-in black / `(50, 50, 50)` line, text or
133 /// outline colour (including the legend key colour).
134 pub ink: Option<(u8, u8, u8)>,
135 /// Replaces a geom's built-in white fill.
136 pub paper: Option<(u8, u8, u8)>,
137 /// Replaces the smooth line's built-in colour.
138 pub accent: Option<(u8, u8, u8)>,
139 /// Font family — the middle tier of B2-6's resolution order
140 /// (a geom's own `fontfamily`, then this, then `theme.text.family`).
141 pub family: Option<String>,
142}
143
144impl ElementGeom {
145 /// Builder for [`ink`](Self::ink) — `#[non_exhaustive]` blocks a
146 /// cross-crate struct literal even with `..Default::default()`, so
147 /// each field gets one of these.
148 pub fn with_ink(mut self, color: (u8, u8, u8)) -> Self {
149 self.ink = Some(color);
150 self
151 }
152
153 /// Builder for [`paper`](Self::paper).
154 pub fn with_paper(mut self, color: (u8, u8, u8)) -> Self {
155 self.paper = Some(color);
156 self
157 }
158
159 /// Builder for [`accent`](Self::accent).
160 pub fn with_accent(mut self, color: (u8, u8, u8)) -> Self {
161 self.accent = Some(color);
162 self
163 }
164
165 /// Builder for [`family`](Self::family).
166 pub fn with_family(mut self, family: &str) -> Self {
167 self.family = Some(family.to_string());
168 self
169 }
170}
171
172#[cfg(test)]
173mod element_geom_tests {
174 use super::ElementGeom;
175
176 #[test]
177 fn builders_set_every_field() {
178 let el = ElementGeom::default()
179 .with_ink((1, 2, 3))
180 .with_paper((4, 5, 6))
181 .with_accent((7, 8, 9))
182 .with_family("Georgia");
183 assert_eq!(el.ink, Some((1, 2, 3)));
184 assert_eq!(el.paper, Some((4, 5, 6)));
185 assert_eq!(el.accent, Some((7, 8, 9)));
186 assert_eq!(el.family, Some("Georgia".to_string()));
187 }
188}