use eframe::egui::{plot::Value, widgets::plot::Bar}; use meval::Expr; // Struct that stores and manages the output of a function pub struct FunctionOutput { // The actual line graph back: Vec, // Integral information front: Option<(Vec, f64)>, } impl FunctionOutput { #[inline] pub fn new(back: Vec, front: Option<(Vec, f64)>) -> Self { Self { back, front } } #[inline] pub fn get_back(&self) -> Vec { self.back.clone() } #[inline] pub fn get_front(&self) -> (Vec, f64) { match &self.front { Some(x) => (x.0.clone(), x.1), None => panic!(""), } } #[inline] pub fn has_integral(&self) -> bool { self.front.is_some() } } pub struct Function { function: Box f64>, pub(crate) func_str: String, min_x: f64, max_x: f64, pixel_width: usize, back_cache: Option>, front_cache: Option<(Vec, f64)>, integral: bool, integral_min_x: f64, integral_max_x: f64, integral_num: usize, } impl Clone for Function { fn clone(&self) -> Self { let expr: Expr = self.func_str.parse().unwrap(); let func = expr.bind("x").unwrap(); Self { function: Box::new(func), func_str: self.func_str.clone(), min_x: self.min_x.clone(), max_x: self.max_x.clone(), pixel_width: self.pixel_width.clone(), back_cache: self.back_cache.clone(), front_cache: self.front_cache.clone(), integral: self.integral.clone(), integral_min_x: self.integral_min_x.clone(), integral_max_x: self.integral_max_x.clone(), integral_num: self.integral_num.clone(), } } } impl Function { pub fn new( func_str: String, min_x: f64, max_x: f64, pixel_width: usize, integral: bool, integral_min_x: Option, integral_max_x: Option, integral_num: Option, ) -> Self { // Makes sure proper arguments are passed when integral is enabled if integral { if integral_min_x.is_none() { panic!("Invalid arguments: integral_min_x is None, but integral is enabled.") } else if integral_max_x.is_none() { panic!("Invalid arguments: integral_max_x is None, but integral is enabled.") } else if integral_num.is_none() { panic!("Invalid arguments: integral_num is None, but integral is enabled.") } } let expr: Expr = func_str.parse().unwrap(); let func = expr.bind("x").unwrap(); Self { function: Box::new(func), func_str, min_x, max_x, pixel_width, back_cache: None, front_cache: None, integral, integral_min_x: match integral_min_x { Some(x) => x, None => f64::NAN, }, integral_max_x: match integral_max_x { Some(x) => x, None => f64::NAN, }, integral_num: integral_num.unwrap_or(0), } } // Runs the internal function to get values #[inline] fn run_func(&self, x: f64) -> f64 { (self.function)(x) } #[inline(always)] pub fn update( &mut self, func_str: String, integral: bool, integral_min_x: Option, integral_max_x: Option, integral_num: Option, ) { if func_str.is_empty() { self.func_str = func_str; return; } // If the function string changes, just wipe and restart from scratch if func_str != self.func_str { *self = Self::new( func_str, self.min_x, self.max_x, self.pixel_width, integral, integral_min_x, integral_max_x, integral_num, ); return; } if integral != self.integral { self.integral = integral; } // Makes sure proper arguments are passed when integral is enabled if integral { if integral_min_x.is_none() { panic!("Invalid arguments: integral_min_x is None, but integral is enabled.") } else if integral_max_x.is_none() { panic!("Invalid arguments: integral_max_x is None, but integral is enabled.") } else if integral_num.is_none() { panic!("Invalid arguments: integral_num is None, but integral is enabled.") } if (integral_min_x != Some(self.integral_min_x)) | (integral_max_x != Some(self.integral_max_x)) | (integral_num != Some(self.integral_num)) { self.front_cache = None; self.integral_min_x = integral_min_x.expect(""); self.integral_max_x = integral_max_x.expect(""); self.integral_num = integral_num.expect(""); } } } #[inline(always)] pub fn update_bounds(&mut self, min_x: f64, max_x: f64, pixel_width: usize) { if pixel_width != self.pixel_width { self.back_cache = None; self.min_x = min_x; self.max_x = max_x; self.pixel_width = pixel_width; } else if ((min_x != self.min_x) | (max_x != self.max_x)) && self.back_cache.is_some() && false { println!("rebuilding cache"); let range_new: f64 = max_x.abs() + min_x.abs(); let resolution: f64 = (self.pixel_width as f64 / range_new) as f64; let movement_right = min_x > self.min_x; let mut new_back: Vec = self .back_cache .as_ref() .expect("") .clone() .iter() .filter(|ele| (ele.x >= min_x) && (min_x >= ele.x)) .map(|ele| *ele) .collect(); let x_to_go = match movement_right { true => ((self.max_x - max_x) * resolution) as usize, false => ((self.min_x - min_x) * resolution) as usize, }; new_back.append( &mut (1..x_to_go) .map(|x| (x as f64 / resolution as f64) + min_x) .map(|x| (x, self.run_func(x))) .map(|(x, y)| Value::new(x, y)) .collect(), ); self.back_cache = Some(new_back); } else { self.back_cache = None; self.min_x = min_x; self.max_x = max_x; self.pixel_width = pixel_width; } } #[inline] pub fn get_step(&self) -> f64 { (self.integral_min_x - self.integral_max_x).abs() / (self.integral_num as f64) } #[inline] pub fn is_integral(&self) -> bool { self.integral } #[inline(always)] pub fn run(&mut self) -> FunctionOutput { let front_values: Vec = match self.back_cache.is_some() { true => self.back_cache.as_ref().expect("").clone(), false => { let absrange = (self.max_x - self.min_x).abs(); let resolution: f64 = (self.pixel_width as f64 / absrange) as f64; let front_data: Vec = (1..=self.pixel_width) .map(|x| (x as f64 / resolution as f64) + self.min_x) .map(|x| (x, self.run_func(x))) .map(|(x, y)| Value::new(x, y)) .collect(); // println!("{} {}", front_data.len(), front_data.len() as f64/absrange); self.back_cache = Some(front_data.clone()); front_data } }; if self.integral { let back_bars: (Vec, f64) = match self.front_cache.is_some() { true => { let cache = self.front_cache.as_ref().expect(""); let vec_bars: Vec = cache.0.to_vec(); (vec_bars, cache.1) } false => { let (data, area) = self.integral_rectangles(); let bars: Vec = data.iter().map(|(x, y)| Bar::new(*x, *y)).collect(); let output = (bars, area); self.front_cache = Some(output.clone()); output } }; FunctionOutput::new(front_values, Some(back_bars)) } else { FunctionOutput::new(front_values, None) } } // Creates and does the math for creating all the rectangles under the graph fn integral_rectangles(&self) -> (Vec<(f64, f64)>, f64) { if !self.integral { panic!("integral_rectangles called, but self.integral is false!"); } if self.integral_min_x.is_nan() { panic!("integral_min_x is NaN") } if self.integral_max_x.is_nan() { panic!("integral_max_x is NaN") } let step = self.get_step(); let half_step = step / 2.0; let data2: Vec<(f64, f64)> = (0..self.integral_num) .map(|e| { let x: f64 = ((e as f64) * step) + self.integral_min_x; // Makes sure rectangles are properly handled on x values below 0 let x2: f64 = match x > 0.0 { true => x + step, false => x - step, }; let tmp1: f64 = self.run_func(x); let tmp2: f64 = self.run_func(x2); // Chooses the y value who's absolute value is the smallest let mut output = match tmp2.abs() > tmp1.abs() { true => (x, tmp1), false => (x2, tmp2), }; // Applies `half_step` in order to make the bar graph display properly if output.0 > 0.0 { output.0 += half_step; } else { output.0 -= half_step; } output }) .filter(|(_, y)| !y.is_nan()) .collect(); let area: f64 = data2.iter().map(|(_, y)| y * step).sum(); // sum of all rectangles' areas (data2, area) } }