ramp.rs
3.5 kB · rust · 114 lines
1use super::colors::{gradient, shades, Color, BLACK, BLUE, ORANGE, RED, WHITE, YELLOW};2use super::errors::Result;34/// A rule that turns counter values into colors.5#[derive(Clone, Debug)]6pub enum Colorizer {7 /// The background at zero, then the ramp binned across the range.8 Bins {9 /// The color at zero.10 background: Color,11 /// The colors binned across the range.12 ramp: Vec<Color>,13 },14}1516impl Colorizer {17 /// Builds the white-to-black heat ramp.18 pub fn heat() -> Colorizer {19 let ramp = dedup(gradient(&[WHITE, BLACK], 128).unwrap_or_else(|_| vec![BLACK]));20 Colorizer::Bins {21 background: WHITE,22 ramp,23 }24 }25 /// Builds the black-through-ember fire ramp: black, dark red, orange, light yellow.26 pub fn fire() -> Colorizer {27 let stops = [BLACK, shades(RED)[2], ORANGE, shades(YELLOW)[0]];28 let ramp = dedup(gradient(&stops, 128).unwrap_or_else(|_| vec![BLACK]));29 Colorizer::Bins {30 background: ramp[0],31 ramp,32 }33 }34 /// Builds the blue-to-red diverging ramp around a white middle.35 pub fn diverge() -> Colorizer {36 let stops = [BLUE, WHITE, RED];37 let ramp = dedup(gradient(&stops, 128).unwrap_or_else(|_| vec![WHITE]));38 Colorizer::Bins {39 background: ramp[0],40 ramp,41 }42 }43 /// Builds a binned colorizer from a gradient through the given stops.44 pub fn gradient_bins(background: Color, colors: &[Color], shades: usize) -> Result<Colorizer> {45 let ramp = dedup(gradient(colors, shades.max(1))?);46 Ok(Colorizer::Bins { background, ramp })47 }48 /// Returns the color for one value against the range maximum.49 pub fn color(&self, value: usize, max: usize) -> Color {50 match self {51 Colorizer::Bins { background, ramp } => {52 if value == 0 || ramp.is_empty() {53 return *background;54 }55 if max <= 1 {56 return ramp[ramp.len() - 1];57 }58 let idx = (value - 1) * (ramp.len() - 1) / (max - 1).max(1);59 ramp[idx.min(ramp.len() - 1)]60 }61 }62 }63 /// Maps a slice of values to rgba pixels against the range maximum.64 pub fn colors(&self, values: &[usize], max: usize) -> Vec<[u8; 4]> {65 values66 .iter()67 .map(|&v| {68 let c = self.color(v, max);69 [c.r, c.g, c.b, c.a]70 })71 .collect()72 }73}7475impl Default for Colorizer {76 fn default() -> Colorizer {77 Colorizer::heat()78 }79}8081fn dedup(colors: Vec<Color>) -> Vec<Color> {82 let mut out: Vec<Color> = Vec::with_capacity(colors.len());83 for c in colors {84 if out.last() != Some(&c) {85 out.push(c);86 }87 }88 out89}9091#[cfg(test)]92mod tests {93 use super::*;94 #[test]95 fn heat_is_white_bg_dark_max() {96 let r = Colorizer::heat();97 assert_eq!(r.color(0, 10), WHITE);98 assert_eq!(r.color(10, 10), BLACK);99 }100 #[test]101 fn bins_spread_across_range() {102 let r = Colorizer::gradient_bins(WHITE, &[WHITE, BLACK], 4).unwrap();103 let low = r.color(1, 100);104 let high = r.color(100, 100);105 assert!(low.r > high.r);106 }107 #[test]108 fn dedup_collapses_repeats() {109 assert_eq!(110 dedup(vec![BLACK, BLACK, WHITE, WHITE, BLACK]),111 vec![BLACK, WHITE, BLACK]112 );113 }114}