From 8e6f304cab4b96779ae6c9c91b7354a1b2807c8e Mon Sep 17 00:00:00 2001 From: Simon Gardling Date: Fri, 4 Mar 2022 10:22:00 -0500 Subject: [PATCH] ability to plot integral as a line --- src/egui_app.rs | 62 +++++++++++++++++++++++++++++++++++++++++++++++-- src/function.rs | 62 ++++++++++++++++++++++++++++++------------------- 2 files changed, 98 insertions(+), 26 deletions(-) diff --git a/src/egui_app.rs b/src/egui_app.rs index 7ebac3d..ede57ea 100644 --- a/src/egui_app.rs +++ b/src/egui_app.rs @@ -11,10 +11,21 @@ use epi::{Frame, Storage}; use include_flate::flate; use instant::Duration; use shadow_rs::shadow; +use std::fmt::{self, Debug}; use std::ops::RangeInclusive; shadow!(build); +#[derive(PartialEq, Debug, Copy, Clone)] +enum DisplayIntegral { + Rectangles, + Plot, +} + +impl fmt::Display for DisplayIntegral { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) } +} + // Constant string that has a string containing information about the build. const BUILD_INFO: &str = formatc!( "Commit: {} ({})\nBuild Date: {}\nRust Channel: {}\nRust Version: {}", @@ -94,6 +105,11 @@ struct AppSettings { // Number of rectangles used to calculate integral pub integral_num: usize, + + // Stores whether or not the settings window is open + pub settings_open: bool, + + pub integral_display_type: DisplayIntegral, } impl Default for AppSettings { @@ -106,6 +122,8 @@ impl Default for AppSettings { integral_min_x: DEFAULT_MIN_X, integral_max_x: DEFAULT_MAX_X, integral_num: DEFAULT_INTEGRAL_NUM, + settings_open: false, + integral_display_type: DisplayIntegral::Rectangles, } } } @@ -345,6 +363,17 @@ impl epi::App for MathApp { self.settings.help_open = !self.settings.help_open; } + if ui + .add(Button::new("Settings")) + .on_hover_text(match self.settings.settings_open { + true => "Close Settings Window", + false => "Open Settings Window", + }) + .clicked() + { + self.settings.settings_open = !self.settings.settings_open; + } + if ui .add(Button::new("Info")) .on_hover_text(match self.settings.info_open { @@ -363,6 +392,29 @@ impl epi::App for MathApp { }); }); + // Help window with information for users + Window::new("Settings") + .default_pos([200.0, 200.0]) + .open(&mut self.settings.settings_open) + .resizable(false) + .collapsible(false) + .show(ctx, |ui| { + ComboBox::from_label("Integral Display") + .selected_text(self.settings.integral_display_type.to_string()) + .show_ui(ui, |ui| { + ui.selectable_value( + &mut self.settings.integral_display_type, + DisplayIntegral::Rectangles, + "Rectangles", + ); + ui.selectable_value( + &mut self.settings.integral_display_type, + DisplayIntegral::Plot, + "Line", + ); + }); + }); + // Help window with information for users Window::new("Help") .default_pos([200.0, 200.0]) @@ -439,8 +491,14 @@ impl epi::App for MathApp { } if let Some(bars_data) = bars { - let (bar_chart, area) = bars_data; - plot_ui.bar_chart(bar_chart.color(Color32::BLUE).width(step)); + let (integral_bar, integral_line, area) = bars_data; + match self.settings.integral_display_type { + DisplayIntegral::Rectangles => plot_ui + .bar_chart(integral_bar.color(Color32::BLUE).width(step)), + DisplayIntegral::Plot => { + plot_ui.line(integral_line.color(Color32::BLUE)) + } + } digits_precision(area, 8) } else { f64::NAN diff --git a/src/function.rs b/src/function.rs index c76ca34..529713e 100644 --- a/src/function.rs +++ b/src/function.rs @@ -29,7 +29,7 @@ pub struct Function { pixel_width: usize, back_cache: Option>, - front_cache: Option<(Vec, f64)>, + front_cache: Option<(Vec, Vec, f64)>, derivative_cache: Option>, pub(crate) integral: bool, @@ -161,7 +161,13 @@ impl Function { } } - pub fn run_back(&mut self) -> (Vec, Option<(Vec, f64)>, Option>) { + pub fn run_back( + &mut self, + ) -> ( + Vec, + Option<(Vec, Vec, f64)>, + Option>, + ) { let back_values: Vec = { if self.back_cache.is_none() { let resolution: f64 = @@ -203,11 +209,14 @@ impl Function { true => { if self.front_cache.is_none() { let (data, area) = self.integral_rectangles(); - self.front_cache = - Some((data.iter().map(|(x, y)| Bar::new(*x, *y)).collect(), area)); + self.front_cache = Some(( + data.iter().map(|(x, y, _)| Bar::new(*x, *y)).collect(), + data.iter().map(|(x, _, y)| Value::new(*x, *y)).collect(), + area, + )); } let cache = self.front_cache.as_ref().unwrap(); - Some((cache.0.clone(), cache.1)) + Some((cache.0.clone(), cache.1.clone(), cache.2)) } false => None, }; @@ -215,13 +224,17 @@ impl Function { (back_values, front_bars, derivative_values) } - pub fn run(&mut self) -> (Line, Option<(BarChart, f64)>, Option) { + pub fn run(&mut self) -> (Line, Option<(BarChart, Line, f64)>, Option) { let (back_values, front_data_option, derivative_option) = self.run_back(); ( Line::new(Values::from_values(back_values)), if let Some(front_data1) = front_data_option { - Some((BarChart::new(front_data1.0), front_data1.1)) + Some(( + BarChart::new(front_data1.0), + Line::new(Values::from_values(front_data1.1)), + front_data1.2, + )) } else { None }, @@ -234,7 +247,7 @@ impl Function { } // Creates and does the math for creating all the rectangles under the graph - fn integral_rectangles(&self) -> (Vec<(f64, f64)>, f64) { + fn integral_rectangles(&self) -> (Vec<(f64, f64, f64)>, f64) { if self.integral_min_x.is_nan() { panic!("integral_min_x is NaN") } else if self.integral_max_x.is_nan() { @@ -243,7 +256,8 @@ impl Function { let step = (self.integral_min_x - self.integral_max_x).abs() / (self.integral_num as f64); - let data2: Vec<(f64, f64)> = (1..=self.integral_num) + let mut area: f64 = 0.0; + let data2: Vec<(f64, f64, f64)> = (1..=self.integral_num) .map(|e| { let x: f64 = ((e as f64) * step) + self.integral_min_x; let step_offset = step * x.signum(); // store the offset here so it doesn't have to be calculated multiple times @@ -254,20 +268,20 @@ impl Function { false => (x2, x), }; - ( - x + (step_offset / 2.0), - match self.sum { - RiemannSum::Left => self.run_func(left_x), - RiemannSum::Right => self.run_func(right_x), - RiemannSum::Middle => { - (self.run_func(left_x) + self.run_func(right_x)) / 2.0 - } - }, - ) + let y = match self.sum { + RiemannSum::Left => self.run_func(left_x), + RiemannSum::Right => self.run_func(right_x), + RiemannSum::Middle => (self.run_func(left_x) + self.run_func(right_x)) / 2.0, + }; + + if !y.is_nan() { + area += y * step; + } + + (x + (step_offset / 2.0), y, area) }) - .filter(|(_, y)| !y.is_nan()) + .filter(|(_, y, _)| !y.is_nan()) .collect(); - let area: f64 = data2.iter().map(|(_, y)| y * step).sum(); // sum of all rectangles' areas (data2, area) } @@ -337,7 +351,7 @@ fn left_function_test() { 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); + assert_eq!(bars.clone().unwrap().2, 0.8720000000000001); let vec_bars = bars.unwrap().0; assert_eq!(vec_bars.len(), 10); } @@ -390,7 +404,7 @@ fn middle_function_test() { 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); + assert_eq!(bars.clone().unwrap().2, 0.9200000000000002); let vec_bars = bars.unwrap().0; assert_eq!(vec_bars.len(), 10); } @@ -443,7 +457,7 @@ fn right_function_test() { 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); + assert_eq!(bars.clone().unwrap().2, 0.9680000000000002); let vec_bars = bars.unwrap().0; assert_eq!(vec_bars.len(), 10); }