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, Clone, Copy, PartialEq, Eq)]
97pub enum TickDirection {
98 Outward,
100 Inward,
102}
103
104#[derive(Debug, Clone)]
114pub struct Theme {
115 pub figure_background: Color,
117 pub axes_background: Color,
119
120 pub grid_color: Color,
123 pub grid_width: f64,
125 pub show_grid: bool,
127
128 pub spine_color: Color,
131 pub spine_width: f64,
133 pub show_top_spine: bool,
135 pub show_right_spine: bool,
137 pub show_bottom_spine: bool,
139 pub show_left_spine: bool,
141
142 pub tick_color: Color,
145 pub tick_length: f64,
147 pub tick_direction: TickDirection,
149 pub tick_label_size: f64,
151
152 pub axis_label_size: f64,
155 pub title_size: f64,
157 pub title_weight: FontWeight,
159 pub text_color: Color,
161
162 pub line_width: f64,
165 pub marker_size: f64,
167 pub marker_alpha: f64,
169
170 pub color_cycle: Vec<Color>,
173
174 pub font_family: Option<String>,
178}
179
180const TABLEAU_10: [Color; 10] = Color::TABLEAU_10;
186
187impl Default for Theme {
192 fn default() -> Self {
203 let spine = Color::rgb(0x33, 0x33, 0x33);
204
205 Self {
206 figure_background: Color::WHITE,
207 axes_background: Color::WHITE,
208
209 grid_color: Color::rgb(0xE6, 0xE6, 0xE6),
210 grid_width: 1.0,
211 show_grid: true,
212
213 spine_color: spine,
214 spine_width: 1.0,
215 show_top_spine: false,
216 show_right_spine: false,
217 show_bottom_spine: true,
218 show_left_spine: true,
219
220 tick_color: spine,
221 tick_length: 4.0,
222 tick_direction: TickDirection::Outward,
223 tick_label_size: 9.0,
224
225 axis_label_size: 11.0,
226 title_size: 14.0,
227 title_weight: FontWeight::Bold,
228 text_color: spine,
229
230 line_width: 1.5,
231 marker_size: 6.0,
232 marker_alpha: 0.8,
233
234 color_cycle: TABLEAU_10.to_vec(),
235
236 font_family: None,
237 }
238 }
239}
240
241impl Theme {
246 pub fn dark() -> Self {
250 let bg = Color::rgb(0x1C, 0x1C, 0x1C);
251 let text = Color::rgb(0xE0, 0xE0, 0xE0);
252 let grid = Color::rgb(0x3A, 0x3A, 0x3A);
253 let spine = Color::rgb(0x55, 0x55, 0x55);
254
255 let cycle = vec![
257 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), ];
268
269 Self {
270 figure_background: bg,
271 axes_background: bg,
272
273 grid_color: grid,
274 grid_width: 1.0,
275 show_grid: true,
276
277 spine_color: spine,
278 spine_width: 1.0,
279 show_top_spine: false,
280 show_right_spine: false,
281 show_bottom_spine: true,
282 show_left_spine: true,
283
284 tick_color: text,
285 tick_length: 4.0,
286 tick_direction: TickDirection::Outward,
287 tick_label_size: 9.0,
288
289 axis_label_size: 11.0,
290 title_size: 14.0,
291 title_weight: FontWeight::Bold,
292 text_color: text,
293
294 line_width: 1.5,
295 marker_size: 6.0,
296 marker_alpha: 0.9,
297
298 color_cycle: cycle,
299
300 font_family: None,
301 }
302 }
303
304 pub fn seaborn() -> Self {
309 let text = Color::rgb(0x33, 0x33, 0x33);
310 let axes_bg = Color::rgb(0xEA, 0xEA, 0xF2);
311
312 Self {
313 figure_background: Color::WHITE,
314 axes_background: axes_bg,
315
316 grid_color: Color::WHITE,
317 grid_width: 1.0,
318 show_grid: true,
319
320 spine_color: Color::rgb(0xCC, 0xCC, 0xCC),
321 spine_width: 1.0,
322 show_top_spine: false,
323 show_right_spine: false,
324 show_bottom_spine: true,
325 show_left_spine: true,
326
327 tick_color: text,
328 tick_length: 0.0, tick_direction: TickDirection::Outward,
330 tick_label_size: 9.0,
331
332 axis_label_size: 11.0,
333 title_size: 14.0,
334 title_weight: FontWeight::Bold,
335 text_color: text,
336
337 line_width: 1.5,
338 marker_size: 6.0,
339 marker_alpha: 0.8,
340
341 color_cycle: TABLEAU_10.to_vec(),
342
343 font_family: None,
344 }
345 }
346
347 pub fn ggplot() -> Self {
353 let panel = Color::rgb(0xE5, 0xE5, 0xE5);
354 let text = Color::rgb(0x30, 0x30, 0x30);
355
356 let cycle = vec![
358 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), ];
367
368 Self {
369 figure_background: Color::WHITE,
370 axes_background: panel,
371
372 grid_color: Color::WHITE,
373 grid_width: 1.0,
374 show_grid: true,
375
376 spine_color: Color::WHITE,
378 spine_width: 0.0,
379 show_top_spine: false,
380 show_right_spine: false,
381 show_bottom_spine: false,
382 show_left_spine: false,
383
384 tick_color: text,
385 tick_length: 0.0, tick_direction: TickDirection::Outward,
387 tick_label_size: 9.0,
388
389 axis_label_size: 11.0,
390 title_size: 14.0,
391 title_weight: FontWeight::Bold,
392 text_color: text,
393
394 line_width: 1.0,
395 marker_size: 5.0,
396 marker_alpha: 1.0,
397
398 color_cycle: cycle,
399
400 font_family: None,
401 }
402 }
403
404 pub fn publication() -> Self {
411 let ink = Color::rgb(0x1A, 0x1A, 0x1A);
412
413 Self {
414 figure_background: Color::WHITE,
415 axes_background: Color::WHITE,
416
417 grid_color: Color::rgb(0xD0, 0xD0, 0xD0),
418 grid_width: 0.5,
419 show_grid: false,
420
421 spine_color: ink,
422 spine_width: 0.5,
423 show_top_spine: true,
424 show_right_spine: true,
425 show_bottom_spine: true,
426 show_left_spine: true,
427
428 tick_color: ink,
429 tick_length: 3.0,
430 tick_direction: TickDirection::Inward,
431 tick_label_size: 8.0,
432
433 axis_label_size: 10.0,
434 title_size: 12.0,
435 title_weight: FontWeight::Bold,
436 text_color: ink,
437
438 line_width: 1.0,
439 marker_size: 4.0,
440 marker_alpha: 1.0,
441
442 color_cycle: TABLEAU_10.to_vec(),
443
444 font_family: Some("serif".to_string()),
445 }
446 }
447}
448
449#[cfg(test)]
454mod tests {
455 use super::*;
456
457 #[test]
458 fn default_theme_background_is_white() {
459 let t = Theme::default();
460 assert_eq!(t.figure_background, Color::WHITE);
461 assert_eq!(t.axes_background, Color::WHITE);
462 }
463
464 #[test]
465 fn default_theme_despine_look() {
466 let t = Theme::default();
467 assert!(!t.show_top_spine);
468 assert!(!t.show_right_spine);
469 assert!(t.show_bottom_spine);
470 assert!(t.show_left_spine);
471 }
472
473 #[test]
474 fn default_theme_grid() {
475 let t = Theme::default();
476 assert_eq!(t.grid_color, Color::rgb(0xE6, 0xE6, 0xE6));
477 assert!((t.grid_width - 1.0).abs() < f64::EPSILON);
478 assert!(t.show_grid);
479 }
480
481 #[test]
482 fn default_theme_spines() {
483 let t = Theme::default();
484 let expected = Color::rgb(0x33, 0x33, 0x33);
485 assert_eq!(t.spine_color, expected);
486 assert!((t.spine_width - 1.0).abs() < f64::EPSILON);
487 }
488
489 #[test]
490 fn default_theme_ticks() {
491 let t = Theme::default();
492 assert_eq!(t.tick_color, Color::rgb(0x33, 0x33, 0x33));
493 assert!((t.tick_length - 4.0).abs() < f64::EPSILON);
494 assert_eq!(t.tick_direction, TickDirection::Outward);
495 }
496
497 #[test]
498 fn default_theme_font_sizes() {
499 let t = Theme::default();
500 assert!((t.tick_label_size - 9.0).abs() < f64::EPSILON);
501 assert!((t.axis_label_size - 11.0).abs() < f64::EPSILON);
502 assert!((t.title_size - 14.0).abs() < f64::EPSILON);
503 assert_eq!(t.title_weight, FontWeight::Bold);
504 }
505
506 #[test]
507 fn default_theme_text_color() {
508 let t = Theme::default();
509 assert_eq!(t.text_color, Color::rgb(0x33, 0x33, 0x33));
510 }
511
512 #[test]
513 fn default_theme_data_defaults() {
514 let t = Theme::default();
515 assert!((t.line_width - 1.5).abs() < f64::EPSILON);
516 assert!((t.marker_size - 6.0).abs() < f64::EPSILON);
517 assert!((t.marker_alpha - 0.8).abs() < f64::EPSILON);
518 }
519
520 #[test]
521 fn default_theme_tableau_10_cycle() {
522 let t = Theme::default();
523 assert_eq!(t.color_cycle.len(), 10);
524 assert_eq!(t.color_cycle[0], Color::TAB_BLUE);
525 assert_eq!(t.color_cycle[9], Color::TAB_CYAN);
526 }
527
528 #[test]
529 fn dark_theme_has_dark_background() {
530 let t = Theme::dark();
531 assert_eq!(t.figure_background, Color::rgb(0x1C, 0x1C, 0x1C));
532 assert_eq!(t.axes_background, Color::rgb(0x1C, 0x1C, 0x1C));
533 }
534
535 #[test]
536 fn dark_theme_light_text() {
537 let t = Theme::dark();
538 assert_eq!(t.text_color, Color::rgb(0xE0, 0xE0, 0xE0));
539 }
540
541 #[test]
542 fn dark_theme_neon_cycle() {
543 let t = Theme::dark();
544 assert_eq!(t.color_cycle.len(), 10);
545 assert_eq!(t.color_cycle[0], Color::rgb(0x00, 0xD4, 0xFF));
547 }
548
549 #[test]
550 fn seaborn_theme_tinted_face() {
551 let t = Theme::seaborn();
552 assert_eq!(t.axes_background, Color::rgb(0xEA, 0xEA, 0xF2));
553 }
554
555 #[test]
556 fn seaborn_theme_white_grid() {
557 let t = Theme::seaborn();
558 assert_eq!(t.grid_color, Color::WHITE);
559 assert!(t.show_grid);
560 }
561
562 #[test]
563 fn ggplot_theme_grey_panel() {
564 let t = Theme::ggplot();
565 assert_eq!(t.axes_background, Color::rgb(0xE5, 0xE5, 0xE5));
566 }
567
568 #[test]
569 fn ggplot_theme_white_grid() {
570 let t = Theme::ggplot();
571 assert_eq!(t.grid_color, Color::WHITE);
572 assert!(t.show_grid);
573 }
574
575 #[test]
576 fn ggplot_theme_no_spines() {
577 let t = Theme::ggplot();
578 assert!(!t.show_top_spine);
579 assert!(!t.show_right_spine);
580 assert!(!t.show_bottom_spine);
581 assert!(!t.show_left_spine);
582 }
583
584 #[test]
585 fn ggplot_theme_palette() {
586 let t = Theme::ggplot();
587 assert_eq!(t.color_cycle.len(), 8);
588 assert_eq!(t.color_cycle[0], Color::rgb(0xF8, 0x76, 0x6D));
589 }
590
591 #[test]
592 fn publication_theme_all_spines_visible() {
593 let t = Theme::publication();
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 publication_theme_no_grid() {
602 let t = Theme::publication();
603 assert!(!t.show_grid);
604 }
605
606 #[test]
607 fn publication_theme_thin_spines() {
608 let t = Theme::publication();
609 assert!((t.spine_width - 0.5).abs() < f64::EPSILON);
610 }
611
612 #[test]
613 fn publication_theme_inward_ticks() {
614 let t = Theme::publication();
615 assert_eq!(t.tick_direction, TickDirection::Inward);
616 }
617
618 #[test]
619 fn publication_theme_serif_font() {
620 let t = Theme::publication();
621 assert_eq!(t.font_family, Some("serif".to_string()));
622 }
623
624 #[test]
625 fn publication_theme_white_background() {
626 let t = Theme::publication();
627 assert_eq!(t.figure_background, Color::WHITE);
628 assert_eq!(t.axes_background, Color::WHITE);
629 }
630}