use std::collections::HashMap;
use crate::chart::TagPercent;
const COLORS: [&str; 8] = [
"#1f78b4", "#a6cee3", "#33a02c", "#b2df8a", "#aa3133", "#fb9a99", "#ff7f00", "#fdbf6f"
];
const LAST_COLOR: usize = COLORS.len() - 1;
#[derive(Clone)]
pub struct ColorIter<'a> {
idx: usize,
colors: &'a [&'static str],
last: usize
}
impl<'a> ColorIter<'a> {
pub fn new(colors: &'a [&'static str]) -> Self {
Self { idx: 0, colors, last: colors.len() - 1 }
}
pub fn limit_percents(&self, percents: &[TagPercent], other: &str) -> Vec<TagPercent> {
if percents.len() <= self.colors.len() {
percents.to_vec()
}
else {
let len = self.colors.len() - 1;
let percent: f32 = percents.iter().skip(len).map(TagPercent::percent_val).sum();
let mut percents: Vec<TagPercent> = percents.iter().take(len).cloned().collect();
if let Some(perc) = TagPercent::new(other, percent) {
percents.push(perc);
}
percents
}
}
}
impl<'a> Iterator for ColorIter<'a> {
type Item = &'static str;
fn next(&mut self) -> Option<Self::Item> {
let idx = self.idx;
if idx < self.last {
self.idx += 1;
}
Some(self.colors[idx])
}
}
impl<'a> Default for ColorIter<'a> {
fn default() -> Self { Self::new(&COLORS) }
}
pub struct ColorMap(HashMap<String, &'static str>);
impl ColorMap {
pub fn new(percents: &[TagPercent]) -> Self {
Self::new_with_colors(percents, ColorIter::default())
}
pub fn new_with_colors(percents: &[TagPercent], colors: ColorIter) -> Self {
Self(
percents
.iter()
.zip(colors)
.map(|(tp, clr)| (tp.label().to_string(), clr))
.collect()
)
}
pub fn get(&self, label: &str) -> Option<&'static str> { self.0.get(label).copied() }
pub fn set_default(&mut self, label: &str) {
self.0.insert(label.to_string(), COLORS[LAST_COLOR]);
}
pub fn set_default_to_color(&mut self, label: &str, clr: &'static str) {
self.0.insert(label.to_string(), clr);
}
}
#[cfg(test)]
mod tests {
use spectral::prelude::*;
use super::*;
#[test]
fn test_new_iter() {
let mut iter = ColorIter::new(&COLORS);
assert_that!(iter.next()).is_some();
}
#[test]
fn test_color_iter_base() {
for (clr, expect) in ColorIter::default().zip(&COLORS) {
assert_that!(clr).is_equal_to(expect);
}
}
#[test]
fn test_color_iter_tail() {
let mut iter = ColorIter::default().skip(COLORS.len());
for _ in 0..10 {
assert_that!(iter.next()).contains(COLORS[LAST_COLOR]);
}
}
#[test]
fn test_new() {
let colors = [
"black", "brown", "red", "orange", "yellow", "green", "blue", "violet", "grey", "white"
];
let iter = ColorIter::new(&colors);
for (actual, expect) in iter.zip(&colors) {
assert_that!(actual).is_equal_to(expect);
}
}
#[test]
fn color_map_few() {
#[rustfmt::skip]
let percents = [
TagPercent::new("david", 40.0).unwrap(),
TagPercent::new("connie", 20.0).unwrap(),
TagPercent::new("mark", 10.0).unwrap(),
TagPercent::new("kirsten", 10.0).unwrap(),
TagPercent::new("fred", 5.0).unwrap(),
TagPercent::new("bianca", 5.0).unwrap(),
];
let cmap = ColorMap::new(&percents);
for (label, clr) in percents
.iter()
.map(|tp| tp.label())
.zip(ColorIter::default())
{
assert_that!(cmap.get(label)).contains(clr);
}
}
#[test]
fn color_map_with_default() {
#[rustfmt::skip]
let percents = [
TagPercent::new("david", 40.0).unwrap(),
TagPercent::new("connie", 20.0).unwrap(),
TagPercent::new("mark", 10.0).unwrap(),
TagPercent::new("kirsten", 10.0).unwrap(),
TagPercent::new("fred", 5.0).unwrap(),
TagPercent::new("bianca", 5.0).unwrap(),
];
let mut cmap = ColorMap::new(&percents);
assert_that!(cmap.get("Other"))
.named("no default")
.is_none();
cmap.set_default("Other");
assert_that!(cmap.get("Other"))
.named("has default")
.contains(COLORS[LAST_COLOR]);
}
#[test]
fn color_map_many() {
#[rustfmt::skip]
let percents = [
TagPercent::new("david", 40.0).unwrap(),
TagPercent::new("connie", 20.0).unwrap(),
TagPercent::new("mark", 10.0).unwrap(),
TagPercent::new("kirsten", 10.0).unwrap(),
TagPercent::new("fred", 5.0).unwrap(),
TagPercent::new("bianca", 5.0).unwrap(),
TagPercent::new("aramis", 1.0).unwrap(),
TagPercent::new("bryan", 1.0).unwrap(),
TagPercent::new("serpent", 1.0).unwrap(),
TagPercent::new("mobile", 1.0).unwrap(),
];
let cmap = ColorMap::new(&percents);
for (label, clr) in percents
.iter()
.map(|tp| tp.label())
.zip(ColorIter::default())
{
assert_that!(cmap.get(label)).contains(clr);
}
}
#[test]
fn limit_percents_few() {
#[rustfmt::skip]
let percents = [
TagPercent::new("david", 40.0).unwrap(),
TagPercent::new("connie", 20.0).unwrap(),
TagPercent::new("mark", 10.0).unwrap(),
TagPercent::new("kirsten", 10.0).unwrap(),
TagPercent::new("fred", 5.0).unwrap(),
TagPercent::new("bianca", 5.0).unwrap(),
];
let colors = ColorIter::default();
assert_that!(colors.limit_percents(&percents, "Other").iter())
.equals_iterator(&percents.iter());
}
#[test]
fn limit_percents_many() {
#[rustfmt::skip]
let percents = [
TagPercent::new("david", 40.0).unwrap(),
TagPercent::new("connie", 20.0).unwrap(),
TagPercent::new("mark", 10.0).unwrap(),
TagPercent::new("kirsten", 10.0).unwrap(),
TagPercent::new("fred", 5.0).unwrap(),
TagPercent::new("bianca", 5.0).unwrap(),
TagPercent::new("aramis", 1.0).unwrap(),
TagPercent::new("bryan", 1.0).unwrap(),
TagPercent::new("serpent", 1.0).unwrap(),
TagPercent::new("mobile", 1.0).unwrap(),
];
#[rustfmt::skip]
let expected = [
TagPercent::new("david", 40.0).unwrap(),
TagPercent::new("connie", 20.0).unwrap(),
TagPercent::new("mark", 10.0).unwrap(),
TagPercent::new("kirsten", 10.0).unwrap(),
TagPercent::new("fred", 5.0).unwrap(),
TagPercent::new("bianca", 5.0).unwrap(),
TagPercent::new("aramis", 1.0).unwrap(),
TagPercent::new("Other", 3.0).unwrap(),
];
let colors = ColorIter::default();
assert_that!(colors.limit_percents(&percents, "Other").iter())
.equals_iterator(&expected.iter());
}
}