use crate::color;
use tiny_skia::{Paint, PathBuilder, Pixmap, Point, Rect, Stroke, Transform};
use crate::{
drawable::{Bound, Drawable},
primitive::Config,
};
pub(crate) struct Bars {
y: Vec<f32>,
config: Config,
}
impl Bars {
pub fn new(y: Vec<f32>, config: Config) -> Self {
Self { y, config }
}
}
pub struct Histrogram {
name: String,
x: Vec<f32>,
bars: Vec<Bars>,
color_index: usize,
}
impl Histrogram {
pub fn new(name: String) -> Self {
Self {
name,
x: Vec::new(),
bars: Vec::new(),
color_index: 0,
}
}
fn max_len(&self) -> usize {
self.x.len().saturating_sub(1)
}
fn resize(&mut self, final_limit: usize) {
for bar in self.bars.iter_mut() {
if bar.y.len() != final_limit {
bar.y.resize(final_limit, 0.0);
}
}
}
pub fn set_x(&mut self, x: &[f32]) {
let limit = x.len();
self.x.clear();
self.x.extend_from_slice(x);
if self.bars.is_empty() {
self.resize(limit);
}
}
pub(crate) fn gen_config(&mut self) -> Config {
let color = color::get_color(self.color_index);
let index = self.color_index;
self.color_index = (index + 1) & 7;
Config {
color,
..Config::default()
}
}
pub(crate) fn get_color_index(&self) -> usize {
self.color_index
}
pub fn set_data(&mut self, y: &[f32]) {
let config = self.gen_config();
let valid_value: Vec<f32> = y.iter().take(self.max_len()).cloned().collect();
let bar = Bars::new(valid_value, config);
self.bars.push(bar);
}
pub fn add_data(&mut self, index: usize, y: &[f32]) {
let limit = self.max_len();
if let Some(bar) = self.bars.get_mut(index) {
let current_len = bar.y.len();
if current_len < limit {
let should_add_len = limit - current_len;
bar.y.extend(y.iter().take(should_add_len).cloned());
}
}
}
pub fn change_data(&mut self, index: usize, y: &[f32]) {
let limit = self.max_len();
if let Some(bar) = self.bars.get_mut(index) {
let new_y: Vec<f32> = y.iter().take(limit).cloned().collect();
bar.y.clear();
bar.y.extend(new_y);
}
}
pub fn set_data_prototype(&mut self, y: &[f32], x_start: f32, step: f32) {
let n = y.len();
if n == 0 {
return;
}
if self.x.is_empty() {
let mut new_x = Vec::with_capacity(n + 1);
for i in 0..=n {
new_x.push(x_start + i as f32 * step);
}
self.x = new_x;
let limit = self.x.len().saturating_sub(1);
let config = self.gen_config();
let final_y = y.iter().take(limit).cloned().collect::<Vec<f32>>();
self.bars.push(Bars::new(final_y, config));
} else {
self.set_data(y);
}
}
pub fn set_data_with_step(&mut self, y: &[f32], step: f32) {
self.set_data_prototype(y, 0., step);
}
pub fn set_data_norm(&mut self, y: &[f32]) {
self.set_data_prototype(y, 0., 1.);
}
}
impl Histrogram {
pub(crate) fn get_bars(&self) -> &[Bars] {
&self.bars
}
}
impl Bars {
pub(crate) fn get_values(&self) -> &[f32] {
self.y.as_ref()
}
}
impl Drawable for Histrogram {
fn draw(&self, pixmap: &mut Pixmap, ts: &Transform) {
if self.x.len() < 2 || self.bars.is_empty() {
return;
}
let num_groups = self.bars.len() as f32;
let group_width_ratio = 0.8;
let half_num_groups = (num_groups - 1.0) / 2.0;
for i in 0..(self.x.len() - 1) {
let x_start = self.x[i];
let x_end = self.x[i + 1];
let total_step_w = x_end - x_start;
let x_start = x_start + total_step_w * 0.5;
let x_center = x_start + total_step_w * 0.5;
let single_bar_w = (total_step_w * group_width_ratio) / num_groups;
for (g_idx, bar) in self.bars.iter().enumerate() {
if bar.config.is_hidden {
continue;
}
let y_val = bar.y[i];
if y_val == 0.0 {
continue;
}
let offset = (g_idx as f32 - half_num_groups) * single_bar_w;
let x_l = x_center + offset - single_bar_w * 0.5;
let x_r = x_center + offset + single_bar_w * 0.5;
let mut p1 = Point::from_xy(x_l, y_val);
let mut p2 = Point::from_xy(x_r, 0.0);
ts.map_point(&mut p1);
ts.map_point(&mut p2);
if let Some(r_rect) = Rect::from_ltrb(
p1.x.min(p2.x),
p1.y.min(p2.y),
p1.x.max(p2.x),
p1.y.max(p2.y),
) {
let [r, g, b, a] = bar.config.color;
let mut paint = Paint::default();
paint.set_color_rgba8(r, g, b, a);
paint.anti_alias = true;
pixmap.fill_rect(r_rect, &paint, Transform::identity(), None);
let path = PathBuilder::from_rect(r_rect);
let mut stroke_paint = Paint::default();
stroke_paint.set_color_rgba8(
r.saturating_sub(40),
g.saturating_sub(40),
b.saturating_sub(40),
255,
);
let stroke = Stroke {
width: bar.config.stroke_width,
..Default::default()
};
pixmap.stroke_path(&path, &stroke_paint, &stroke, Transform::identity(), None);
}
}
}
}
fn bound(&self) -> Option<Bound> {
if self.x.is_empty() {
return None;
}
let mut y_max = 0.0f32;
let mut y_min = 0.0f32;
let mut has_data = false;
for bar in self.bars.iter() {
if bar.config.is_hidden {
continue;
}
for &y in &bar.y {
y_max = y_max.max(y);
y_min = y_min.min(y);
has_data = true;
}
}
if !has_data {
return None;
}
Some(Bound {
x_min: *self.x.first().unwrap(),
x_max: *self.x.last().unwrap(),
y_min,
y_max,
})
}
fn name(&self) -> String {
self.name.clone()
}
fn get_color(&self) -> [u8; 4] {
[255, 255, 255, 255]
}
fn set_color(&mut self, _color: [u8; 4]) {}
}