plotlars_core/components/
text.rs1use crate::components::Rgb;
2
3#[derive(Clone)]
50pub struct Text {
51 pub content: String,
52 pub font: String,
53 pub size: usize,
54 pub color: Rgb,
55 pub x: f64,
56 pub y: f64,
57}
58
59impl Default for Text {
60 fn default() -> Self {
69 Text {
70 content: String::new(),
71 font: String::new(),
72 size: 12,
73 color: Rgb::default(),
74 x: 0.5,
75 y: 0.9,
76 }
77 }
78}
79
80impl Text {
81 pub fn from(content: impl Into<String>) -> Self {
87 Self {
88 content: content.into(),
89 ..Default::default()
90 }
91 }
92
93 pub fn font(mut self, font: impl Into<String>) -> Self {
99 self.font = font.into();
100 self
101 }
102
103 pub fn size(mut self, size: usize) -> Self {
109 self.size = size;
110 self
111 }
112
113 pub fn color(mut self, color: Rgb) -> Self {
119 self.color = color;
120 self
121 }
122
123 pub fn x(mut self, x: f64) -> Self {
129 self.x = x;
130 self
131 }
132
133 pub fn y(mut self, y: f64) -> Self {
139 self.y = y;
140 self
141 }
142
143 pub fn has_custom_position(&self) -> bool {
144 const EPSILON: f64 = 1e-6;
145 (self.x - 0.5).abs() > EPSILON || (self.y - 0.9).abs() > EPSILON
146 }
147
148 pub fn with_plot_title_defaults(mut self) -> Self {
150 const EPSILON: f64 = 1e-6;
151 let y_is_default = (self.y - 0.9).abs() < EPSILON;
152
153 if y_is_default {
154 self.y = 0.95;
155 }
156
157 self
158 }
159
160 pub fn with_subplot_title_defaults(mut self) -> Self {
162 const EPSILON: f64 = 1e-6;
163 let y_is_default = (self.y - 0.9).abs() < EPSILON;
164 let y_is_plot_default = (self.y - 0.95).abs() < EPSILON;
165
166 if y_is_default || y_is_plot_default {
168 self.y = 1.1;
169 }
170
171 self
172 }
173
174 pub fn with_x_title_defaults(mut self) -> Self {
176 const EPSILON: f64 = 1e-6;
177 let y_is_default = (self.y - 0.9).abs() < EPSILON;
178
179 if y_is_default {
180 self.y = -0.15;
181 }
182
183 self
184 }
185
186 pub fn with_y_title_defaults(mut self) -> Self {
188 const EPSILON: f64 = 1e-6;
189 let x_is_default = (self.x - 0.5).abs() < EPSILON;
190 let y_is_default = (self.y - 0.9).abs() < EPSILON;
191
192 if x_is_default {
193 self.x = -0.08;
194 }
195
196 if y_is_default {
197 self.y = 0.5;
198 }
199
200 self
201 }
202
203 pub fn with_x_title_defaults_for_annotation(mut self) -> Self {
207 const EPSILON: f64 = 1e-6;
208 let x_is_default = (self.x - 0.5).abs() < EPSILON;
209 let y_is_default = (self.y - 0.9).abs() < EPSILON;
210
211 if x_is_default {
212 self.x = 0.5;
213 }
214
215 if y_is_default {
216 self.y = -0.15;
217 }
218
219 self
220 }
221
222 pub fn with_y_title_defaults_for_annotation(mut self) -> Self {
226 const EPSILON: f64 = 1e-6;
227 let x_is_default = (self.x - 0.5).abs() < EPSILON;
228 let y_is_default = (self.y - 0.9).abs() < EPSILON;
229
230 if x_is_default {
231 self.x = -0.08;
232 }
233
234 if y_is_default {
235 self.y = 0.5;
236 }
237
238 self
239 }
240}
241
242impl From<&str> for Text {
243 fn from(content: &str) -> Self {
244 Self::from(content.to_string())
245 }
246}
247
248impl From<String> for Text {
249 fn from(content: String) -> Self {
250 Self::from(content)
251 }
252}
253
254impl From<&String> for Text {
255 fn from(content: &String) -> Self {
256 Self::from(content)
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263 use crate::components::Rgb;
264
265 fn assert_float_eq(a: f64, b: f64) {
266 assert!(
267 (a - b).abs() < 1e-6,
268 "expected {b}, got {a} (diff {})",
269 (a - b).abs()
270 );
271 }
272
273 #[test]
274 fn test_from_str() {
275 let t = Text::from("hello");
276 assert_eq!(t.content, "hello");
277 assert_eq!(t.font, "");
278 assert_eq!(t.size, 12);
279 assert_eq!(t.color.0, 0);
280 assert_eq!(t.color.1, 0);
281 assert_eq!(t.color.2, 0);
282 assert_float_eq(t.x, 0.5);
283 assert_float_eq(t.y, 0.9);
284 }
285
286 #[test]
287 fn test_from_string() {
288 let t: Text = String::from("world").into();
289 assert_eq!(t.content, "world");
290 }
291
292 #[test]
293 fn test_from_ref_string() {
294 let s = String::from("ref");
295 let t: Text = (&s).into();
296 assert_eq!(t.content, "ref");
297 }
298
299 #[test]
300 fn test_default_values() {
301 let t = Text::default();
302 assert_eq!(t.content, "");
303 assert_eq!(t.font, "");
304 assert_eq!(t.size, 12);
305 assert_eq!(t.color.0, 0);
306 assert_eq!(t.color.1, 0);
307 assert_eq!(t.color.2, 0);
308 assert_float_eq(t.x, 0.5);
309 assert_float_eq(t.y, 0.9);
310 }
311
312 #[test]
313 fn test_font() {
314 let t = Text::from("x").font("Arial");
315 assert_eq!(t.font, "Arial");
316 assert_eq!(t.size, 12);
317 }
318
319 #[test]
320 fn test_size() {
321 let t = Text::from("x").size(20);
322 assert_eq!(t.size, 20);
323 assert_eq!(t.font, "");
324 }
325
326 #[test]
327 fn test_color() {
328 let t = Text::from("x").color(Rgb(1, 2, 3));
329 assert_eq!(t.color.0, 1);
330 assert_eq!(t.color.1, 2);
331 assert_eq!(t.color.2, 3);
332 }
333
334 #[test]
335 fn test_x() {
336 let t = Text::from("x").x(0.1);
337 assert_float_eq(t.x, 0.1);
338 assert_float_eq(t.y, 0.9);
339 }
340
341 #[test]
342 fn test_y() {
343 let t = Text::from("x").y(0.2);
344 assert_float_eq(t.y, 0.2);
345 assert_float_eq(t.x, 0.5);
346 }
347
348 #[test]
349 fn test_builder_chaining() {
350 let t = Text::from("chained")
351 .font("Courier")
352 .size(24)
353 .color(Rgb(10, 20, 30))
354 .x(0.3)
355 .y(0.7);
356 assert_eq!(t.content, "chained");
357 assert_eq!(t.font, "Courier");
358 assert_eq!(t.size, 24);
359 assert_eq!(t.color.0, 10);
360 assert_eq!(t.color.1, 20);
361 assert_eq!(t.color.2, 30);
362 assert_float_eq(t.x, 0.3);
363 assert_float_eq(t.y, 0.7);
364 }
365
366 #[test]
367 fn test_has_custom_position_default() {
368 let t = Text::default();
369 assert!(!t.has_custom_position());
370 }
371
372 #[test]
373 fn test_has_custom_position_x_changed() {
374 let t = Text::from("x").x(0.3);
375 assert!(t.has_custom_position());
376 }
377
378 #[test]
379 fn test_has_custom_position_epsilon() {
380 let t = Text::from("x").x(0.5 + 1e-7);
381 assert!(!t.has_custom_position());
382 }
383
384 #[test]
385 fn test_with_plot_title_defaults() {
386 let t = Text::from("title").with_plot_title_defaults();
387 assert_float_eq(t.y, 0.95);
388
389 let t2 = Text::from("title").y(0.7).with_plot_title_defaults();
390 assert_float_eq(t2.y, 0.7);
391 }
392
393 #[test]
394 fn test_with_x_title_defaults() {
395 let t = Text::from("x").with_x_title_defaults();
396 assert_float_eq(t.y, -0.15);
397 }
398
399 #[test]
400 fn test_with_y_title_defaults() {
401 let t = Text::from("y").with_y_title_defaults();
402 assert_float_eq(t.x, -0.08);
403 assert_float_eq(t.y, 0.5);
404
405 let t2 = Text::from("y").x(0.2).y(0.3).with_y_title_defaults();
406 assert_float_eq(t2.x, 0.2);
407 assert_float_eq(t2.y, 0.3);
408 }
409}