vizkit 0.1.3

A rendering-agnostic kit for data visualization
Documentation
use super::color::Color;
use std::str::FromStr;

/// Trait used for interpolating values between [0, 1] as a Color
pub trait ColorMap {
    /// Interpolates a value between [0, 1] and returns a color
    fn interpolate<T>(&self, t: f32) -> T
    where
        Color: Into<T>;
}

/// Trait used for returning a collection of colors
pub trait Scheme {
    /// Returns a collection of colors in hex format (without the `#`)
    fn scheme(&self) -> Vec<&str>;
}

pub(crate) fn split_colors(string: &str) -> Vec<&str> {
    let n = string.len() / 6;
    (0..n).map(|i| &string[i * 6..(i + 1) * 6]).collect()
}

#[derive(Clone)]
struct InterpolateBasis {
    channel_values: Vec<f32>,
}

impl InterpolateBasis {
    fn new(channel_values: Vec<f32>) -> Self {
        Self { channel_values }
    }

    fn interpolate(&self, t: f32) -> f32 {
        let n = self.channel_values.len() - 1;
        let i = if t <= 0. {
            0
        } else if t >= 1. {
            n - 1
        } else {
            (t * n as f32) as usize
        };
        let v1 = self.channel_values[i];
        let v2 = self.channel_values[i + 1];
        let v0 = if i > 0 {
            self.channel_values[i - 1]
        } else {
            2. * v1 - v2
        };
        let v3 = if i < n - 1 {
            self.channel_values[i + 2]
        } else {
            2. * v2 - v1
        };
        let n = n as f32;
        Self::basis((t - i as f32 / n) * n, v0, v1, v2, v3)
    }

    fn basis(t1: f32, v0: f32, v1: f32, v2: f32, v3: f32) -> f32 {
        let t2 = t1 * t1;
        let t3 = t2 * t1;
        let channel_value = ((1. - 3. * t1 + 3. * t2 - t3) * v0
            + (4. - 6. * t2 + 3. * t3) * v1
            + (1. + 3. * t1 + 3. * t2 - 3. * t3) * v2
            + t3 * v3)
            / 6.;
        channel_value.clamp(0., 1.)
    }
}

#[derive(Clone)]
pub struct RGBInterpolator {
    r_channel: InterpolateBasis,
    g_channel: InterpolateBasis,
    b_channel: InterpolateBasis,
}

impl RGBInterpolator {
    pub(crate) fn new(colors: Vec<&str>) -> Self {
        let (r_values, g_values, b_values) = colors
            .into_iter()
            .map(|s| Color::from_str(s).unwrap_or_default())
            .fold((vec![], vec![], vec![]), |(mut r, mut g, mut b), color| {
                r.push(color.0[0]);
                g.push(color.0[1]);
                b.push(color.0[2]);
                (r, g, b)
            });
        RGBInterpolator {
            r_channel: InterpolateBasis::new(r_values),
            g_channel: InterpolateBasis::new(g_values),
            b_channel: InterpolateBasis::new(b_values),
        }
    }

    pub fn interpolate<T>(&self, t: f32) -> T
    where
        Color: Into<T>,
    {
        Color([
            self.r_channel.interpolate(t),
            self.g_channel.interpolate(t),
            self.b_channel.interpolate(t),
        ])
        .into()
    }
}