1use crate::primitives::{Color, FontWeight};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22#[non_exhaustive]
23pub enum LineStyle {
24 Solid,
26 Dashed,
28 Dotted,
30 DashDot,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40#[non_exhaustive]
41pub enum Marker {
42 Circle,
44 Square,
46 Triangle,
48 Diamond,
50 Plus,
52 Cross,
54 Star,
56 Point,
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66#[non_exhaustive]
67pub enum Loc {
68 Best,
70 UpperRight,
72 UpperLeft,
74 LowerLeft,
76 LowerRight,
78 Right,
80 CenterLeft,
82 CenterRight,
84 LowerCenter,
86 UpperCenter,
88 Center,
90}
91
92#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
98pub enum GridAxis {
99 X,
101 Y,
103 #[default]
105 Both,
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
114pub enum TickDirection {
115 Outward,
117 Inward,
119}
120
121#[derive(Debug, Clone)]
131pub struct Theme {
132 pub figure_background: Color,
134 pub axes_background: Color,
136
137 pub grid_color: Color,
140 pub grid_width: f64,
142 pub show_grid: bool,
144
145 pub spine_color: Color,
148 pub spine_width: f64,
150 pub show_top_spine: bool,
152 pub show_right_spine: bool,
154 pub show_bottom_spine: bool,
156 pub show_left_spine: bool,
158
159 pub tick_color: Color,
162 pub tick_length: f64,
164 pub tick_direction: TickDirection,
166 pub tick_label_size: f64,
168
169 pub axis_label_size: f64,
172 pub title_size: f64,
174 pub title_weight: FontWeight,
176 pub text_color: Color,
178
179 pub line_width: f64,
182 pub marker_size: f64,
184 pub marker_alpha: f64,
186
187 pub color_cycle: Vec<Color>,
190
191 pub font_family: Option<String>,
195}
196
197const TABLEAU_10: [Color; 10] = Color::TABLEAU_10;
203
204impl Default for Theme {
209 fn default() -> Self {
220 let spine = Color::rgb(0x33, 0x33, 0x33);
221
222 Self {
223 figure_background: Color::WHITE,
224 axes_background: Color::WHITE,
225
226 grid_color: Color::rgb(0xE6, 0xE6, 0xE6),
227 grid_width: 1.0,
228 show_grid: true,
229
230 spine_color: spine,
231 spine_width: 1.0,
232 show_top_spine: false,
233 show_right_spine: false,
234 show_bottom_spine: true,
235 show_left_spine: true,
236
237 tick_color: spine,
238 tick_length: 4.0,
239 tick_direction: TickDirection::Outward,
240 tick_label_size: 9.0,
241
242 axis_label_size: 11.0,
243 title_size: 14.0,
244 title_weight: FontWeight::Bold,
245 text_color: spine,
246
247 line_width: 1.5,
248 marker_size: 6.0,
249 marker_alpha: 0.8,
250
251 color_cycle: TABLEAU_10.to_vec(),
252
253 font_family: None,
254 }
255 }
256}
257
258impl Theme {
263 pub fn dark() -> Self {
267 let bg = Color::rgb(0x1C, 0x1C, 0x1C);
268 let text = Color::rgb(0xE0, 0xE0, 0xE0);
269 let grid = Color::rgb(0x3A, 0x3A, 0x3A);
270 let spine = Color::rgb(0x55, 0x55, 0x55);
271
272 let cycle = vec![
274 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), ];
285
286 Self {
287 figure_background: bg,
288 axes_background: bg,
289
290 grid_color: grid,
291 grid_width: 1.0,
292 show_grid: true,
293
294 spine_color: spine,
295 spine_width: 1.0,
296 show_top_spine: false,
297 show_right_spine: false,
298 show_bottom_spine: true,
299 show_left_spine: true,
300
301 tick_color: text,
302 tick_length: 4.0,
303 tick_direction: TickDirection::Outward,
304 tick_label_size: 9.0,
305
306 axis_label_size: 11.0,
307 title_size: 14.0,
308 title_weight: FontWeight::Bold,
309 text_color: text,
310
311 line_width: 1.5,
312 marker_size: 6.0,
313 marker_alpha: 0.9,
314
315 color_cycle: cycle,
316
317 font_family: None,
318 }
319 }
320
321 pub fn seaborn() -> Self {
330 let text = Color::rgb(0x33, 0x33, 0x33);
331 let axes_bg = Color::rgb(0xEA, 0xEA, 0xF2);
332
333 let cycle = vec![
335 Color::rgb(0x4C, 0x72, 0xB0), Color::rgb(0xDD, 0x85, 0x52), Color::rgb(0x55, 0xA8, 0x68), Color::rgb(0xC4, 0x4E, 0x52), Color::rgb(0x81, 0x72, 0xB3), Color::rgb(0x93, 0x7A, 0x60), Color::rgb(0xDA, 0x8B, 0xC3), Color::rgb(0x8C, 0x8C, 0x8C), Color::rgb(0xCC, 0xB9, 0x74), Color::rgb(0x64, 0xB5, 0xCD), ];
346
347 Self {
348 figure_background: Color::WHITE,
349 axes_background: axes_bg,
350
351 grid_color: Color::WHITE,
352 grid_width: 1.5,
353 show_grid: true,
354
355 spine_color: Color::rgb(0xCC, 0xCC, 0xCC),
356 spine_width: 1.0,
357 show_top_spine: false,
358 show_right_spine: false,
359 show_bottom_spine: true,
360 show_left_spine: true,
361
362 tick_color: text,
363 tick_length: 0.0, tick_direction: TickDirection::Outward,
365 tick_label_size: 9.0,
366
367 axis_label_size: 11.0,
368 title_size: 14.0,
369 title_weight: FontWeight::Bold,
370 text_color: text,
371
372 line_width: 1.5,
373 marker_size: 6.0,
374 marker_alpha: 0.8,
375
376 color_cycle: cycle,
377
378 font_family: Some("sans-serif".to_string()),
379 }
380 }
381
382 pub fn ggplot() -> Self {
389 let panel = Color::rgb(0xE5, 0xE5, 0xE5);
390 let text = Color::rgb(0x30, 0x30, 0x30);
391 let border = Color::rgb(0x80, 0x80, 0x80);
392
393 let cycle = vec![
395 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), ];
404
405 Self {
406 figure_background: Color::WHITE,
407 axes_background: panel,
408
409 grid_color: Color::WHITE,
410 grid_width: 1.0,
411 show_grid: true,
412
413 spine_color: border,
415 spine_width: 0.5,
416 show_top_spine: true,
417 show_right_spine: true,
418 show_bottom_spine: true,
419 show_left_spine: true,
420
421 tick_color: text,
422 tick_length: 0.0, tick_direction: TickDirection::Outward,
424 tick_label_size: 9.0,
425
426 axis_label_size: 11.0,
427 title_size: 14.0,
428 title_weight: FontWeight::Bold,
429 text_color: text,
430
431 line_width: 1.0,
432 marker_size: 5.0,
433 marker_alpha: 1.0,
434
435 color_cycle: cycle,
436
437 font_family: None,
438 }
439 }
440
441 pub fn publication() -> Self {
451 let ink = Color::rgb(0x1A, 0x1A, 0x1A);
452
453 Self {
454 figure_background: Color::WHITE,
455 axes_background: Color::WHITE,
456
457 grid_color: Color::rgb(0xD0, 0xD0, 0xD0),
458 grid_width: 0.5,
459 show_grid: false,
460
461 spine_color: ink,
462 spine_width: 0.5,
463 show_top_spine: true,
464 show_right_spine: true,
465 show_bottom_spine: true,
466 show_left_spine: true,
467
468 tick_color: ink,
469 tick_length: 3.0,
470 tick_direction: TickDirection::Inward,
471 tick_label_size: 8.0,
472
473 axis_label_size: 12.0,
474 title_size: 13.0,
475 title_weight: FontWeight::Bold,
476 text_color: ink,
477
478 line_width: 1.0,
479 marker_size: 4.0,
480 marker_alpha: 1.0,
481
482 color_cycle: TABLEAU_10.to_vec(),
483
484 font_family: Some("serif".to_string()),
485 }
486 }
487
488 pub fn nature() -> Self {
499 let ink = Color::rgb(0x1A, 0x1A, 0x1A);
500
501 let cycle = vec![
503 Color::rgb(0xE6, 0x4B, 0x35), Color::rgb(0x4D, 0xBB, 0xD5), Color::rgb(0x00, 0xA0, 0x87), Color::rgb(0x30, 0x66, 0xBE), Color::rgb(0xF3, 0x9B, 0x7F), Color::rgb(0x87, 0x5F, 0x9A), Color::rgb(0xFE, 0xBE, 0x10), Color::rgb(0x00, 0x72, 0xB2), ];
512
513 Self {
514 figure_background: Color::WHITE,
515 axes_background: Color::WHITE,
516
517 grid_color: Color::rgb(0xDD, 0xDD, 0xDD),
518 grid_width: 0.5,
519 show_grid: false,
520
521 spine_color: ink,
522 spine_width: 0.75,
523 show_top_spine: false,
524 show_right_spine: false,
525 show_bottom_spine: true,
526 show_left_spine: true,
527
528 tick_color: ink,
529 tick_length: 3.0,
530 tick_direction: TickDirection::Outward,
531 tick_label_size: 7.0,
532
533 axis_label_size: 8.0,
534 title_size: 10.0,
535 title_weight: FontWeight::Bold,
536 text_color: ink,
537
538 line_width: 1.0,
539 marker_size: 4.0,
540 marker_alpha: 1.0,
541
542 color_cycle: cycle,
543
544 font_family: Some("sans-serif".to_string()),
545 }
546 }
547
548 pub fn solarized() -> Self {
558 let base03 = Color::rgb(0x00, 0x2B, 0x36); let base02 = Color::rgb(0x07, 0x36, 0x42); let base01 = Color::rgb(0x58, 0x6E, 0x75); let base0 = Color::rgb(0x83, 0x94, 0x96); let base1 = Color::rgb(0x93, 0xA1, 0xA1); let cycle = vec![
566 Color::rgb(0x26, 0x8B, 0xD2), Color::rgb(0xDC, 0x32, 0x2F), Color::rgb(0x85, 0x99, 0x00), Color::rgb(0xB5, 0x89, 0x00), Color::rgb(0x2A, 0xA1, 0x98), Color::rgb(0xD3, 0x36, 0x82), Color::rgb(0xCB, 0x4B, 0x16), Color::rgb(0x6C, 0x71, 0xC4), ];
575
576 Self {
577 figure_background: base03,
578 axes_background: base03,
579
580 grid_color: base02,
581 grid_width: 1.0,
582 show_grid: true,
583
584 spine_color: base01,
585 spine_width: 1.0,
586 show_top_spine: false,
587 show_right_spine: false,
588 show_bottom_spine: true,
589 show_left_spine: true,
590
591 tick_color: base0,
592 tick_length: 4.0,
593 tick_direction: TickDirection::Outward,
594 tick_label_size: 9.0,
595
596 axis_label_size: 11.0,
597 title_size: 14.0,
598 title_weight: FontWeight::Bold,
599 text_color: base1,
600
601 line_width: 1.5,
602 marker_size: 6.0,
603 marker_alpha: 0.9,
604
605 color_cycle: cycle,
606
607 font_family: Some("sans-serif".to_string()),
608 }
609 }
610}
611
612#[cfg(test)]
617mod tests {
618 use super::*;
619
620 #[test]
621 fn default_theme_background_is_white() {
622 let t = Theme::default();
623 assert_eq!(t.figure_background, Color::WHITE);
624 assert_eq!(t.axes_background, Color::WHITE);
625 }
626
627 #[test]
628 fn default_theme_despine_look() {
629 let t = Theme::default();
630 assert!(!t.show_top_spine);
631 assert!(!t.show_right_spine);
632 assert!(t.show_bottom_spine);
633 assert!(t.show_left_spine);
634 }
635
636 #[test]
637 fn default_theme_grid() {
638 let t = Theme::default();
639 assert_eq!(t.grid_color, Color::rgb(0xE6, 0xE6, 0xE6));
640 assert!((t.grid_width - 1.0).abs() < f64::EPSILON);
641 assert!(t.show_grid);
642 }
643
644 #[test]
645 fn default_theme_spines() {
646 let t = Theme::default();
647 let expected = Color::rgb(0x33, 0x33, 0x33);
648 assert_eq!(t.spine_color, expected);
649 assert!((t.spine_width - 1.0).abs() < f64::EPSILON);
650 }
651
652 #[test]
653 fn default_theme_ticks() {
654 let t = Theme::default();
655 assert_eq!(t.tick_color, Color::rgb(0x33, 0x33, 0x33));
656 assert!((t.tick_length - 4.0).abs() < f64::EPSILON);
657 assert_eq!(t.tick_direction, TickDirection::Outward);
658 }
659
660 #[test]
661 fn default_theme_font_sizes() {
662 let t = Theme::default();
663 assert!((t.tick_label_size - 9.0).abs() < f64::EPSILON);
664 assert!((t.axis_label_size - 11.0).abs() < f64::EPSILON);
665 assert!((t.title_size - 14.0).abs() < f64::EPSILON);
666 assert_eq!(t.title_weight, FontWeight::Bold);
667 }
668
669 #[test]
670 fn default_theme_text_color() {
671 let t = Theme::default();
672 assert_eq!(t.text_color, Color::rgb(0x33, 0x33, 0x33));
673 }
674
675 #[test]
676 fn default_theme_data_defaults() {
677 let t = Theme::default();
678 assert!((t.line_width - 1.5).abs() < f64::EPSILON);
679 assert!((t.marker_size - 6.0).abs() < f64::EPSILON);
680 assert!((t.marker_alpha - 0.8).abs() < f64::EPSILON);
681 }
682
683 #[test]
684 fn default_theme_tableau_10_cycle() {
685 let t = Theme::default();
686 assert_eq!(t.color_cycle.len(), 10);
687 assert_eq!(t.color_cycle[0], Color::TAB_BLUE);
688 assert_eq!(t.color_cycle[9], Color::TAB_CYAN);
689 }
690
691 #[test]
692 fn dark_theme_has_dark_background() {
693 let t = Theme::dark();
694 assert_eq!(t.figure_background, Color::rgb(0x1C, 0x1C, 0x1C));
695 assert_eq!(t.axes_background, Color::rgb(0x1C, 0x1C, 0x1C));
696 }
697
698 #[test]
699 fn dark_theme_light_text() {
700 let t = Theme::dark();
701 assert_eq!(t.text_color, Color::rgb(0xE0, 0xE0, 0xE0));
702 }
703
704 #[test]
705 fn dark_theme_neon_cycle() {
706 let t = Theme::dark();
707 assert_eq!(t.color_cycle.len(), 10);
708 assert_eq!(t.color_cycle[0], Color::rgb(0x00, 0xD4, 0xFF));
710 }
711
712 #[test]
713 fn seaborn_theme_tinted_face() {
714 let t = Theme::seaborn();
715 assert_eq!(t.axes_background, Color::rgb(0xEA, 0xEA, 0xF2));
716 }
717
718 #[test]
719 fn seaborn_theme_white_grid_thicker() {
720 let t = Theme::seaborn();
721 assert_eq!(t.grid_color, Color::WHITE);
722 assert!(t.show_grid);
723 assert!((t.grid_width - 1.5).abs() < f64::EPSILON);
724 }
725
726 #[test]
727 fn ggplot_theme_grey_panel() {
728 let t = Theme::ggplot();
729 assert_eq!(t.axes_background, Color::rgb(0xE5, 0xE5, 0xE5));
730 }
731
732 #[test]
733 fn ggplot_theme_white_grid() {
734 let t = Theme::ggplot();
735 assert_eq!(t.grid_color, Color::WHITE);
736 assert!(t.show_grid);
737 }
738
739 #[test]
740 fn ggplot_theme_panel_border() {
741 let t = Theme::ggplot();
742 assert!(t.show_top_spine);
743 assert!(t.show_right_spine);
744 assert!(t.show_bottom_spine);
745 assert!(t.show_left_spine);
746 assert!((t.spine_width - 0.5).abs() < f64::EPSILON);
747 }
748
749 #[test]
750 fn ggplot_theme_palette() {
751 let t = Theme::ggplot();
752 assert_eq!(t.color_cycle.len(), 8);
753 assert_eq!(t.color_cycle[0], Color::rgb(0xF8, 0x76, 0x6D));
754 }
755
756 #[test]
757 fn publication_theme_all_spines_visible() {
758 let t = Theme::publication();
759 assert!(t.show_top_spine);
760 assert!(t.show_right_spine);
761 assert!(t.show_bottom_spine);
762 assert!(t.show_left_spine);
763 }
764
765 #[test]
766 fn publication_theme_no_grid() {
767 let t = Theme::publication();
768 assert!(!t.show_grid);
769 }
770
771 #[test]
772 fn publication_theme_thin_spines() {
773 let t = Theme::publication();
774 assert!((t.spine_width - 0.5).abs() < f64::EPSILON);
775 }
776
777 #[test]
778 fn publication_theme_inward_ticks() {
779 let t = Theme::publication();
780 assert_eq!(t.tick_direction, TickDirection::Inward);
781 }
782
783 #[test]
784 fn publication_theme_serif_font() {
785 let t = Theme::publication();
786 assert_eq!(t.font_family, Some("serif".to_string()));
787 }
788
789 #[test]
790 fn publication_theme_white_background() {
791 let t = Theme::publication();
792 assert_eq!(t.figure_background, Color::WHITE);
793 assert_eq!(t.axes_background, Color::WHITE);
794 }
795
796 #[test]
797 fn grid_axis_default_is_both() {
798 assert_eq!(GridAxis::default(), GridAxis::Both);
799 }
800
801 #[test]
804 fn seaborn_theme_muted_palette() {
805 let t = Theme::seaborn();
806 assert_eq!(t.color_cycle.len(), 10);
807 assert_eq!(t.color_cycle[0], Color::rgb(0x4C, 0x72, 0xB0));
809 }
810
811 #[test]
812 fn seaborn_theme_no_top_right_spines() {
813 let t = Theme::seaborn();
814 assert!(!t.show_top_spine);
815 assert!(!t.show_right_spine);
816 assert!(t.show_bottom_spine);
817 assert!(t.show_left_spine);
818 }
819
820 #[test]
821 fn seaborn_theme_sans_serif_font() {
822 let t = Theme::seaborn();
823 assert_eq!(t.font_family, Some("sans-serif".to_string()));
824 }
825
826 #[test]
829 fn publication_theme_larger_axis_labels() {
830 let t = Theme::publication();
831 assert!((t.axis_label_size - 12.0).abs() < f64::EPSILON);
832 }
833
834 #[test]
837 fn nature_theme_constructs_without_panic() {
838 let _t = Theme::nature();
839 }
840
841 #[test]
842 fn nature_theme_white_background() {
843 let t = Theme::nature();
844 assert_eq!(t.figure_background, Color::WHITE);
845 assert_eq!(t.axes_background, Color::WHITE);
846 }
847
848 #[test]
849 fn nature_theme_no_grid() {
850 let t = Theme::nature();
851 assert!(!t.show_grid);
852 }
853
854 #[test]
855 fn nature_theme_thin_spines() {
856 let t = Theme::nature();
857 assert!((t.spine_width - 0.75).abs() < f64::EPSILON);
858 assert!(!t.show_top_spine);
859 assert!(!t.show_right_spine);
860 assert!(t.show_bottom_spine);
861 assert!(t.show_left_spine);
862 }
863
864 #[test]
865 fn nature_theme_compact_font_sizes() {
866 let t = Theme::nature();
867 assert!((t.tick_label_size - 7.0).abs() < f64::EPSILON);
868 assert!((t.axis_label_size - 8.0).abs() < f64::EPSILON);
869 assert!((t.title_size - 10.0).abs() < f64::EPSILON);
870 }
871
872 #[test]
873 fn nature_theme_bold_labels() {
874 let t = Theme::nature();
875 assert_eq!(t.title_weight, FontWeight::Bold);
876 }
877
878 #[test]
879 fn nature_theme_sans_serif_font() {
880 let t = Theme::nature();
881 assert_eq!(t.font_family, Some("sans-serif".to_string()));
882 }
883
884 #[test]
885 fn nature_theme_palette() {
886 let t = Theme::nature();
887 assert_eq!(t.color_cycle.len(), 8);
888 assert_eq!(t.color_cycle[0], Color::rgb(0xE6, 0x4B, 0x35));
890 }
891
892 #[test]
893 fn nature_theme_small_markers() {
894 let t = Theme::nature();
895 assert!((t.marker_size - 4.0).abs() < f64::EPSILON);
896 assert!((t.marker_alpha - 1.0).abs() < f64::EPSILON);
897 }
898
899 #[test]
902 fn solarized_theme_constructs_without_panic() {
903 let _t = Theme::solarized();
904 }
905
906 #[test]
907 fn solarized_theme_dark_background() {
908 let t = Theme::solarized();
909 assert_eq!(t.figure_background, Color::rgb(0x00, 0x2B, 0x36));
911 assert_eq!(t.axes_background, Color::rgb(0x00, 0x2B, 0x36));
912 }
913
914 #[test]
915 fn solarized_theme_content_text_color() {
916 let t = Theme::solarized();
917 assert_eq!(t.text_color, Color::rgb(0x93, 0xA1, 0xA1));
919 }
920
921 #[test]
922 fn solarized_theme_accent_palette() {
923 let t = Theme::solarized();
924 assert_eq!(t.color_cycle.len(), 8);
925 assert_eq!(t.color_cycle[0], Color::rgb(0x26, 0x8B, 0xD2));
927 assert_eq!(t.color_cycle[7], Color::rgb(0x6C, 0x71, 0xC4)); }
930
931 #[test]
932 fn solarized_theme_grid_uses_base02() {
933 let t = Theme::solarized();
934 assert_eq!(t.grid_color, Color::rgb(0x07, 0x36, 0x42));
935 assert!(t.show_grid);
936 }
937
938 #[test]
939 fn solarized_theme_sans_serif_font() {
940 let t = Theme::solarized();
941 assert_eq!(t.font_family, Some("sans-serif".to_string()));
942 }
943
944 #[test]
945 fn solarized_theme_despine_look() {
946 let t = Theme::solarized();
947 assert!(!t.show_top_spine);
948 assert!(!t.show_right_spine);
949 assert!(t.show_bottom_spine);
950 assert!(t.show_left_spine);
951 }
952
953 #[test]
956 fn all_themes_have_distinct_backgrounds() {
957 let themes: Vec<(&str, Theme)> = vec![
958 ("default", Theme::default()),
959 ("dark", Theme::dark()),
960 ("seaborn", Theme::seaborn()),
961 ("ggplot", Theme::ggplot()),
962 ("publication", Theme::publication()),
963 ("nature", Theme::nature()),
964 ("solarized", Theme::solarized()),
965 ];
966 let mut backgrounds: Vec<(Color, Color)> = themes
970 .iter()
971 .map(|(_, t)| (t.figure_background, t.axes_background))
972 .collect();
973 backgrounds.sort_by_key(|(f, a)| (f.r, f.g, f.b, a.r, a.g, a.b));
974 backgrounds.dedup();
975 assert!(
976 backgrounds.len() >= 4,
977 "Expected at least 4 distinct background combos, got {}",
978 backgrounds.len()
979 );
980 }
981
982 #[test]
983 fn all_themes_have_reasonable_spine_widths() {
984 let themes = [
985 Theme::default(),
986 Theme::dark(),
987 Theme::seaborn(),
988 Theme::ggplot(),
989 Theme::publication(),
990 Theme::nature(),
991 Theme::solarized(),
992 ];
993 for t in &themes {
994 assert!(
995 t.spine_width >= 0.0 && t.spine_width <= 3.0,
996 "spine_width {} out of reasonable range",
997 t.spine_width
998 );
999 }
1000 }
1001
1002 #[test]
1003 fn all_themes_have_reasonable_tick_sizes() {
1004 let themes = [
1005 Theme::default(),
1006 Theme::dark(),
1007 Theme::seaborn(),
1008 Theme::ggplot(),
1009 Theme::publication(),
1010 Theme::nature(),
1011 Theme::solarized(),
1012 ];
1013 for t in &themes {
1014 assert!(
1015 t.tick_length >= 0.0 && t.tick_length <= 10.0,
1016 "tick_length {} out of reasonable range",
1017 t.tick_length
1018 );
1019 assert!(
1020 t.tick_label_size >= 5.0 && t.tick_label_size <= 16.0,
1021 "tick_label_size {} out of reasonable range",
1022 t.tick_label_size
1023 );
1024 }
1025 }
1026
1027 #[test]
1028 fn each_theme_has_nonempty_color_cycle() {
1029 let themes = [
1030 Theme::default(),
1031 Theme::dark(),
1032 Theme::seaborn(),
1033 Theme::ggplot(),
1034 Theme::publication(),
1035 Theme::nature(),
1036 Theme::solarized(),
1037 ];
1038 for t in &themes {
1039 assert!(
1040 !t.color_cycle.is_empty(),
1041 "color_cycle must not be empty"
1042 );
1043 }
1044 }
1045
1046 #[test]
1047 fn nature_and_publication_are_distinct() {
1048 let n = Theme::nature();
1049 let p = Theme::publication();
1050 assert_ne!(n.font_family, p.font_family);
1052 assert_ne!(n.show_top_spine, p.show_top_spine);
1053 assert!((n.axis_label_size - p.axis_label_size).abs() > f64::EPSILON);
1054 }
1055
1056 #[test]
1057 fn solarized_and_dark_are_distinct() {
1058 let s = Theme::solarized();
1059 let d = Theme::dark();
1060 assert_ne!(s.figure_background, d.figure_background);
1062 assert_ne!(s.color_cycle[0], d.color_cycle[0]);
1064 assert_ne!(s.text_color, d.text_color);
1066 }
1067}