it builds! (still need to implement colors and multiple inputs)
This commit is contained in:
parent
0c538cb7fd
commit
9fded6856c
8
TODO.md
8
TODO.md
@ -3,4 +3,12 @@
|
|||||||
2. Proper support for dynamic chart display size.
|
2. Proper support for dynamic chart display size.
|
||||||
3. Fix (very rare) crashes in parsing strings (`TextDecoder.decode: Decoding failed.`)
|
3. Fix (very rare) crashes in parsing strings (`TextDecoder.decode: Decoding failed.`)
|
||||||
4. Multiple functions in one graph.
|
4. Multiple functions in one graph.
|
||||||
|
- Backend support
|
||||||
|
- Generation of data
|
||||||
|
- Management
|
||||||
|
- Handle by IDs
|
||||||
|
- UI
|
||||||
|
- Dynamically create inputs
|
||||||
|
- Different colors
|
||||||
|
- Better Handling of area and integrals
|
||||||
5. Non `y=` functions.
|
5. Non `y=` functions.
|
||||||
@ -1,130 +0,0 @@
|
|||||||
use crate::misc::{add_asterisks, Function};
|
|
||||||
|
|
||||||
pub enum UpdateType {
|
|
||||||
Full,
|
|
||||||
Front,
|
|
||||||
Back,
|
|
||||||
None,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manages Chart generation and caching of values
|
|
||||||
pub struct ChartManager {
|
|
||||||
function: Function,
|
|
||||||
min_x: f64,
|
|
||||||
max_x: f64,
|
|
||||||
min_x_back: f64,
|
|
||||||
max_x_back: f64,
|
|
||||||
num_interval: usize,
|
|
||||||
resolution: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ChartManager {
|
|
||||||
pub fn new(
|
|
||||||
func_str: String, min_x: f64, max_x: f64, min_x_back: f64, max_x_back: f64,
|
|
||||||
num_interval: usize, resolution: usize,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
function: Function::from_string(func_str),
|
|
||||||
min_x,
|
|
||||||
max_x,
|
|
||||||
min_x_back,
|
|
||||||
max_x_back,
|
|
||||||
num_interval,
|
|
||||||
resolution,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn draw_back(&mut self) -> Vec<(f64, f64)> {
|
|
||||||
let absrange = (self.max_x_back - self.min_x_back).abs();
|
|
||||||
let output: Vec<(f64, f64)> = (1..=self.resolution)
|
|
||||||
.map(|x| ((x as f64 / self.resolution as f64) * absrange) + self.min_x_back)
|
|
||||||
.map(|x| (x, self.function.run(x)))
|
|
||||||
.collect();
|
|
||||||
output
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn draw_front(&mut self) -> (Vec<(f64, f64)>, f64) {
|
|
||||||
self.integral_rectangles(self.get_step())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn get_step(&self) -> f64 { (self.max_x - self.min_x).abs() / (self.num_interval as f64) }
|
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
pub fn update(
|
|
||||||
&mut self, func_str_new: String, min_x: f64, max_x: f64, min_x_back: f64, max_x_back: f64,
|
|
||||||
num_interval: usize, resolution: usize,
|
|
||||||
) -> UpdateType {
|
|
||||||
let func_str: String = add_asterisks(func_str_new);
|
|
||||||
let update_func: bool = !self.function.str_compare(func_str.clone());
|
|
||||||
|
|
||||||
let update_back =
|
|
||||||
update_func | (min_x_back != self.min_x_back) | (max_x_back != self.max_x_back);
|
|
||||||
let update_front = update_func
|
|
||||||
| (min_x != self.min_x)
|
|
||||||
| (max_x != self.max_x)
|
|
||||||
| (self.resolution != resolution)
|
|
||||||
| (num_interval != self.num_interval);
|
|
||||||
|
|
||||||
if update_func {
|
|
||||||
self.function = Function::from_string(func_str);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.min_x = min_x;
|
|
||||||
self.max_x = max_x;
|
|
||||||
self.min_x_back = min_x_back;
|
|
||||||
self.max_x_back = max_x_back;
|
|
||||||
self.num_interval = num_interval;
|
|
||||||
self.resolution = resolution;
|
|
||||||
|
|
||||||
if update_back && update_front {
|
|
||||||
UpdateType::Full
|
|
||||||
} else if update_back {
|
|
||||||
UpdateType::Back
|
|
||||||
} else if update_front {
|
|
||||||
UpdateType::Front
|
|
||||||
} else {
|
|
||||||
UpdateType::None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates and does the math for creating all the rectangles under the graph
|
|
||||||
#[inline]
|
|
||||||
fn integral_rectangles(&self, step: f64) -> (Vec<(f64, f64)>, f64) {
|
|
||||||
let half_step = step / 2.0;
|
|
||||||
let data2: Vec<(f64, f64)> = (0..self.num_interval)
|
|
||||||
.map(|e| {
|
|
||||||
let x: f64 = ((e as f64) * step) + self.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.function.run_func(x);
|
|
||||||
let tmp2: f64 = self.function.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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
179
src/egui_app.rs
179
src/egui_app.rs
@ -1,7 +1,7 @@
|
|||||||
use std::ops::RangeInclusive;
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
use crate::chart_manager::{ChartManager, UpdateType};
|
use crate::function::Function;
|
||||||
use crate::misc::{digits_precision, test_func, Cache};
|
use crate::misc::{digits_precision, test_func};
|
||||||
use eframe::{egui, epi};
|
use eframe::{egui, epi};
|
||||||
use egui::plot::{Line, Plot, Value, Values};
|
use egui::plot::{Line, Plot, Value, Values};
|
||||||
use egui::widgets::plot::{Bar, BarChart};
|
use egui::widgets::plot::{Bar, BarChart};
|
||||||
@ -12,98 +12,60 @@ use git_version::git_version;
|
|||||||
const GIT_VERSION: &str = git_version!();
|
const GIT_VERSION: &str = git_version!();
|
||||||
|
|
||||||
// Sets some hard-coded limits to the application
|
// Sets some hard-coded limits to the application
|
||||||
const NUM_INTERVAL_RANGE: RangeInclusive<usize> = 10..=1000000;
|
const INTEGRAL_NUM_RANGE: RangeInclusive<usize> = 10..=1000000;
|
||||||
const MIN_X_TOTAL: f64 = -1000.0;
|
const MIN_X_TOTAL: f64 = -1000.0;
|
||||||
const MAX_X_TOTAL: f64 = 1000.0;
|
const MAX_X_TOTAL: f64 = 1000.0;
|
||||||
const X_RANGE: RangeInclusive<f64> = MIN_X_TOTAL..=MAX_X_TOTAL;
|
const X_RANGE: RangeInclusive<f64> = MIN_X_TOTAL..=MAX_X_TOTAL;
|
||||||
|
|
||||||
pub struct MathApp {
|
pub struct MathApp {
|
||||||
func_str: String,
|
functions: Vec<Function>,
|
||||||
min_x: f64,
|
min_x: f64,
|
||||||
max_x: f64,
|
max_x: f64,
|
||||||
|
|
||||||
// Currently really unused. But once fully implemented it will represent the full graph's min_x and max_x, being seperate from min_x and max_x for the intergral.
|
// Currently really unused. But once fully implemented it will represent the full graph's min_x and max_x, being seperate from min_x and max_x for the intergral.
|
||||||
min_x_graph: f64,
|
integral_min_x: f64,
|
||||||
max_x_graph: f64,
|
integral_max_x: f64,
|
||||||
|
|
||||||
num_interval: usize,
|
integral_num: usize,
|
||||||
resolution: usize,
|
|
||||||
chart_manager: ChartManager,
|
|
||||||
back_cache: Cache<Vec<Value>>,
|
|
||||||
front_cache: Cache<(Vec<Bar>, f64)>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MathApp {
|
impl Default for MathApp {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let def_func = "x^2".to_string();
|
let def_func_str = "x^2".to_string();
|
||||||
let def_min_x = -10.0;
|
let def_min_x = -10.0;
|
||||||
let def_max_x = 10.0;
|
let def_max_x = 10.0;
|
||||||
let def_interval: usize = 1000;
|
let def_interval: usize = 1000;
|
||||||
let def_resolution: usize = 10000;
|
|
||||||
|
let def_funcs: Vec<Function> = vec![Function::new(
|
||||||
|
def_func_str,
|
||||||
|
def_min_x,
|
||||||
|
def_max_x,
|
||||||
|
true,
|
||||||
|
Some(def_min_x),
|
||||||
|
Some(def_max_x),
|
||||||
|
Some(def_interval),
|
||||||
|
)];
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
func_str: def_func.clone(),
|
functions: def_funcs,
|
||||||
min_x: def_min_x,
|
min_x: def_min_x,
|
||||||
max_x: def_max_x,
|
max_x: def_max_x,
|
||||||
min_x_graph: def_min_x,
|
integral_min_x: def_min_x,
|
||||||
max_x_graph: def_max_x,
|
integral_max_x: def_max_x,
|
||||||
num_interval: def_interval,
|
integral_num: def_interval,
|
||||||
resolution: def_resolution,
|
|
||||||
chart_manager: ChartManager::new(
|
|
||||||
def_func,
|
|
||||||
def_min_x,
|
|
||||||
def_max_x,
|
|
||||||
def_min_x,
|
|
||||||
def_max_x,
|
|
||||||
def_interval,
|
|
||||||
def_resolution,
|
|
||||||
),
|
|
||||||
back_cache: Cache::new_empty(),
|
|
||||||
front_cache: Cache::new_empty(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MathApp {
|
impl MathApp {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn get_back(&mut self) -> Line {
|
pub fn get_step(&self) -> f64 {
|
||||||
let data = if self.back_cache.is_valid() {
|
(self.integral_min_x - self.integral_max_x).abs() / (self.integral_num as f64)
|
||||||
self.back_cache.get().clone()
|
|
||||||
} else {
|
|
||||||
let data = self.chart_manager.draw_back();
|
|
||||||
let data_values: Vec<Value> = data.iter().map(|(x, y)| Value::new(*x, *y)).collect();
|
|
||||||
self.back_cache.set(data_values.clone());
|
|
||||||
data_values
|
|
||||||
};
|
|
||||||
Line::new(Values::from_values(data)).color(Color32::RED)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn get_front(&mut self) -> (Vec<Bar>, f64) {
|
|
||||||
if self.front_cache.is_valid() {
|
|
||||||
let cache = self.front_cache.get();
|
|
||||||
let vec_bars: Vec<Bar> = cache.0.to_vec();
|
|
||||||
(vec_bars, cache.1)
|
|
||||||
} else {
|
|
||||||
let (data, area) = self.chart_manager.draw_front();
|
|
||||||
let bars: Vec<Bar> = data.iter().map(|(x, y)| Bar::new(*x, *y)).collect();
|
|
||||||
|
|
||||||
let output = (bars, area);
|
|
||||||
self.front_cache.set(output.clone());
|
|
||||||
output
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn get_data(&mut self) -> (Line, Vec<Bar>, f64) {
|
|
||||||
let (bars, area) = self.get_front();
|
|
||||||
(self.get_back(), bars, area)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl epi::App for MathApp {
|
impl epi::App for MathApp {
|
||||||
|
|
||||||
// The name of the program (displayed when running natively as the window title)
|
// The name of the program (displayed when running natively as the window title)
|
||||||
fn name(&self) -> &str { "Integral Demonstration" }
|
fn name(&self) -> &str { "Integral Demonstration" }
|
||||||
|
|
||||||
@ -118,16 +80,12 @@ impl epi::App for MathApp {
|
|||||||
#[inline]
|
#[inline]
|
||||||
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
|
||||||
let Self {
|
let Self {
|
||||||
func_str,
|
functions,
|
||||||
min_x,
|
min_x,
|
||||||
max_x,
|
max_x,
|
||||||
min_x_graph,
|
integral_min_x,
|
||||||
max_x_graph,
|
integral_max_x,
|
||||||
num_interval,
|
integral_num,
|
||||||
resolution,
|
|
||||||
chart_manager,
|
|
||||||
back_cache,
|
|
||||||
front_cache,
|
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
// Note: This Instant implementation does not show microseconds when using wasm.
|
// Note: This Instant implementation does not show microseconds when using wasm.
|
||||||
@ -146,21 +104,28 @@ impl epi::App for MathApp {
|
|||||||
ui.label("- signum, min, max");
|
ui.label("- signum, min, max");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let mut func_new_strings: Vec<String> = Vec::new();
|
||||||
let mut parse_error: String = "".to_string();
|
let mut parse_error: String = "".to_string();
|
||||||
egui::SidePanel::left("side_panel")
|
egui::SidePanel::left("side_panel")
|
||||||
.resizable(false)
|
.resizable(false)
|
||||||
.show(ctx, |ui| {
|
.show(ctx, |ui| {
|
||||||
ui.heading("Side Panel");
|
ui.heading("Side Panel");
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
for function in functions.iter() {
|
||||||
ui.label("Function: ");
|
let mut func_str = function.get_string();
|
||||||
ui.text_edit_singleline(func_str);
|
ui.horizontal(|ui| {
|
||||||
});
|
ui.label("Function: ");
|
||||||
|
ui.text_edit_singleline(&mut func_str);
|
||||||
|
});
|
||||||
|
|
||||||
let func_test_output = test_func(func_str.clone());
|
let func_test_output = test_func(func_str.clone());
|
||||||
if !func_test_output.is_empty() {
|
if !func_test_output.is_empty() {
|
||||||
parse_error = func_test_output;
|
parse_error += &func_test_output;
|
||||||
|
}
|
||||||
|
|
||||||
|
func_new_strings.push(func_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
let min_x_old = *min_x;
|
let min_x_old = *min_x;
|
||||||
let min_x_response =
|
let min_x_response =
|
||||||
ui.add(egui::Slider::new(min_x, X_RANGE.clone()).text("Min X"));
|
ui.add(egui::Slider::new(min_x, X_RANGE.clone()).text("Min X"));
|
||||||
@ -178,11 +143,11 @@ impl epi::App for MathApp {
|
|||||||
*min_x = -10.0;
|
*min_x = -10.0;
|
||||||
*max_x = 10.0;
|
*max_x = 10.0;
|
||||||
}
|
}
|
||||||
*min_x_graph = *min_x;
|
*integral_min_x = *min_x;
|
||||||
*max_x_graph = *max_x;
|
*integral_max_x = *max_x;
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.add(egui::Slider::new(num_interval, NUM_INTERVAL_RANGE).text("Interval"));
|
ui.add(egui::Slider::new(integral_num, INTEGRAL_NUM_RANGE).text("Interval"));
|
||||||
|
|
||||||
// Opensource and Licensing information
|
// Opensource and Licensing information
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
@ -210,30 +175,13 @@ impl epi::App for MathApp {
|
|||||||
ui.label(GIT_VERSION);
|
ui.label(GIT_VERSION);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
if parse_error.is_empty() {
|
let mut i: usize = 0;
|
||||||
let do_update = chart_manager.update(
|
for function in functions.iter_mut() {
|
||||||
func_str.clone(),
|
function.update(func_new_strings[i].clone(), *min_x, *max_x, true, Some(*integral_min_x), Some(*integral_max_x), Some(*integral_num));
|
||||||
*min_x,
|
i += 1;
|
||||||
*max_x,
|
|
||||||
*min_x_graph,
|
|
||||||
*max_x_graph,
|
|
||||||
*num_interval,
|
|
||||||
*resolution,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Invalidates caches according to what settings were changed
|
|
||||||
match do_update {
|
|
||||||
UpdateType::Full => {
|
|
||||||
back_cache.invalidate();
|
|
||||||
front_cache.invalidate();
|
|
||||||
}
|
}
|
||||||
UpdateType::Back => back_cache.invalidate(),
|
});
|
||||||
UpdateType::Front => front_cache.invalidate(),
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
egui::CentralPanel::default().show(ctx, |ui| {
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
if !parse_error.is_empty() {
|
if !parse_error.is_empty() {
|
||||||
@ -241,26 +189,33 @@ impl epi::App for MathApp {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (curve, bars, area) = self.get_data();
|
let step = self.get_step();
|
||||||
|
let mut area_list: Vec<f64> = Vec::new();
|
||||||
let bar_chart = BarChart::new(bars)
|
|
||||||
.color(Color32::BLUE)
|
|
||||||
.width(self.chart_manager.get_step());
|
|
||||||
|
|
||||||
Plot::new("plot")
|
Plot::new("plot")
|
||||||
.view_aspect(1.0)
|
.view_aspect(1.0)
|
||||||
.data_aspect(1.0)
|
.data_aspect(1.0)
|
||||||
.include_y(0)
|
.include_y(0)
|
||||||
.show(ui, |plot_ui| {
|
.show(ui, |plot_ui| {
|
||||||
plot_ui.line(curve);
|
for function in self.functions.iter_mut() {
|
||||||
plot_ui.bar_chart(bar_chart);
|
let output = function.run();
|
||||||
|
let back = output.get_back();
|
||||||
|
plot_ui.line(Line::new(Values::from_values(back)).color(Color32::RED));
|
||||||
|
|
||||||
|
if output.has_integral() {
|
||||||
|
let (bars, area) = output.get_front();
|
||||||
|
let bar_chart =
|
||||||
|
BarChart::new(bars.clone()).color(Color32::BLUE).width(step);
|
||||||
|
plot_ui.bar_chart(bar_chart);
|
||||||
|
area_list.push(digits_precision(area, 8))
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let duration = start.elapsed();
|
let duration = start.elapsed();
|
||||||
|
|
||||||
ui.label(format!(
|
ui.label(format!(
|
||||||
"Area: {} Took: {:?}",
|
"Area: {:?} Took: {:?}",
|
||||||
digits_precision(area, 8),
|
area_list.clone(),
|
||||||
duration
|
duration
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
|||||||
119
src/function.rs
119
src/function.rs
@ -1,9 +1,9 @@
|
|||||||
use meval::Expr;
|
use crate::misc::Cache;
|
||||||
use egui::plot::Value;
|
use egui::plot::Value;
|
||||||
use egui::widgets::plot::Bar;
|
use egui::widgets::plot::Bar;
|
||||||
|
use meval::Expr;
|
||||||
|
|
||||||
const RESOLUTION: f64 = 1000.0;
|
pub const RESOLUTION: f64 = 1000.0;
|
||||||
|
|
||||||
|
|
||||||
// Struct that stores and manages the output of a function
|
// Struct that stores and manages the output of a function
|
||||||
pub struct FunctionOutput {
|
pub struct FunctionOutput {
|
||||||
@ -16,16 +16,22 @@ pub struct FunctionOutput {
|
|||||||
|
|
||||||
impl FunctionOutput {
|
impl FunctionOutput {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(back: Vec<Value>, front: Option<(Vec<Bar>, f64)>) -> Self {
|
pub fn new(back: Vec<Value>, front: Option<(Vec<Bar>, f64)>) -> Self { Self { back, front } }
|
||||||
Self {
|
|
||||||
back,
|
#[inline]
|
||||||
front,
|
pub fn get_back(&self) -> Vec<Value> { self.back.clone() }
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn get_front(&self) -> (Vec<Bar>, f64) {
|
||||||
|
match &self.front {
|
||||||
|
Some(x) => (x.0.clone(), x.1.clone()),
|
||||||
|
None => panic!(""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn has_integral(&self) -> bool {
|
pub fn has_integral(&self) -> bool {
|
||||||
match self.front {
|
match &self.front {
|
||||||
Some(x) => true,
|
Some(x) => true,
|
||||||
None => false,
|
None => false,
|
||||||
}
|
}
|
||||||
@ -37,6 +43,7 @@ pub struct Function {
|
|||||||
func_str: String,
|
func_str: String,
|
||||||
min_x: f64,
|
min_x: f64,
|
||||||
max_x: f64,
|
max_x: f64,
|
||||||
|
|
||||||
back_cache: Cache<Vec<Value>>,
|
back_cache: Cache<Vec<Value>>,
|
||||||
front_cache: Cache<(Vec<Bar>, f64)>,
|
front_cache: Cache<(Vec<Bar>, f64)>,
|
||||||
|
|
||||||
@ -47,8 +54,10 @@ pub struct Function {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Function {
|
impl Function {
|
||||||
pub fn new(func_str: String, min_x: f64, max_x: f64, integral: bool, integral_min_x: Option<f64>, integral_max_x: Option<f64>, integral_num: Option<usize>) -> Self {
|
pub fn new(
|
||||||
|
func_str: String, min_x: f64, max_x: f64, integral: bool, integral_min_x: Option<f64>,
|
||||||
|
integral_max_x: Option<f64>, integral_num: Option<usize>,
|
||||||
|
) -> Self {
|
||||||
// Makes sure proper arguments are passed when integral is enabled
|
// Makes sure proper arguments are passed when integral is enabled
|
||||||
if integral {
|
if integral {
|
||||||
if integral_min_x.is_none() {
|
if integral_min_x.is_none() {
|
||||||
@ -80,7 +89,7 @@ impl Function {
|
|||||||
},
|
},
|
||||||
integral_num: match integral_num {
|
integral_num: match integral_num {
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
None => f64::NAN,
|
None => 0,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,15 +99,24 @@ impl Function {
|
|||||||
fn run_func(&self, x: f64) -> f64 { (self.function)(x) }
|
fn run_func(&self, x: f64) -> f64 { (self.function)(x) }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn update(&mut self, func_str: String, min_x: f64, max_x: f64, integral: bool, integral_min_x: Option<f64>, integral_max_x: Option<f64>, integral_num: Option<usize>) {
|
pub fn update(
|
||||||
|
&mut self, func_str: String, min_x: f64, max_x: f64, integral: bool,
|
||||||
|
integral_min_x: Option<f64>, integral_max_x: Option<f64>, integral_num: Option<usize>,
|
||||||
|
) {
|
||||||
// If the function string changes, just wipe and restart from scratch
|
// If the function string changes, just wipe and restart from scratch
|
||||||
if func_str != self.func_str {
|
if func_str != self.func_str {
|
||||||
*self = Self::new(func_str, min_x, max_x, integral, integral_min_x, integral_max_x, integral_num);
|
*self = Self::new(
|
||||||
|
func_str,
|
||||||
|
min_x,
|
||||||
|
max_x,
|
||||||
|
integral,
|
||||||
|
integral_min_x,
|
||||||
|
integral_max_x,
|
||||||
|
integral_num,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (min_x != self.min_x) | (max_x != self.max_x) {
|
if (min_x != self.min_x) | (max_x != self.max_x) {
|
||||||
self.back_cache.invalidate();
|
self.back_cache.invalidate();
|
||||||
self.min_x = min_x;
|
self.min_x = min_x;
|
||||||
@ -115,38 +133,81 @@ impl Function {
|
|||||||
panic!("Invalid arguments: integral_num is None, but integral is enabled.")
|
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)) {
|
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.invalidate();
|
self.front_cache.invalidate();
|
||||||
self.integral_min_x = integral_min_x.expect("");
|
self.integral_min_x = integral_min_x.expect("");
|
||||||
self.integral_max_x = integral_max_x.expect("");
|
self.integral_max_x = integral_max_x.expect("");
|
||||||
self.integral_num = integral_num.expect("");
|
self.integral_num = integral_num.expect("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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]
|
#[inline]
|
||||||
pub fn run(&mut self) -> FunctionOutput {
|
pub fn run(&mut self) -> FunctionOutput {
|
||||||
let absrange = (self.max_x - self.min_x).abs();
|
let front_values: Vec<Value> = match self.back_cache.is_valid() {
|
||||||
let output: Vec<(f64, f64)> = (1..=self.resolution)
|
false => {
|
||||||
.map(|x| ((x as f64 / RESOLUTION) * absrange) + self.min_x_back)
|
let absrange = (self.max_x - self.min_x).abs();
|
||||||
.map(|x| (x, self.function.run(x)))
|
let front_data: Vec<(f64, f64)> = (1..=(RESOLUTION as usize))
|
||||||
.collect();
|
.map(|x| ((x as f64 / RESOLUTION) * absrange) + self.min_x)
|
||||||
output
|
.map(|x| (x, self.run_func(x)))
|
||||||
|
.collect();
|
||||||
|
let output: Vec<Value> =
|
||||||
|
front_data.iter().map(|(x, y)| Value::new(*x, *y)).collect();
|
||||||
|
self.back_cache.set(output.clone());
|
||||||
|
output
|
||||||
|
}
|
||||||
|
true => self.back_cache.get().clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.integral {
|
||||||
|
let back_bars: (Vec<Bar>, f64) = match self.front_cache.is_valid() {
|
||||||
|
false => {
|
||||||
|
let (data, area) = self.integral_rectangles();
|
||||||
|
let bars: Vec<Bar> = data.iter().map(|(x, y)| Bar::new(*x, *y)).collect();
|
||||||
|
|
||||||
|
let output = (bars, area);
|
||||||
|
self.front_cache.set(output.clone());
|
||||||
|
output
|
||||||
|
}
|
||||||
|
true => {
|
||||||
|
let cache = self.front_cache.get();
|
||||||
|
let vec_bars: Vec<Bar> = cache.0.to_vec();
|
||||||
|
(vec_bars, cache.1)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
FunctionOutput::new(front_values, Some(back_bars))
|
||||||
|
} else {
|
||||||
|
FunctionOutput::new(front_values, None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn get_string(&self) -> String { self.func_str.clone() }
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn str_compare(&self, other_string: String) -> bool { self.func_str == other_string }
|
pub fn str_compare(&self, other_string: String) -> bool { self.func_str == other_string }
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn get_step(&self) -> f64 { (self.integral_min_x - self.integral_max_x).abs() / (self.num_interval as f64) }
|
|
||||||
|
|
||||||
// Creates and does the math for creating all the rectangles under the graph
|
// Creates and does the math for creating all the rectangles under the graph
|
||||||
#[inline]
|
#[inline]
|
||||||
fn integral_rectangles(&self, step: f64) -> (Vec<(f64, f64)>, f64) {
|
fn integral_rectangles(&self) -> (Vec<(f64, f64)>, f64) {
|
||||||
|
if !self.integral {
|
||||||
|
panic!("integral_rectangles called, but self.integral is false!");
|
||||||
|
}
|
||||||
|
let step = self.get_step();
|
||||||
|
|
||||||
let half_step = step / 2.0;
|
let half_step = step / 2.0;
|
||||||
let data2: Vec<(f64, f64)> = (0..self.num_interval)
|
let data2: Vec<(f64, f64)> = (0..self.integral_num)
|
||||||
.map(|e| {
|
.map(|e| {
|
||||||
let x: f64 = ((e as f64) * step) + self.integral_min_x;
|
let x: f64 = ((e as f64) * step) + self.integral_min_x;
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
#![allow(clippy::unused_unit)] // Fixes clippy keep complaining about wasm_bindgen
|
#![allow(clippy::unused_unit)] // Fixes clippy keep complaining about wasm_bindgen
|
||||||
#![allow(clippy::type_complexity)] // Clippy, my types are fine.
|
#![allow(clippy::type_complexity)] // Clippy, my types are fine.
|
||||||
|
|
||||||
mod chart_manager;
|
|
||||||
mod egui_app;
|
mod egui_app;
|
||||||
mod misc;
|
|
||||||
mod function;
|
mod function;
|
||||||
|
mod misc;
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
mod egui_app;
|
mod egui_app;
|
||||||
|
|
||||||
// These 2 are needed for rust-analyzer to work in vscode.
|
|
||||||
mod chart_manager;
|
|
||||||
mod misc;
|
|
||||||
mod function;
|
mod function;
|
||||||
|
mod misc;
|
||||||
|
|
||||||
// For running the program natively! (Because why not?)
|
// For running the program natively! (Because why not?)
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user