use tiny_skia::{FillRule, Paint, PathBuilder, Pixmap, Point, Transform};
use crate::{
drawable::{Bound, Drawable},
primitive::Config,
};
pub struct Scatter {
name: String,
x: Vec<f32>,
y: Vec<f32>,
value: Option<Vec<f32>>,
forth_dim: Option<Vec<f32>>,
config: Config,
}
impl Scatter {
pub fn new(name: String, config: Config) -> Self {
Self {
name,
x: Vec::new(),
y: Vec::new(),
value: None,
forth_dim: None,
config,
}
}
pub fn set_x(&mut self, x: &[f32]) {
self.x = x.to_vec();
}
pub fn set_y(&mut self, y: &[f32]) {
if self.x.is_empty() {
return;
}
let n = self.x.len();
self.y = y.iter().take(n).cloned().collect();
}
pub fn set_value(&mut self, values: &[f32]) {
if self.x.is_empty() {
return;
}
let n = self.x.len();
self.value = Some(values.iter().take(n).cloned().collect());
}
pub fn set_forth_dim(&mut self, values: &[f32]) {
if self.x.is_empty() {
return;
}
let n = self.x.len();
self.forth_dim = Some(values.iter().take(n).cloned().collect());
}
pub fn change_y(&mut self, y: &[f32]) {
if self.x.is_empty() {
return;
}
let n = self.x.len();
self.y = y.iter().take(n).cloned().collect();
}
pub fn change_values(&mut self, values: &[f32]) {
if self.x.is_empty() {
return;
}
let n = self.x.len();
self.value = Some(values.iter().take(n).cloned().collect());
}
pub fn change_forth_dim(&mut self, values: &[f32]) {
if self.x.is_empty() {
return;
}
let n = self.x.len();
self.forth_dim = Some(values.iter().take(n).cloned().collect());
}
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() {
self.set_y(y);
return;
}
self.x = (0..=n).map(|v| v as f32 * step + x_start).collect();
self.set_y(y);
}
pub fn set_date_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 Drawable for Scatter {
fn draw(&self, pixmap: &mut Pixmap, ts: &Transform) {
if self.x.len() != self.y.len() || self.x.is_empty() {
return;
}
const RADIUS: f32 = 5.;
let mut paint = Paint::default();
let [r, g, b, a] = self.config.color;
paint.anti_alias = true;
let values;
if self.value.is_none() {
values = vec![1.0; self.x.len()];
} else {
values = self.value.as_ref().unwrap().clone();
}
let mean = values.iter().sum::<f32>() / values.len() as f32;
for i in 0..self.x.len() {
let mut center = Point::from_xy(self.x[i], self.y[i]);
paint.set_color_rgba8(r, g, b, a);
ts.map_point(&mut center);
let radius = (values[i] / mean).clamp(1., 6.) * RADIUS;
if let Some(circle) = PathBuilder::from_circle(center.x, center.y, radius) {
pixmap.fill_path(
&circle,
&paint,
FillRule::Winding,
Transform::identity(),
None,
);
}
}
}
fn bound(&self) -> Option<Bound> {
if self.x.is_empty() || self.y.is_empty() || self.x.len() != self.y.len() {
return None;
}
let padding = 1.0;
let mut x_min = f32::INFINITY;
let mut x_max = f32::NEG_INFINITY;
let mut y_min = f32::INFINITY;
let mut y_max = f32::NEG_INFINITY;
for (&xv, &yv) in self.x.iter().zip(self.y.iter()) {
x_min = x_min.min(xv);
x_max = x_max.max(xv);
y_min = y_min.min(yv);
y_max = y_max.max(yv);
}
Some(Bound {
x_min: if x_min == 0. { 0. } else { x_min - padding },
x_max: x_max + padding,
y_min: if y_min == 0. { 0. } else { y_min - padding },
y_max: y_max + padding,
})
}
fn name(&self) -> String {
self.name.clone()
}
fn get_color(&self) -> [u8; 4] {
self.config.color
}
fn set_color(&mut self, color: [u8; 4]) {
self.config.color = color;
}
}