ability to plot integral as a line

This commit is contained in:
Simon Gardling 2022-03-04 10:22:00 -05:00
parent d5cf8e5f6d
commit 8e6f304cab
2 changed files with 98 additions and 26 deletions

View File

@ -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

View File

@ -29,7 +29,7 @@ pub struct Function {
pixel_width: usize,
back_cache: Option<Vec<Value>>,
front_cache: Option<(Vec<Bar>, f64)>,
front_cache: Option<(Vec<Bar>, Vec<Value>, f64)>,
derivative_cache: Option<Vec<Value>>,
pub(crate) integral: bool,
@ -161,7 +161,13 @@ impl Function {
}
}
pub fn run_back(&mut self) -> (Vec<Value>, Option<(Vec<Bar>, f64)>, Option<Vec<Value>>) {
pub fn run_back(
&mut self,
) -> (
Vec<Value>,
Option<(Vec<Bar>, Vec<Value>, f64)>,
Option<Vec<Value>>,
) {
let back_values: Vec<Value> = {
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<Line>) {
pub fn run(&mut self) -> (Line, Option<(BarChart, Line, f64)>, Option<Line>) {
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);
}