use std::sync::Arc;
use colorous::{VIRIDIS, INFERNO, GREYS, TURBO};
const HEX_DIGITS: &[u8; 16] = b"0123456789abcdef";
#[inline]
fn rgb_hex(r: u8, g: u8, b: u8) -> String {
let bytes = [
b'#',
HEX_DIGITS[(r >> 4) as usize],
HEX_DIGITS[(r & 0xf) as usize],
HEX_DIGITS[(g >> 4) as usize],
HEX_DIGITS[(g & 0xf) as usize],
HEX_DIGITS[(b >> 4) as usize],
HEX_DIGITS[(b & 0xf) as usize],
];
unsafe { String::from_utf8_unchecked(bytes.to_vec()) }
}
fn viridis(value: f64) -> String {
let rgb = VIRIDIS.eval_continuous(value.clamp(0.0, 1.0));
rgb_hex(rgb.r, rgb.g, rgb.b)
}
fn inferno(value: f64) -> String {
let rgb = INFERNO.eval_continuous(value.clamp(0.0, 1.0));
rgb_hex(rgb.r, rgb.g, rgb.b)
}
fn greyscale(value: f64) -> String {
let rgb = GREYS.eval_continuous(value.clamp(0.0, 1.0));
rgb_hex(rgb.r, rgb.g, rgb.b)
}
fn turbo(value: f64) -> String {
let rgb = TURBO.eval_continuous(value.clamp(0.0, 1.0));
rgb_hex(rgb.r, rgb.g, rgb.b)
}
#[derive(Clone)]
pub enum ColorMap {
Grayscale,
Viridis,
Inferno,
Turbo,
Custom(Arc<dyn Fn(f64) -> String + Send + Sync>),
}
impl ColorMap {
pub fn map(&self, value: f64) -> String {
match self {
ColorMap::Grayscale => greyscale(value),
ColorMap::Viridis => viridis(value),
ColorMap::Inferno => inferno(value),
ColorMap::Turbo => turbo(value),
ColorMap::Custom(f) => f(value),
}
}
}
#[derive(Clone)]
pub struct Histogram2D {
pub data: Vec<(f64, f64)>,
pub bins: Vec<Vec<usize>>,
pub x_range: (f64, f64),
pub y_range: (f64, f64),
pub bins_x: usize,
pub bins_y: usize,
pub color_map: ColorMap,
pub show_correlation: bool,
pub log_count: bool,
}
impl Default for Histogram2D {
fn default() -> Self { Self::new() }
}
impl Histogram2D {
pub fn new() -> Self {
Self {
data: vec![],
bins: vec![],
x_range: (0.0, 0.0),
y_range: (0.0, 0.0),
bins_x: 10,
bins_y: 10,
color_map: ColorMap::Viridis,
show_correlation: false,
log_count: false,
}
}
pub fn with_data<T: Into<f64>>(mut self,
data: Vec<(T, T)>,
x_range: (f64, f64),
y_range: (f64, f64),
bins_x: usize,
bins_y: usize)
-> Self {
let mut bins = vec![vec![0usize; bins_x]; bins_y];
let x_bin_width = (x_range.1 - x_range.0) / bins_x as f64;
let y_bin_height = (y_range.1 - y_range.0) / bins_y as f64;
for (x_raw, y_raw) in data {
let x = x_raw.into();
let y = y_raw.into();
self.data.push((x, y));
if x < x_range.0 || x > x_range.1 || y < y_range.0 || y > y_range.1 {
continue; }
let col = (((x - x_range.0) / x_bin_width).floor() as usize).min(bins_x - 1);
let row = (((y - y_range.0) / y_bin_height).floor() as usize).min(bins_y - 1);
bins[row][col] += 1;
}
self.bins = bins;
self.x_range = x_range;
self.y_range = y_range;
self.bins_x = bins_x;
self.bins_y = bins_y;
self
}
pub fn with_color_map(mut self, map: ColorMap) -> Self {
self.color_map = map;
self
}
pub fn with_correlation(mut self) -> Self {
self.show_correlation = true;
self
}
pub fn with_log_count(mut self) -> Self {
self.log_count = true;
self
}
}