use core::f32;
use tiny_skia::{Paint, PathBuilder, Pixmap, Point, Stroke, Transform};
use crate::primitive::{Config, Histrogram};
use crate::{
color,
drawable::{Bound, Drawable},
};
pub enum ErrorBarType {
BaseOnX,
BaseOnY,
}
#[derive(Debug)]
struct Bar {
mean: f32,
min: f32,
max: f32,
config: Config,
}
impl Bar {
pub fn new(mean: f32, min: f32, max: f32, config: Config) -> Self {
Self {
mean,
min,
max,
config,
}
}
fn or(mut self, rhs: f32) -> Self {
let mean = (self.mean + rhs) / 2.;
let min = self.min.min(rhs);
let max = self.max.max(rhs);
self.mean = mean;
self.min = min;
self.max = max;
self
}
}
pub struct ErrorBar {
name: String,
bars: Vec<Bar>,
err_type: ErrorBarType,
need_shift: bool,
color_index: usize,
}
impl ErrorBar {
const OFFSET: usize = 1;
pub fn new(name: String) -> Self {
Self {
name,
bars: Vec::new(),
err_type: ErrorBarType::BaseOnX,
color_index: 0,
need_shift: true,
}
}
fn get_avarage(vals: &[f32]) -> f32 {
if vals.is_empty() {
return 0.0;
}
vals.iter().sum::<f32>() / vals.len() as f32
}
fn gen_config(&mut self) -> Config {
let index = self.color_index;
let color = color::get_color(index);
self.color_index = (index + 1) & 7;
Config {
color,
..Config::default()
} }
pub fn set_data(&mut self, vals: &[f32]) {
let config = self.gen_config();
let mut min = f32::INFINITY;
let mut max = f32::NEG_INFINITY;
let mean = Self::get_avarage(vals);
for &val in vals {
min = min.min(val);
max = max.max(val);
}
let bar = Bar::new(mean, min, max, config);
self.bars.push(bar);
}
pub fn set_data_prototype(&mut self, mean: f32, min: f32, max: f32) {
let config = self.gen_config();
let bar = Bar::new(mean, min, max, config);
self.bars.push(bar);
}
pub fn from_histogram(&mut self, his: &Histrogram) {
let bars = his.get_bars(); if bars.is_empty() {
return;
}
let num_points = bars[0].get_values().len();
if num_points == 0 {
return;
}
self.need_shift = false;
self.set_type(ErrorBarType::BaseOnY);
let mut sums = vec![0.0f32; num_points];
let mut mins = vec![f32::INFINITY; num_points];
let mut maxs = vec![f32::NEG_INFINITY; num_points];
for bar_group in bars {
let values = bar_group.get_values();
for (i, &val) in values.iter().enumerate().take(num_points) {
sums[i] += val;
if val < mins[i] {
mins[i] = val;
}
if val > maxs[i] {
maxs[i] = val;
}
}
}
let count = bars.len() as f32;
let color_index = his.get_color_index();
self.color_index = color_index + 1;
let config = self.gen_config();
let bar_vec: Vec<Bar> = (0..num_points)
.map(|i| {
Bar::new(
sums[i] / count,
mins[i],
maxs[i], config,
)
})
.collect();
self.bars = bar_vec;
}
pub fn set_type(&mut self, tp: ErrorBarType) {
self.err_type = tp;
}
}
impl ErrorBar {
fn draw_internal(&self, pixmap: &mut Pixmap, ts: &Transform, mode: &ErrorBarType) {
if self.bars.is_empty() {
return;
}
let mut paint = Paint::default();
let stroke = Stroke {
width: 2.,
..Stroke::default()
};
for (index, bar) in self.bars.iter().enumerate() {
if bar.config.is_hidden {
continue;
}
let index_pos = index as f32
+ if self.need_shift {
1.5 } else {
Self::OFFSET as f32
};
let (mut start, mut end, mut mean) = match mode {
ErrorBarType::BaseOnY => (
Point::from_xy(index_pos, bar.min),
Point::from_xy(index_pos, bar.max),
Point::from_xy(index_pos, bar.mean),
),
ErrorBarType::BaseOnX => (
Point::from_xy(bar.min, index_pos),
Point::from_xy(bar.max, index_pos),
Point::from_xy(bar.mean, index_pos),
),
};
ts.map_point(&mut start);
ts.map_point(&mut end);
ts.map_point(&mut mean);
let [r, g, b, a] = bar.config.color;
paint.set_color_rgba8(r, g, b, a);
paint.anti_alias = true;
if let Some(circle) = PathBuilder::from_circle(mean.x, mean.y, 6.) {
pixmap.fill_path(
&circle,
&paint,
tiny_skia::FillRule::Winding,
Transform::identity(),
None,
);
}
let mut pb = PathBuilder::new();
pb.move_to(start.x, start.y);
pb.line_to(end.x, end.y);
let offset = 4.;
match mode {
ErrorBarType::BaseOnY => {
pb.move_to(start.x - offset, start.y);
pb.line_to(start.x + offset, start.y);
pb.move_to(end.x - offset, end.y);
pb.line_to(end.x + offset, end.y);
}
ErrorBarType::BaseOnX => {
pb.move_to(start.x, start.y - offset);
pb.line_to(start.x, start.y + offset);
pb.move_to(end.x, end.y - offset);
pb.line_to(end.x, end.y + offset);
}
}
if let Some(path) = pb.finish() {
pixmap.stroke_path(&path, &paint, &stroke, Transform::identity(), None);
}
}
}
fn bound_x(&self) -> Option<Bound> {
if self.bars.is_empty() {
return None;
}
let y_min: f32 = Self::OFFSET as f32;
let y_max = (self.bars.len() + Self::OFFSET) as f32;
let mut x_min = f32::INFINITY;
let mut x_max = f32::NEG_INFINITY;
for bar in self.bars.iter() {
x_min = x_min.min(bar.min);
x_max = x_max.max(bar.max);
}
Some(Bound {
x_min,
x_max,
y_min,
y_max,
})
}
fn bound_y(&self) -> Option<Bound> {
if self.bars.is_empty() {
return None;
}
let x_min: f32 = if self.need_shift { Self::OFFSET } else { 0 } as f32;
let x_max = (self.bars.len() + if self.need_shift { Self::OFFSET } else { 0 }) as f32;
let mut y_min = f32::INFINITY;
let mut y_max = f32::NEG_INFINITY;
for bar in self.bars.iter() {
y_min = y_min.min(bar.min);
y_max = y_max.max(bar.max);
}
Some(Bound {
x_min,
x_max,
y_min,
y_max,
})
}
}
impl Drawable for ErrorBar {
fn draw(&self, pixmap: &mut tiny_skia::Pixmap, ts: &tiny_skia::Transform) {
self.draw_internal(pixmap, ts, &self.err_type);
}
fn bound(&self) -> Option<crate::drawable::Bound> {
match self.err_type {
ErrorBarType::BaseOnX => self.bound_x(),
ErrorBarType::BaseOnY => self.bound_y(),
}
}
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]) {}
}