diff --git a/src/egui_app.rs b/src/egui_app.rs index ff3346c..e378d4f 100644 --- a/src/egui_app.rs +++ b/src/egui_app.rs @@ -205,7 +205,10 @@ impl MathApp { self.last_error = String::new(); for (i, function) in self.functions.iter_mut().enumerate() { let mut integral_toggle: bool = false; + let mut derivative_toggle: bool = false; let integral_enabled = function.integral; + let derivative_enabled = function.derivative; + // Entry for a function ui.horizontal(|ui| { ui.label("Function:"); @@ -216,6 +219,7 @@ impl MathApp { { remove_i = Some(i); } + if ui .add(Button::new("∫")) .on_hover_text(match integral_enabled { @@ -226,6 +230,18 @@ impl MathApp { { integral_toggle = true; } + + if ui + .add(Button::new("d/dx")) + .on_hover_text(match derivative_enabled { + true => "Calculate Derivative", + false => "Don't Calculate Derivative", + }) + .clicked() + { + derivative_toggle = true; + } + ui.text_edit_singleline(&mut self.func_strs[i]); }); @@ -235,15 +251,23 @@ impl MathApp { integral_enabled }; + let derivative: bool = if derivative_toggle { + !derivative_enabled + } else { + derivative_enabled + }; + if !self.func_strs[i].is_empty() { let proc_func_str = add_asterisks(self.func_strs[i].clone()); let func_test_output = test_func(proc_func_str.clone()); if let Some(test_output_value) = func_test_output { - self.last_error += &format!("(Function #{}) {}\n", i, test_output_value); + self.last_error += + &format!("(Function #{}) {}\n", i, test_output_value); } else { function.update( proc_func_str, integral, + derivative, Some(self.settings.integral_min_x), Some(self.settings.integral_max_x), Some(self.settings.integral_num), @@ -426,9 +450,13 @@ impl epi::App for MathApp { function.update_bounds(minx_bounds, maxx_bounds, available_width); - let (back_values, bars) = function.run(); + let (back_values, bars, derivative) = function.run(); plot_ui.line(back_values.color(Color32::RED)); + if let Some(derivative_data) = derivative { + plot_ui.line(derivative_data.color(Color32::GREEN)); + } + if let Some(bars_data) = bars { let (bar_chart, area) = bars_data; plot_ui.bar_chart(bar_chart.color(Color32::BLUE).width(step)); diff --git a/src/function.rs b/src/function.rs index eca99bb..1561b1c 100644 --- a/src/function.rs +++ b/src/function.rs @@ -30,8 +30,10 @@ pub struct Function { back_cache: Option>, front_cache: Option<(Vec, f64)>, + derivative_cache: Option>, pub(crate) integral: bool, + pub(crate) derivative: bool, integral_min_x: f64, integral_max_x: f64, integral_num: usize, @@ -41,6 +43,8 @@ pub struct Function { // x^2 function, set here so we don't have to regenerate it every time a new function is made fn default_function(x: f64) -> f64 { x.powi(2) } +const EPSILON: f64 = 5.0e-7; + impl Function { // Creates Empty Function instance pub fn empty() -> Self { @@ -52,7 +56,9 @@ impl Function { pixel_width: 100, back_cache: None, front_cache: None, + derivative_cache: None, integral: false, + derivative: false, integral_min_x: f64::NAN, integral_max_x: f64::NAN, integral_num: 0, @@ -64,7 +70,7 @@ impl Function { fn run_func(&self, x: f64) -> f64 { (self.function)(x) } pub fn update( - &mut self, func_str: String, integral: bool, integral_min_x: Option, + &mut self, func_str: String, integral: bool, derivative: bool, integral_min_x: Option, integral_max_x: Option, integral_num: Option, sum: Option, ) { // If the function string changes, just wipe and restart from scratch @@ -76,8 +82,10 @@ impl Function { }); self.back_cache = None; self.front_cache = None; + self.derivative_cache = None; } + self.derivative = derivative; self.integral = integral; // Makes sure proper arguments are passed when integral is enabled @@ -98,6 +106,7 @@ impl Function { 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.derivative_cache = None; self.min_x = min_x; self.max_x = max_x; self.pixel_width = pixel_width; @@ -123,15 +132,17 @@ impl Function { }) .collect(), ); + self.derivative_cache = None; // TODO: setup this caching system for derivatives } else { self.back_cache = None; + self.derivative_cache = None; self.min_x = min_x; self.max_x = max_x; self.pixel_width = pixel_width; } } - pub fn run_back(&mut self) -> (Vec, Option<(Vec, f64)>) { + pub fn run_back(&mut self) -> (Vec, Option<(Vec, f64)>, Option>) { let back_values: Vec = { if self.back_cache.is_none() { let resolution: f64 = @@ -147,6 +158,28 @@ impl Function { self.back_cache.as_ref().unwrap().clone() }; + let derivative_values: Option> = match self.derivative { + true => { + if self.derivative_cache.is_none() { + let back_cache = self.back_cache.as_ref().unwrap().clone(); + self.derivative_cache = Some( + back_cache + .iter() + .map(|ele| { + let x = ele.x; + let (x1, x2) = (x - EPSILON, x + EPSILON); + let (y1, y2) = (self.run_func(x1), self.run_func(x2)); + let slope = (y2 - y1) / (EPSILON * 2.0); + Value::new(x, slope) + }) + .collect(), + ); + } + Some(self.derivative_cache.as_ref().unwrap().clone()) + } + false => None, + }; + let front_bars = match self.integral { true => { if self.front_cache.is_none() { @@ -160,11 +193,11 @@ impl Function { false => None, }; - (back_values, front_bars) + (back_values, front_bars, derivative_values) } - pub fn run(&mut self) -> (Line, Option<(BarChart, f64)>) { - let (back_values, front_data_option) = self.run_back(); + pub fn run(&mut self) -> (Line, Option<(BarChart, f64)>, Option) { + let (back_values, front_data_option, derivative_option) = self.run_back(); ( Line::new(Values::from_values(back_values)), @@ -173,6 +206,11 @@ impl Function { } else { None }, + if let Some(derivative_data) = derivative_option { + Some(Line::new(Values::from_values(derivative_data))) + } else { + None + }, ) } @@ -243,7 +281,9 @@ fn left_function_test() { pixel_width: 10, back_cache: None, front_cache: None, + derivative_cache: None, integral: false, + derivative: false, integral_min_x: -1.0, integral_max_x: 1.0, integral_num: 10, @@ -251,7 +291,7 @@ fn left_function_test() { }; { - let (back_values, bars) = function.run_back(); + let (back_values, bars, _) = function.run_back(); assert!(bars.is_none()); assert_eq!(back_values.len(), 10); let back_values_tuple: Vec<(f64, f64)> = @@ -275,7 +315,7 @@ fn left_function_test() { { function = function.integral(true); - let (back_values, bars) = function.run_back(); + let (back_values, bars, _) = function.run_back(); assert!(bars.is_some()); assert_eq!(back_values.len(), 10); assert_eq!(bars.clone().unwrap().1, 0.8720000000000001); @@ -294,7 +334,9 @@ fn middle_function_test() { pixel_width: 10, back_cache: None, front_cache: None, + derivative_cache: None, integral: false, + derivative: false, integral_min_x: -1.0, integral_max_x: 1.0, integral_num: 10, @@ -302,7 +344,7 @@ fn middle_function_test() { }; { - let (back_values, bars) = function.run_back(); + let (back_values, bars, _) = function.run_back(); assert!(bars.is_none()); assert_eq!(back_values.len(), 10); let back_values_tuple: Vec<(f64, f64)> = @@ -326,7 +368,7 @@ fn middle_function_test() { { function = function.integral(true); - let (back_values, bars) = function.run_back(); + let (back_values, bars, _) = function.run_back(); assert!(bars.is_some()); assert_eq!(back_values.len(), 10); assert_eq!(bars.clone().unwrap().1, 0.9200000000000002); @@ -345,7 +387,9 @@ fn right_function_test() { pixel_width: 10, back_cache: None, front_cache: None, + derivative_cache: None, integral: false, + derivative: false, integral_min_x: -1.0, integral_max_x: 1.0, integral_num: 10, @@ -377,7 +421,7 @@ fn right_function_test() { { function = function.integral(true); - let (back_values, bars) = function.run_back(); + let (back_values, bars, _) = function.run_back(); assert!(bars.is_some()); assert_eq!(back_values.len(), 10); assert_eq!(bars.clone().unwrap().1, 0.9680000000000002);