Skip to main content

contribution_grid/
builtins.rs

1//! Built-in themes and strategy factory for the contribution grid library.
2//!
3//! This module provides:
4//! - [`Theme`] factory for creating predefined color palettes
5//! - [`Strategy`] factory for creating built-in strategy instances
6//! - Built-in strategy implementations: [`LinearStrategy`], [`LogarithmicStrategy`], [`ThresholdStrategy`]
7
8use image::Rgba;
9
10use crate::MappingStrategy;
11use crate::Palette;
12
13/// Linear percentile-based mapping.
14pub struct LinearStrategy;
15impl MappingStrategy for LinearStrategy {
16    fn map(&self, count: u32, max_count: u32, num_colors: usize) -> usize {
17        if count == 0 || max_count == 0 {
18            return 0;
19        }
20        let ratio = count as f32 / max_count as f32;
21        ((ratio * (num_colors - 1) as f32).round() as usize).min(num_colors - 1)
22    }
23}
24
25/// Logarithmic mapping (emphasizes differences at lower values).
26pub struct LogarithmicStrategy;
27impl MappingStrategy for LogarithmicStrategy {
28    fn map(&self, count: u32, max_count: u32, num_colors: usize) -> usize {
29        if count == 0 {
30            return 0;
31        }
32        let log_count = (count as f32 + 1.0).ln();
33        let log_max = (max_count as f32 + 1.0).ln();
34        let ratio = log_count / log_max;
35        ((ratio * (num_colors - 1) as f32).round() as usize).min(num_colors - 1)
36    }
37}
38
39/// Fixed threshold mapping (ignores `max_count`).
40pub struct ThresholdStrategy {
41    thresholds: Vec<u32>,
42}
43impl ThresholdStrategy {
44    pub fn new(thresholds: Vec<u32>) -> Self {
45        Self { thresholds }
46    }
47}
48impl MappingStrategy for ThresholdStrategy {
49    fn map(&self, count: u32, _max_count: u32, _num_colors: usize) -> usize {
50        self.thresholds
51            .iter()
52            .position(|&t| count < t)
53            .unwrap_or(self.thresholds.len())
54    }
55}
56
57/// Factory for creating built-in color theme palettes.
58pub struct Theme;
59
60impl Theme {
61    /// Modern GitHub green style (5 colors).
62    pub fn github(strategy: impl MappingStrategy + 'static) -> Palette {
63        Palette::new(
64            vec![
65                Rgba([235, 237, 240, 255]),
66                Rgba([155, 233, 168, 255]),
67                Rgba([48, 196, 99, 255]),
68                Rgba([48, 161, 78, 255]),
69                Rgba([33, 110, 57, 255]),
70            ],
71            strategy,
72        )
73    }
74
75    /// Classic GitHub style (5 colors).
76    pub fn github_old(strategy: impl MappingStrategy + 'static) -> Palette {
77        Palette::new(
78            vec![
79                Rgba([238, 238, 238, 255]),
80                Rgba([198, 228, 139, 255]),
81                Rgba([123, 201, 111, 255]),
82                Rgba([35, 154, 59, 255]),
83                Rgba([25, 97, 39, 255]),
84            ],
85            strategy,
86        )
87    }
88
89    /// A blue-shaded theme (7 colors).
90    pub fn blue(strategy: impl MappingStrategy + 'static) -> Palette {
91        Palette::new(
92            vec![
93                Rgba([235, 237, 240, 255]),
94                Rgba([201, 231, 245, 255]),
95                Rgba([168, 196, 238, 255]),
96                Rgba([135, 146, 232, 255]),
97                Rgba([96, 101, 181, 255]),
98                Rgba([61, 62, 125, 255]),
99                Rgba([31, 29, 66, 255]),
100            ],
101            strategy,
102        )
103    }
104
105    /// A red-shaded theme (5 colors).
106    pub fn red(strategy: impl MappingStrategy + 'static) -> Palette {
107        Palette::new(
108            vec![
109                Rgba([235, 237, 240, 255]),
110                Rgba([255, 208, 198, 255]),
111                Rgba([255, 148, 133, 255]),
112                Rgba([234, 86, 70, 255]),
113                Rgba([181, 28, 28, 255]),
114            ],
115            strategy,
116        )
117    }
118}
119
120/// Factory for creating built-in strategy instances.
121pub struct Strategy;
122
123impl Strategy {
124    /// Creates a linear mapping strategy.
125    ///
126    /// # Example
127    ///
128    /// ```rust
129    /// use contribution_grid::builtins::{Theme, Strategy};
130    ///
131    /// let palette = Theme::github(Strategy::linear());
132    /// ```
133    pub fn linear() -> impl MappingStrategy + 'static {
134        LinearStrategy
135    }
136
137    /// Creates a logarithmic mapping strategy.
138    ///
139    /// # Example
140    ///
141    /// ```rust
142    /// use contribution_grid::builtins::{Theme, Strategy};
143    ///
144    /// let palette = Theme::github(Strategy::logarithmic());
145    /// ```
146    pub fn logarithmic() -> impl MappingStrategy + 'static {
147        LogarithmicStrategy
148    }
149
150    /// Creates a threshold-based mapping strategy.
151    ///
152    /// # Example
153    ///
154    /// ```rust
155    /// use contribution_grid::builtins::{Theme, Strategy};
156    ///
157    /// let palette = Theme::github(Strategy::threshold(vec![1, 5, 10, 20]));
158    /// ```
159    pub fn threshold(thresholds: Vec<u32>) -> impl MappingStrategy + 'static {
160        ThresholdStrategy::new(thresholds)
161    }
162}