1use crate::primitives::{Color, FontWeight};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21#[non_exhaustive]
22pub enum LineStyle {
23 Solid,
25 Dashed,
27 Dotted,
29 DashDot,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39#[non_exhaustive]
40pub enum Marker {
41 Circle,
43 Square,
45 Triangle,
47 Diamond,
49 Plus,
51 Cross,
53 Star,
55 Point,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65#[non_exhaustive]
66pub enum Loc {
67 Best,
69 UpperRight,
71 UpperLeft,
73 LowerLeft,
75 LowerRight,
77 Right,
79 CenterLeft,
81 CenterRight,
83 LowerCenter,
85 UpperCenter,
87 Center,
89}
90
91#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
97pub enum GridAxis {
98 X,
100 Y,
102 #[default]
104 Both,
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113pub enum TickDirection {
114 Outward,
116 Inward,
118}
119
120#[derive(Debug, Clone)]
130pub struct Theme {
131 pub figure_background: Color,
133 pub axes_background: Color,
135
136 pub grid_color: Color,
139 pub grid_width: f64,
141 pub show_grid: bool,
143
144 pub spine_color: Color,
147 pub spine_width: f64,
149 pub show_top_spine: bool,
151 pub show_right_spine: bool,
153 pub show_bottom_spine: bool,
155 pub show_left_spine: bool,
157
158 pub tick_color: Color,
161 pub tick_length: f64,
163 pub tick_direction: TickDirection,
165 pub tick_label_size: f64,
167
168 pub axis_label_size: f64,
171 pub title_size: f64,
173 pub title_weight: FontWeight,
175 pub text_color: Color,
177
178 pub line_width: f64,
181 pub marker_size: f64,
183 pub marker_alpha: f64,
185
186 pub color_cycle: Vec<Color>,
189
190 pub font_family: Option<String>,
194}
195
196const TABLEAU_10: [Color; 10] = Color::TABLEAU_10;
202
203impl Default for Theme {
208 fn default() -> Self {
219 let spine = Color::rgb(0x33, 0x33, 0x33);
220
221 Self {
222 figure_background: Color::WHITE,
223 axes_background: Color::WHITE,
224
225 grid_color: Color::rgb(0xE6, 0xE6, 0xE6),
226 grid_width: 1.0,
227 show_grid: true,
228
229 spine_color: spine,
230 spine_width: 1.0,
231 show_top_spine: false,
232 show_right_spine: false,
233 show_bottom_spine: true,
234 show_left_spine: true,
235
236 tick_color: spine,
237 tick_length: 4.0,
238 tick_direction: TickDirection::Outward,
239 tick_label_size: 9.0,
240
241 axis_label_size: 11.0,
242 title_size: 14.0,
243 title_weight: FontWeight::Bold,
244 text_color: spine,
245
246 line_width: 1.5,
247 marker_size: 6.0,
248 marker_alpha: 0.8,
249
250 color_cycle: TABLEAU_10.to_vec(),
251
252 font_family: None,
253 }
254 }
255}
256
257impl Theme {
262 pub fn dark() -> Self {
266 let bg = Color::rgb(0x1C, 0x1C, 0x1C);
267 let text = Color::rgb(0xE0, 0xE0, 0xE0);
268 let grid = Color::rgb(0x3A, 0x3A, 0x3A);
269 let spine = Color::rgb(0x55, 0x55, 0x55);
270
271 let cycle = vec![
273 Color::rgb(0x00, 0xD4, 0xFF), Color::rgb(0xFF, 0x6F, 0x61), Color::rgb(0x7B, 0xED, 0x72), Color::rgb(0xFF, 0xA6, 0x00), Color::rgb(0xD1, 0x7D, 0xFF), Color::rgb(0xFF, 0xE1, 0x00), Color::rgb(0x00, 0xFF, 0xAB), Color::rgb(0xFF, 0x4D, 0xA6), Color::rgb(0x48, 0xBF, 0xE3), Color::rgb(0xE8, 0xE8, 0xE8), ];
284
285 Self {
286 figure_background: bg,
287 axes_background: bg,
288
289 grid_color: grid,
290 grid_width: 1.0,
291 show_grid: true,
292
293 spine_color: spine,
294 spine_width: 1.0,
295 show_top_spine: false,
296 show_right_spine: false,
297 show_bottom_spine: true,
298 show_left_spine: true,
299
300 tick_color: text,
301 tick_length: 4.0,
302 tick_direction: TickDirection::Outward,
303 tick_label_size: 9.0,
304
305 axis_label_size: 11.0,
306 title_size: 14.0,
307 title_weight: FontWeight::Bold,
308 text_color: text,
309
310 line_width: 1.5,
311 marker_size: 6.0,
312 marker_alpha: 0.9,
313
314 color_cycle: cycle,
315
316 font_family: None,
317 }
318 }
319
320 pub fn seaborn() -> Self {
325 let text = Color::rgb(0x33, 0x33, 0x33);
326 let axes_bg = Color::rgb(0xEA, 0xEA, 0xF2);
327
328 Self {
329 figure_background: Color::WHITE,
330 axes_background: axes_bg,
331
332 grid_color: Color::WHITE,
333 grid_width: 1.0,
334 show_grid: true,
335
336 spine_color: Color::rgb(0xCC, 0xCC, 0xCC),
337 spine_width: 1.0,
338 show_top_spine: false,
339 show_right_spine: false,
340 show_bottom_spine: true,
341 show_left_spine: true,
342
343 tick_color: text,
344 tick_length: 0.0, tick_direction: TickDirection::Outward,
346 tick_label_size: 9.0,
347
348 axis_label_size: 11.0,
349 title_size: 14.0,
350 title_weight: FontWeight::Bold,
351 text_color: text,
352
353 line_width: 1.5,
354 marker_size: 6.0,
355 marker_alpha: 0.8,
356
357 color_cycle: TABLEAU_10.to_vec(),
358
359 font_family: None,
360 }
361 }
362
363 pub fn ggplot() -> Self {
369 let panel = Color::rgb(0xE5, 0xE5, 0xE5);
370 let text = Color::rgb(0x30, 0x30, 0x30);
371
372 let cycle = vec![
374 Color::rgb(0xF8, 0x76, 0x6D), Color::rgb(0xA3, 0xA5, 0x00), Color::rgb(0x00, 0xBA, 0x38), Color::rgb(0x00, 0xBF, 0xC4), Color::rgb(0x61, 0x9C, 0xFF), Color::rgb(0xF5, 0x64, 0xE3), Color::rgb(0xFF, 0x64, 0xB0), Color::rgb(0xB7, 0x9F, 0x00), ];
383
384 Self {
385 figure_background: Color::WHITE,
386 axes_background: panel,
387
388 grid_color: Color::WHITE,
389 grid_width: 1.0,
390 show_grid: true,
391
392 spine_color: Color::WHITE,
394 spine_width: 0.0,
395 show_top_spine: false,
396 show_right_spine: false,
397 show_bottom_spine: false,
398 show_left_spine: false,
399
400 tick_color: text,
401 tick_length: 0.0, tick_direction: TickDirection::Outward,
403 tick_label_size: 9.0,
404
405 axis_label_size: 11.0,
406 title_size: 14.0,
407 title_weight: FontWeight::Bold,
408 text_color: text,
409
410 line_width: 1.0,
411 marker_size: 5.0,
412 marker_alpha: 1.0,
413
414 color_cycle: cycle,
415
416 font_family: None,
417 }
418 }
419
420 pub fn publication() -> Self {
427 let ink = Color::rgb(0x1A, 0x1A, 0x1A);
428
429 Self {
430 figure_background: Color::WHITE,
431 axes_background: Color::WHITE,
432
433 grid_color: Color::rgb(0xD0, 0xD0, 0xD0),
434 grid_width: 0.5,
435 show_grid: false,
436
437 spine_color: ink,
438 spine_width: 0.5,
439 show_top_spine: true,
440 show_right_spine: true,
441 show_bottom_spine: true,
442 show_left_spine: true,
443
444 tick_color: ink,
445 tick_length: 3.0,
446 tick_direction: TickDirection::Inward,
447 tick_label_size: 8.0,
448
449 axis_label_size: 10.0,
450 title_size: 12.0,
451 title_weight: FontWeight::Bold,
452 text_color: ink,
453
454 line_width: 1.0,
455 marker_size: 4.0,
456 marker_alpha: 1.0,
457
458 color_cycle: TABLEAU_10.to_vec(),
459
460 font_family: Some("serif".to_string()),
461 }
462 }
463}
464
465#[cfg(test)]
470mod tests {
471 use super::*;
472
473 #[test]
474 fn default_theme_background_is_white() {
475 let t = Theme::default();
476 assert_eq!(t.figure_background, Color::WHITE);
477 assert_eq!(t.axes_background, Color::WHITE);
478 }
479
480 #[test]
481 fn default_theme_despine_look() {
482 let t = Theme::default();
483 assert!(!t.show_top_spine);
484 assert!(!t.show_right_spine);
485 assert!(t.show_bottom_spine);
486 assert!(t.show_left_spine);
487 }
488
489 #[test]
490 fn default_theme_grid() {
491 let t = Theme::default();
492 assert_eq!(t.grid_color, Color::rgb(0xE6, 0xE6, 0xE6));
493 assert!((t.grid_width - 1.0).abs() < f64::EPSILON);
494 assert!(t.show_grid);
495 }
496
497 #[test]
498 fn default_theme_spines() {
499 let t = Theme::default();
500 let expected = Color::rgb(0x33, 0x33, 0x33);
501 assert_eq!(t.spine_color, expected);
502 assert!((t.spine_width - 1.0).abs() < f64::EPSILON);
503 }
504
505 #[test]
506 fn default_theme_ticks() {
507 let t = Theme::default();
508 assert_eq!(t.tick_color, Color::rgb(0x33, 0x33, 0x33));
509 assert!((t.tick_length - 4.0).abs() < f64::EPSILON);
510 assert_eq!(t.tick_direction, TickDirection::Outward);
511 }
512
513 #[test]
514 fn default_theme_font_sizes() {
515 let t = Theme::default();
516 assert!((t.tick_label_size - 9.0).abs() < f64::EPSILON);
517 assert!((t.axis_label_size - 11.0).abs() < f64::EPSILON);
518 assert!((t.title_size - 14.0).abs() < f64::EPSILON);
519 assert_eq!(t.title_weight, FontWeight::Bold);
520 }
521
522 #[test]
523 fn default_theme_text_color() {
524 let t = Theme::default();
525 assert_eq!(t.text_color, Color::rgb(0x33, 0x33, 0x33));
526 }
527
528 #[test]
529 fn default_theme_data_defaults() {
530 let t = Theme::default();
531 assert!((t.line_width - 1.5).abs() < f64::EPSILON);
532 assert!((t.marker_size - 6.0).abs() < f64::EPSILON);
533 assert!((t.marker_alpha - 0.8).abs() < f64::EPSILON);
534 }
535
536 #[test]
537 fn default_theme_tableau_10_cycle() {
538 let t = Theme::default();
539 assert_eq!(t.color_cycle.len(), 10);
540 assert_eq!(t.color_cycle[0], Color::TAB_BLUE);
541 assert_eq!(t.color_cycle[9], Color::TAB_CYAN);
542 }
543
544 #[test]
545 fn dark_theme_has_dark_background() {
546 let t = Theme::dark();
547 assert_eq!(t.figure_background, Color::rgb(0x1C, 0x1C, 0x1C));
548 assert_eq!(t.axes_background, Color::rgb(0x1C, 0x1C, 0x1C));
549 }
550
551 #[test]
552 fn dark_theme_light_text() {
553 let t = Theme::dark();
554 assert_eq!(t.text_color, Color::rgb(0xE0, 0xE0, 0xE0));
555 }
556
557 #[test]
558 fn dark_theme_neon_cycle() {
559 let t = Theme::dark();
560 assert_eq!(t.color_cycle.len(), 10);
561 assert_eq!(t.color_cycle[0], Color::rgb(0x00, 0xD4, 0xFF));
563 }
564
565 #[test]
566 fn seaborn_theme_tinted_face() {
567 let t = Theme::seaborn();
568 assert_eq!(t.axes_background, Color::rgb(0xEA, 0xEA, 0xF2));
569 }
570
571 #[test]
572 fn seaborn_theme_white_grid() {
573 let t = Theme::seaborn();
574 assert_eq!(t.grid_color, Color::WHITE);
575 assert!(t.show_grid);
576 }
577
578 #[test]
579 fn ggplot_theme_grey_panel() {
580 let t = Theme::ggplot();
581 assert_eq!(t.axes_background, Color::rgb(0xE5, 0xE5, 0xE5));
582 }
583
584 #[test]
585 fn ggplot_theme_white_grid() {
586 let t = Theme::ggplot();
587 assert_eq!(t.grid_color, Color::WHITE);
588 assert!(t.show_grid);
589 }
590
591 #[test]
592 fn ggplot_theme_no_spines() {
593 let t = Theme::ggplot();
594 assert!(!t.show_top_spine);
595 assert!(!t.show_right_spine);
596 assert!(!t.show_bottom_spine);
597 assert!(!t.show_left_spine);
598 }
599
600 #[test]
601 fn ggplot_theme_palette() {
602 let t = Theme::ggplot();
603 assert_eq!(t.color_cycle.len(), 8);
604 assert_eq!(t.color_cycle[0], Color::rgb(0xF8, 0x76, 0x6D));
605 }
606
607 #[test]
608 fn publication_theme_all_spines_visible() {
609 let t = Theme::publication();
610 assert!(t.show_top_spine);
611 assert!(t.show_right_spine);
612 assert!(t.show_bottom_spine);
613 assert!(t.show_left_spine);
614 }
615
616 #[test]
617 fn publication_theme_no_grid() {
618 let t = Theme::publication();
619 assert!(!t.show_grid);
620 }
621
622 #[test]
623 fn publication_theme_thin_spines() {
624 let t = Theme::publication();
625 assert!((t.spine_width - 0.5).abs() < f64::EPSILON);
626 }
627
628 #[test]
629 fn publication_theme_inward_ticks() {
630 let t = Theme::publication();
631 assert_eq!(t.tick_direction, TickDirection::Inward);
632 }
633
634 #[test]
635 fn publication_theme_serif_font() {
636 let t = Theme::publication();
637 assert_eq!(t.font_family, Some("serif".to_string()));
638 }
639
640 #[test]
641 fn publication_theme_white_background() {
642 let t = Theme::publication();
643 assert_eq!(t.figure_background, Color::WHITE);
644 assert_eq!(t.axes_background, Color::WHITE);
645 }
646
647 #[test]
648 fn grid_axis_default_is_both() {
649 assert_eq!(GridAxis::default(), GridAxis::Both);
650 }
651}