use std::ops::{Bound, RangeBounds, RangeInclusive};
use egui::{lerp, Pos2, Shape, Stroke, Vec2};
use crate::transform::PlotBounds;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PlotPoint {
pub x: f64,
pub y: f64,
}
impl From<[f64; 2]> for PlotPoint {
#[inline]
fn from([x, y]: [f64; 2]) -> Self {
Self { x, y }
}
}
impl PlotPoint {
#[inline(always)]
pub fn new(x: impl Into<f64>, y: impl Into<f64>) -> Self {
Self {
x: x.into(),
y: y.into(),
}
}
#[inline(always)]
pub fn to_pos2(self) -> Pos2 {
Pos2::new(self.x as f32, self.y as f32)
}
#[inline(always)]
pub fn to_vec2(self) -> Vec2 {
Vec2::new(self.x as f32, self.y as f32)
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum LineStyle {
Solid,
Dotted { spacing: f32 },
Dashed { length: f32 },
}
impl LineStyle {
pub fn dashed_loose() -> Self {
Self::Dashed { length: 10.0 }
}
pub fn dashed_dense() -> Self {
Self::Dashed { length: 5.0 }
}
pub fn dotted_loose() -> Self {
Self::Dotted { spacing: 10.0 }
}
pub fn dotted_dense() -> Self {
Self::Dotted { spacing: 5.0 }
}
pub(super) fn style_line(
&self,
line: Vec<Pos2>,
mut stroke: Stroke,
highlight: bool,
shapes: &mut Vec<Shape>,
) {
match line.len() {
0 => {}
1 => {
let mut radius = stroke.width / 2.0;
if highlight {
radius *= 2f32.sqrt();
}
shapes.push(Shape::circle_filled(line[0], radius, stroke.color));
}
_ => {
match self {
Self::Solid => {
if highlight {
stroke.width *= 2.0;
}
shapes.push(Shape::line(line, stroke));
}
Self::Dotted { spacing } => {
let mut radius = stroke.width;
if highlight {
radius *= 2f32.sqrt();
}
shapes.extend(Shape::dotted_line(&line, stroke.color, *spacing, radius));
}
Self::Dashed { length } => {
if highlight {
stroke.width *= 2.0;
}
let golden_ratio = (5.0_f32.sqrt() - 1.0) / 2.0; shapes.extend(Shape::dashed_line(
&line,
stroke,
*length,
length * golden_ratio,
));
}
}
}
}
}
}
impl std::fmt::Display for LineStyle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Solid => write!(f, "Solid"),
Self::Dotted { spacing } => write!(f, "Dotted({spacing} px)"),
Self::Dashed { length } => write!(f, "Dashed({length} px)"),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Orientation {
Horizontal,
Vertical,
}
impl Default for Orientation {
fn default() -> Self {
Self::Vertical
}
}
pub enum PlotPoints {
Owned(Vec<PlotPoint>),
Generator(ExplicitGenerator),
}
impl Default for PlotPoints {
fn default() -> Self {
Self::Owned(Vec::new())
}
}
impl From<[f64; 2]> for PlotPoints {
fn from(coordinate: [f64; 2]) -> Self {
Self::new(vec![coordinate])
}
}
impl From<Vec<[f64; 2]>> for PlotPoints {
fn from(coordinates: Vec<[f64; 2]>) -> Self {
Self::new(coordinates)
}
}
impl FromIterator<[f64; 2]> for PlotPoints {
fn from_iter<T: IntoIterator<Item = [f64; 2]>>(iter: T) -> Self {
Self::Owned(iter.into_iter().map(|point| point.into()).collect())
}
}
impl PlotPoints {
pub fn new(points: Vec<[f64; 2]>) -> Self {
Self::from_iter(points)
}
pub fn points(&self) -> &[PlotPoint] {
match self {
Self::Owned(points) => points.as_slice(),
Self::Generator(_) => &[],
}
}
pub fn from_explicit_callback(
function: impl Fn(f64) -> f64 + 'static,
x_range: impl RangeBounds<f64>,
points: usize,
) -> Self {
let start = match x_range.start_bound() {
Bound::Included(x) | Bound::Excluded(x) => *x,
Bound::Unbounded => f64::NEG_INFINITY,
};
let end = match x_range.end_bound() {
Bound::Included(x) | Bound::Excluded(x) => *x,
Bound::Unbounded => f64::INFINITY,
};
let x_range = start..=end;
let generator = ExplicitGenerator {
function: Box::new(function),
x_range,
points,
};
Self::Generator(generator)
}
pub fn from_parametric_callback(
function: impl Fn(f64) -> (f64, f64),
t_range: impl RangeBounds<f64>,
points: usize,
) -> Self {
let start = match t_range.start_bound() {
Bound::Included(x) => x,
Bound::Excluded(_) => unreachable!(),
Bound::Unbounded => panic!("The range for parametric functions must be bounded!"),
};
let end = match t_range.end_bound() {
Bound::Included(x) | Bound::Excluded(x) => x,
Bound::Unbounded => panic!("The range for parametric functions must be bounded!"),
};
let last_point_included = matches!(t_range.end_bound(), Bound::Included(_));
let increment = if last_point_included {
(end - start) / (points - 1) as f64
} else {
(end - start) / points as f64
};
(0..points)
.map(|i| {
let t = start + i as f64 * increment;
function(t).into()
})
.collect()
}
pub fn from_ys_f32(ys: &[f32]) -> Self {
ys.iter()
.enumerate()
.map(|(i, &y)| [i as f64, y as f64])
.collect()
}
pub fn from_ys_f64(ys: &[f64]) -> Self {
ys.iter().enumerate().map(|(i, &y)| [i as f64, y]).collect()
}
pub(crate) fn is_empty(&self) -> bool {
match self {
Self::Owned(points) => points.is_empty(),
Self::Generator(_) => false,
}
}
pub(super) fn generate_points(&mut self, x_range: RangeInclusive<f64>) {
if let Self::Generator(generator) = self {
*self = Self::range_intersection(&x_range, &generator.x_range)
.map(|intersection| {
let increment =
(intersection.end() - intersection.start()) / (generator.points - 1) as f64;
(0..generator.points)
.map(|i| {
let x = intersection.start() + i as f64 * increment;
let y = (generator.function)(x);
[x, y]
})
.collect()
})
.unwrap_or_default();
}
}
fn range_intersection(
range1: &RangeInclusive<f64>,
range2: &RangeInclusive<f64>,
) -> Option<RangeInclusive<f64>> {
let start = range1.start().max(*range2.start());
let end = range1.end().min(*range2.end());
(start < end).then_some(start..=end)
}
pub(super) fn bounds(&self) -> PlotBounds {
match self {
Self::Owned(points) => {
let mut bounds = PlotBounds::NOTHING;
for point in points {
bounds.extend_with(point);
}
bounds
}
Self::Generator(generator) => generator.estimate_bounds(),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum MarkerShape {
Circle,
Diamond,
Square,
Cross,
Plus,
Up,
Down,
Left,
Right,
Asterisk,
}
impl MarkerShape {
pub fn all() -> impl ExactSizeIterator<Item = Self> {
[
Self::Circle,
Self::Diamond,
Self::Square,
Self::Cross,
Self::Plus,
Self::Up,
Self::Down,
Self::Left,
Self::Right,
Self::Asterisk,
]
.iter()
.copied()
}
}
pub enum PlotGeometry<'a> {
None,
Points(&'a [PlotPoint]),
Rects,
}
pub struct ExplicitGenerator {
function: Box<dyn Fn(f64) -> f64>,
x_range: RangeInclusive<f64>,
points: usize,
}
impl ExplicitGenerator {
fn estimate_bounds(&self) -> PlotBounds {
let mut bounds = PlotBounds::NOTHING;
let mut add_x = |x: f64| {
if x.is_finite() {
bounds.extend_with_x(x);
}
let y = (self.function)(x);
if y.is_finite() {
bounds.extend_with_y(y);
}
};
let min_x = *self.x_range.start();
let max_x = *self.x_range.end();
add_x(min_x);
add_x(max_x);
if min_x.is_finite() && max_x.is_finite() {
const N: u32 = 8;
for i in 1..N {
let t = i as f64 / (N - 1) as f64;
let x = lerp(min_x..=max_x, t);
add_x(x);
}
} else {
for x in [-1, 0, 1] {
let x = x as f64;
if min_x <= x && x <= max_x {
add_x(x);
}
}
}
bounds
}
}
pub struct ClosestElem {
pub index: usize,
pub dist_sq: f32,
}