use bevy::prelude::{Color, Font, Handle, Text, Text2dBundle, TextStyle, Transform, Vec2};
use bevy_prototype_lyon::{
entity::ShapeBundle,
prelude::{DrawMode, GeometryBuilder, Path, PathBuilder, StrokeMode},
};
use colorgrad::{Color as GradColor, CustomGradient, Gradient};
pub fn max_f32(slice: &[f32]) -> f32 {
slice
.iter()
.fold(0f32, |acc, x| if x - acc > 1e-8 { *x } else { acc })
}
pub fn min_f32(slice: &[f32]) -> f32 {
slice
.iter()
.fold(0f32, |acc, x| if x - acc <= 1e-8 { *x } else { acc })
}
fn std_normal(x: f32) -> f32 {
std::f32::consts::E.powf(-x.powi(2) / 2.) / (2. * std::f32::consts::PI).sqrt()
}
fn kde(x: f32, samples: &[f32], h: f32) -> f32 {
1. / (h * samples.len() as f32)
* samples
.iter()
.map(|x_i| std_normal((x - x_i) / h))
.sum::<f32>()
}
pub fn linspace(start: f32, stop: f32, nstep: u32) -> Vec<f32> {
let delta: f32 = (stop - start) / (nstep as f32 - 1.);
(0..(nstep)).map(|i| start + i as f32 * delta).collect()
}
enum PlottingState {
Zero,
Over { last_x: f32 },
}
pub fn plot_kde(samples: &[f32], n: u32, size: f32, xlimits: (f32, f32)) -> Option<Path> {
let center = size / 2.;
let anchors = linspace(-center, center, n);
if center.is_nan() {
return None;
}
if samples.is_empty() {
return None;
}
let mut path_builder = PathBuilder::new();
if samples.len() == 1 {
path_builder = plot_spike(path_builder, samples[0], xlimits, center);
} else {
let mut state = PlottingState::Zero;
path_builder.move_to(Vec2::new(anchors[0], 0.));
for (point_x, anchor_x) in linspace(xlimits.0, xlimits.1, n).iter().zip(anchors.iter()) {
let y = f32::max(kde(*point_x, samples, 1.06), 0.);
match state {
PlottingState::Zero => {
if y > 0. {
path_builder.move_to(Vec2::new(*anchor_x, y));
state = PlottingState::Over { last_x: *anchor_x };
}
}
PlottingState::Over { last_x } => {
path_builder.line_to(Vec2::new(*anchor_x, y));
if y == 0. {
path_builder.line_to(Vec2::new(last_x, 0.));
state = PlottingState::Zero;
}
}
}
}
if let PlottingState::Over { last_x } = state {
path_builder.line_to(Vec2::new(anchors[anchors.len() - 1], 0.));
path_builder.line_to(Vec2::new(last_x, 0.));
}
}
Some(path_builder.build())
}
pub fn plot_hist(samples: &[f32], bins: u32, size: f32, xlimits: (f32, f32)) -> Option<Path> {
let center = size / 2.;
let bins = u32::min(samples.len() as u32 / 2, bins);
let points = linspace(xlimits.0, xlimits.1, bins);
let anchors = linspace(-center, center, bins);
if center.is_nan() {
return None;
}
if samples.is_empty() {
return None;
}
let mut path_builder = PathBuilder::new();
if samples.len() == 1 {
path_builder = plot_spike(path_builder, samples[0], xlimits, center);
} else {
for ((anchor_a, anchor_b), (point_a, point_b)) in anchors.clone()[0..(anchors.len() - 1)]
.iter()
.zip(anchors[1..anchors.len()].iter())
.zip(
[0.].iter()
.chain(points.clone()[0..(points.len() - 1)].iter())
.zip(points[1..points.len()].iter()),
)
{
let y = samples
.iter()
.filter(|&&x| (x >= *point_a) & (x < *point_b))
.count();
if y == 0 {
continue;
}
path_builder.move_to(Vec2::new(*anchor_a, 0.));
path_builder.line_to(Vec2::new(*anchor_a, y as f32));
path_builder.line_to(Vec2::new(*anchor_b, y as f32));
path_builder.line_to(Vec2::new(*anchor_b, 0.));
}
}
Some(path_builder.build())
}
fn plot_spike(
mut path_builder: PathBuilder,
t: f32,
xlimits: (f32, f32),
center: f32,
) -> PathBuilder {
let x = lerp(t, xlimits.0, xlimits.1, -center, center);
const EPS: f32 = 2.0;
path_builder.move_to(Vec2::new(x - EPS, 0.));
path_builder.line_to(Vec2::new(x - EPS, 1.0));
path_builder.line_to(Vec2::new(x + EPS, 1.0));
path_builder.line_to(Vec2::new(x + EPS, 0.));
path_builder
}
pub fn plot_box_point(n_cond: usize, cond_index: usize) -> Path {
let box_size = 40.;
let box_center = if n_cond == 0 {
0.
} else {
let center = cond_index as f32 * box_size * 1.2;
center - n_cond as f32 * box_size * 1.2 / 2.
};
let mut path_builder = PathBuilder::new();
path_builder.move_to(Vec2::new(box_center - box_size / 2., 0.));
path_builder.line_to(Vec2::new(box_center + box_size / 2., 0.));
path_builder.line_to(Vec2::new(box_center + box_size / 2., box_size));
path_builder.line_to(Vec2::new(box_center - box_size / 2., box_size));
path_builder.line_to(Vec2::new(box_center - box_size / 2., 0.));
path_builder.build()
}
#[derive(Clone)]
pub struct ScaleBundle {
pub x_0: Text2dBundle,
pub y: Text2dBundle,
pub x_n: Text2dBundle,
}
impl ScaleBundle {
pub fn new(
minimum: f32,
maximum: f32,
mean: f32,
mean_pos: f32,
size: f32,
font: Handle<Font>,
font_size: f32,
color: Color,
) -> Self {
let x_0 = Text2dBundle {
text: Text::from_section(
format!("{:+.3e}", minimum),
TextStyle {
font: font.clone(),
font_size,
color,
},
),
transform: Transform::from_xyz(-size / 2. - font_size * 2., 0., 0.2),
..Default::default()
};
let x_n = Text2dBundle {
text: Text::from_section(
format!("{:+.3e}", maximum),
TextStyle {
font: font.clone(),
font_size,
color,
},
),
transform: Transform::from_xyz(size / 2., 0., 0.2),
..Default::default()
};
let y = Text2dBundle {
text: Text::from_section(
format!("{:+.3e}", mean),
TextStyle {
font,
font_size,
color,
},
),
transform: Transform::from_xyz(mean_pos, 0., 0.2),
..Default::default()
};
Self { x_0, y, x_n }
}
}
pub fn plot_line(size: f32, transform: Transform) -> ShapeBundle {
let mut path_builder = PathBuilder::new();
path_builder.move_to(Vec2::new(-size / 2., 0.));
path_builder.line_to(Vec2::new(size / 2., 0.));
let mut geom = GeometryBuilder::build_as(
&path_builder.build(),
DrawMode::Stroke(StrokeMode::color(Color::BLACK)),
transform,
);
geom.visibility = bevy::prelude::Visibility::INVISIBLE;
geom
}
pub fn plot_scales(samples: &[f32], size: f32, font: Handle<Font>, font_size: f32) -> ScaleBundle {
let mean: f32 = samples.iter().sum::<f32>() / samples.len() as f32;
let min = min_f32(samples);
let max = max_f32(samples);
let mean_pos = lerp(mean, min, max, -size / 2., size / 2.);
ScaleBundle::new(
min,
max,
mean,
mean_pos,
size,
font,
font_size,
Color::rgb(51. / 255., 78. / 255., 107. / 255.),
)
}
fn get_extreme(path: &Path, maximum: bool, x: bool) -> f32 {
let vec = &path
.0
.iter()
.map(|p| if x { p.to().x } else { p.to().y })
.chain(
path.0
.iter()
.map(|p| if x { p.from().x } else { p.from().y }),
)
.collect::<Vec<f32>>();
if maximum {
max_f32(vec)
} else {
min_f32(vec)
}
}
pub fn path_to_vec(path: &Path) -> Vec2 {
let first_point = Vec2::new(
get_extreme(path, false, true),
get_extreme(path, false, false),
);
let last_point = Vec2::new(
get_extreme(path, true, true),
get_extreme(path, true, false),
);
last_point - first_point
}
pub fn lerp(t: f32, min_1: f32, max_1: f32, min_2: f32, max_2: f32) -> f32 {
if t >= max_1 {
max_2
} else if t <= min_1 {
min_2
} else {
(t - min_1) / (max_1 - min_1) * (max_2 - min_2) + min_2
}
}
pub fn zero_lerp(t: f32, min_1: f32, max_1: f32, min_2: f32, max_2: f32) -> f32 {
let (t, min_1, max_1) = if (min_1 * max_1) > 0. {
(t, min_1, max_1)
} else if t > 0. {
(t, 0., max_1)
} else {
(t.abs(), 0., min_1.abs())
};
lerp(t, min_1, max_1, min_2, max_2)
}
fn to_grad(col: &bevy_egui::egui::color::Rgba) -> GradColor {
GradColor::from_linear_rgba(
col.r() as f64,
col.g() as f64,
col.b() as f64,
col.a() as f64,
)
}
pub fn from_grad_clamped(grad: &Gradient, t: f32, min_val: f32, max_val: f32) -> Color {
let t = f32::clamp(t, min_val, max_val) as f64;
let rgba = grad.at(t).to_linear_rgba();
Color::rgba(rgba.0 as f32, rgba.1 as f32, rgba.2 as f32, rgba.3 as f32)
}
pub fn build_grad(
zero: bool,
min_val: f32,
max_val: f32,
min_color: &bevy_egui::egui::color::Rgba,
max_color: &bevy_egui::egui::color::Rgba,
) -> colorgrad::Gradient {
let mut grad = CustomGradient::new();
if zero & ((min_val * max_val) < 0.) {
grad.colors(&[
to_grad(min_color),
to_grad(&bevy_egui::egui::color::Rgba::from_rgb(0.83, 0.83, 0.89)),
to_grad(max_color),
])
.domain(&[min_val as f64, 0., max_val as f64])
} else {
grad.colors(&[to_grad(min_color), to_grad(max_color)])
.domain(&[min_val as f64, max_val as f64])
}
.mode(colorgrad::BlendMode::Oklab)
.interpolation(colorgrad::Interpolation::CatmullRom)
.build()
.expect("no gradient")
}