use tabs, not spaces

This commit is contained in:
Simon Gardling 2022-03-10 09:01:21 -05:00
parent f851b365ab
commit dd6d8c80b8
11 changed files with 1417 additions and 1417 deletions

14
TODO.md
View File

@ -1,14 +1,14 @@
## TODO: ## TODO:
1. Multiple functions in one graph. 1. Multiple functions in one graph.
- Backend support - Backend support
- Integrals between functions (too hard to implement, maybe will shelve) - Integrals between functions (too hard to implement, maybe will shelve)
- UI - UI
- Different colors (kinda) - Different colors (kinda)
2. Rerwite of function parsing code 2. Rerwite of function parsing code
- Non `y=` functions. - Non `y=` functions.
3. Smart display of graph 3. Smart display of graph
- Display of intersections between functions - Display of intersections between functions
- Caching of roots and extrema - Caching of roots and extrema
4. Fix integral line 4. Fix integral line
5. re-add euler's number (well it works if you use capital e like `E^x`) 5. re-add euler's number (well it works if you use capital e like `E^x`)
6. allow constants in min/max integral input (like pi or euler's number) 6. allow constants in min/max integral input (like pi or euler's number)

View File

@ -8,21 +8,21 @@ cargo test
#apply optimizations via wasm-opt #apply optimizations via wasm-opt
wasm_opt() { wasm_opt() {
wasm-opt -Oz -o pkg/ytbn_graphing_software_bg_2.wasm pkg/ytbn_graphing_software_bg.wasm wasm-opt -Oz -o pkg/ytbn_graphing_software_bg_2.wasm pkg/ytbn_graphing_software_bg.wasm
mv pkg/ytbn_graphing_software_bg_2.wasm pkg/ytbn_graphing_software_bg.wasm mv pkg/ytbn_graphing_software_bg_2.wasm pkg/ytbn_graphing_software_bg.wasm
} }
if test "$1" == "" || test "$1" == "release"; then if test "$1" == "" || test "$1" == "release"; then
wasm-pack build --target web --release --no-typescript wasm-pack build --target web --release --no-typescript
echo "Binary size (pre-wasm_opt): $(du -sb pkg/ytbn_graphing_software_bg.wasm)" echo "Binary size (pre-wasm_opt): $(du -sb pkg/ytbn_graphing_software_bg.wasm)"
wasm_opt #apply wasm optimizations wasm_opt #apply wasm optimizations
echo "Binary size (pre-strip): $(du -sb pkg/ytbn_graphing_software_bg.wasm)" echo "Binary size (pre-strip): $(du -sb pkg/ytbn_graphing_software_bg.wasm)"
llvm-strip --strip-all pkg/ytbn_graphing_software_bg.wasm llvm-strip --strip-all pkg/ytbn_graphing_software_bg.wasm
elif test "$1" == "debug"; then elif test "$1" == "debug"; then
wasm-pack build --target web --debug --no-typescript wasm-pack build --target web --debug --no-typescript
else else
echo "ERROR: build.sh, argument invalid" echo "ERROR: build.sh, argument invalid"
exit 1 exit 1
fi fi
mkdir tmp mkdir tmp

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,100 +1,100 @@
use eframe::{ use eframe::{
egui::{ egui::{
plot::{BarChart, Line, PlotUi, Points, Value, Values}, plot::{BarChart, Line, PlotUi, Points, Value, Values},
widgets::plot::Bar, widgets::plot::Bar,
}, },
epaint::Color32, epaint::Color32,
}; };
use crate::misc::digits_precision; use crate::misc::digits_precision;
#[derive(Clone)] #[derive(Clone)]
pub struct FunctionOutput { pub struct FunctionOutput {
pub(crate) back: Option<Vec<Value>>, pub(crate) back: Option<Vec<Value>>,
pub(crate) integral: Option<(Vec<Bar>, f64)>, pub(crate) integral: Option<(Vec<Bar>, f64)>,
pub(crate) derivative: Option<Vec<Value>>, pub(crate) derivative: Option<Vec<Value>>,
pub(crate) extrema: Option<Vec<Value>>, pub(crate) extrema: Option<Vec<Value>>,
pub(crate) roots: Option<Vec<Value>>, pub(crate) roots: Option<Vec<Value>>,
} }
impl FunctionOutput { impl FunctionOutput {
pub fn new_empty() -> Self { pub fn new_empty() -> Self {
Self { Self {
back: None, back: None,
integral: None, integral: None,
derivative: None, derivative: None,
extrema: None, extrema: None,
roots: None, roots: None,
} }
} }
pub fn invalidate_whole(&mut self) { pub fn invalidate_whole(&mut self) {
self.back = None; self.back = None;
self.integral = None; self.integral = None;
self.derivative = None; self.derivative = None;
self.extrema = None; self.extrema = None;
self.roots = None; self.roots = None;
} }
pub fn invalidate_back(&mut self) { self.back = None; } pub fn invalidate_back(&mut self) { self.back = None; }
pub fn invalidate_integral(&mut self) { self.integral = None; } pub fn invalidate_integral(&mut self) { self.integral = None; }
pub fn invalidate_derivative(&mut self) { self.derivative = None; } pub fn invalidate_derivative(&mut self) { self.derivative = None; }
pub fn invalidate_points(&mut self) { pub fn invalidate_points(&mut self) {
self.extrema = None; self.extrema = None;
self.roots = None; self.roots = None;
} }
pub fn display( pub fn display(
&self, plot_ui: &mut PlotUi, func_str: &str, derivative_str: &str, step: f64, &self, plot_ui: &mut PlotUi, func_str: &str, derivative_str: &str, step: f64,
derivative_enabled: bool, derivative_enabled: bool,
) -> f64 { ) -> f64 {
plot_ui.line( plot_ui.line(
Line::new(Values::from_values(self.back.clone().unwrap())) Line::new(Values::from_values(self.back.clone().unwrap()))
.color(Color32::RED) .color(Color32::RED)
.name(func_str), .name(func_str),
); );
if derivative_enabled { if derivative_enabled {
if let Some(derivative_data) = self.derivative.clone() { if let Some(derivative_data) = self.derivative.clone() {
plot_ui.line( plot_ui.line(
Line::new(Values::from_values(derivative_data)) Line::new(Values::from_values(derivative_data))
.color(Color32::GREEN) .color(Color32::GREEN)
.name(derivative_str), .name(derivative_str),
); );
} }
} }
if let Some(extrema_data) = self.extrema.clone() { if let Some(extrema_data) = self.extrema.clone() {
plot_ui.points( plot_ui.points(
Points::new(Values::from_values(extrema_data)) Points::new(Values::from_values(extrema_data))
.color(Color32::YELLOW) .color(Color32::YELLOW)
.name("Extrema") .name("Extrema")
.radius(5.0), .radius(5.0),
); );
} }
if let Some(roots_data) = self.roots.clone() { if let Some(roots_data) = self.roots.clone() {
plot_ui.points( plot_ui.points(
Points::new(Values::from_values(roots_data)) Points::new(Values::from_values(roots_data))
.color(Color32::LIGHT_BLUE) .color(Color32::LIGHT_BLUE)
.name("Root") .name("Root")
.radius(5.0), .radius(5.0),
); );
} }
if let Some(integral_data) = self.integral.clone() { if let Some(integral_data) = self.integral.clone() {
plot_ui.bar_chart( plot_ui.bar_chart(
BarChart::new(integral_data.0) BarChart::new(integral_data.0)
.color(Color32::BLUE) .color(Color32::BLUE)
.width(step), .width(step),
); );
digits_precision(integral_data.1, 8) digits_precision(integral_data.1, 8)
} else { } else {
f64::NAN f64::NAN
} }
} }
} }

View File

@ -8,27 +8,27 @@ mod misc;
mod parsing; mod parsing;
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(target_arch = "wasm32")] { if #[cfg(target_arch = "wasm32")] {
use misc::log_helper; use misc::log_helper;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
#[global_allocator] #[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
#[wasm_bindgen(start)] #[wasm_bindgen(start)]
pub fn start() -> Result<(), wasm_bindgen::JsValue> { pub fn start() -> Result<(), wasm_bindgen::JsValue> {
log_helper("Initializing..."); log_helper("Initializing...");
// Used in order to hook into `panic!()` to log in the browser's console // Used in order to hook into `panic!()` to log in the browser's console
log_helper("Initializing panic hooks..."); log_helper("Initializing panic hooks...");
std::panic::set_hook(Box::new(console_error_panic_hook::hook)); std::panic::set_hook(Box::new(console_error_panic_hook::hook));
log_helper("Initialized panic hooks!"); log_helper("Initialized panic hooks!");
log_helper("Finished initializing!"); log_helper("Finished initializing!");
log_helper("Starting App..."); log_helper("Starting App...");
let app = egui_app::MathApp::default(); let app = egui_app::MathApp::default();
eframe::start_web("canvas", Box::new(app)) eframe::start_web("canvas", Box::new(app))
} }
} }
} }

View File

@ -9,11 +9,11 @@ mod parsing;
// 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"))]
fn main() { fn main() {
let app = egui_app::MathApp::default(); let app = egui_app::MathApp::default();
let options = eframe::NativeOptions { let options = eframe::NativeOptions {
transparent: true, transparent: true,
drag_and_drop_support: true, drag_and_drop_support: true,
..Default::default() ..Default::default()
}; };
eframe::run_native(Box::new(app), options); eframe::run_native(Box::new(app), options);
} }

View File

@ -1,92 +1,92 @@
cfg_if::cfg_if! { cfg_if::cfg_if! {
if #[cfg(target_arch = "wasm32")] { if #[cfg(target_arch = "wasm32")] {
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
#[wasm_bindgen] #[wasm_bindgen]
extern "C" { extern "C" {
// Use `js_namespace` here to bind `console.log(..)` instead of just // Use `js_namespace` here to bind `console.log(..)` instead of just
// `log(..)` // `log(..)`
#[wasm_bindgen(js_namespace = console)] #[wasm_bindgen(js_namespace = console)]
fn log(s: &str); fn log(s: &str);
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn log_helper(s: &str) { pub fn log_helper(s: &str) {
log(s); log(s);
} }
#[allow(dead_code)] #[allow(dead_code)]
#[allow(unused_variables)] #[allow(unused_variables)]
pub fn debug_log(s: &str) { pub fn debug_log(s: &str) {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
log(s); log(s);
} }
} else { } else {
#[allow(dead_code)] #[allow(dead_code)]
pub fn log_helper(s: &str) { pub fn log_helper(s: &str) {
println!("{}", s); println!("{}", s);
} }
#[allow(dead_code)] #[allow(dead_code)]
#[allow(unused_variables)] #[allow(unused_variables)]
pub fn debug_log(s: &str) { pub fn debug_log(s: &str) {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
println!("{}", s); println!("{}", s);
} }
} }
} }
pub struct SteppedVector { pub struct SteppedVector {
data: Vec<f64>, data: Vec<f64>,
min: f64, min: f64,
max: f64, max: f64,
step: f64, step: f64,
} }
impl SteppedVector { impl SteppedVector {
pub fn get_index(&self, x: f64) -> Option<usize> { pub fn get_index(&self, x: f64) -> Option<usize> {
if (x > self.max) | (self.min > x) { if (x > self.max) | (self.min > x) {
return None; return None;
} }
// Should work.... // Should work....
let possible_i = ((x + self.min) / self.step) as usize; let possible_i = ((x + self.min) / self.step) as usize;
if self.data[possible_i] == x { if self.data[possible_i] == x {
Some(possible_i) Some(possible_i)
} else { } else {
None None
} }
// Not really needed as the above code should handle everything // Not really needed as the above code should handle everything
/* /*
for (i, ele) in self.data.iter().enumerate() { for (i, ele) in self.data.iter().enumerate() {
if ele > &x { if ele > &x {
return None; return None;
} else if &x == ele { } else if &x == ele {
return Some(i); return Some(i);
} }
} }
None None
*/ */
} }
} }
impl From<Vec<f64>> for SteppedVector { impl From<Vec<f64>> for SteppedVector {
// Note: input `data` is assumed to be sorted from min to max // Note: input `data` is assumed to be sorted from min to max
fn from(data: Vec<f64>) -> SteppedVector { fn from(data: Vec<f64>) -> SteppedVector {
let max = data[0]; let max = data[0];
let min = data[data.len() - 1]; let min = data[data.len() - 1];
let step = (max - min).abs() / ((data.len() - 1) as f64); let step = (max - min).abs() / ((data.len() - 1) as f64);
SteppedVector { SteppedVector {
data, data,
min, min,
max, max,
step, step,
} }
} }
} }
// Rounds f64 to specific number of digits // Rounds f64 to specific number of digits
pub fn digits_precision(x: f64, digits: usize) -> f64 { pub fn digits_precision(x: f64, digits: usize) -> f64 {
let large_number: f64 = 10.0_f64.powf(digits as f64); let large_number: f64 = 10.0_f64.powf(digits as f64);
(x * large_number).round() / large_number (x * large_number).round() / large_number
} }

View File

@ -1,54 +1,54 @@
use exmex::prelude::*; use exmex::prelude::*;
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref EMPTY_FUNCTION: FlatEx<f64> = exmex::parse::<f64>("0/0").unwrap(); static ref EMPTY_FUNCTION: FlatEx<f64> = exmex::parse::<f64>("0/0").unwrap();
} }
#[derive(Clone)] #[derive(Clone)]
pub struct BackingFunction { pub struct BackingFunction {
function: FlatEx<f64>, function: FlatEx<f64>,
derivative_1: FlatEx<f64>, derivative_1: FlatEx<f64>,
derivative_2: FlatEx<f64>, derivative_2: FlatEx<f64>,
} }
impl BackingFunction { impl BackingFunction {
pub fn new(func_str: &str) -> Self { pub fn new(func_str: &str) -> Self {
let function = exmex::parse::<f64>(func_str).unwrap(); let function = exmex::parse::<f64>(func_str).unwrap();
let derivative_1 = function let derivative_1 = function
.partial(0) .partial(0)
.unwrap_or_else(|_| EMPTY_FUNCTION.clone()); .unwrap_or_else(|_| EMPTY_FUNCTION.clone());
let derivative_2 = function let derivative_2 = function
.partial_iter([0, 0].iter()) .partial_iter([0, 0].iter())
.unwrap_or_else(|_| EMPTY_FUNCTION.clone()); .unwrap_or_else(|_| EMPTY_FUNCTION.clone());
Self { Self {
function, function,
derivative_1, derivative_1,
derivative_2, derivative_2,
} }
} }
pub fn get_derivative_str(&self) -> String { pub fn get_derivative_str(&self) -> String {
String::from(self.derivative_1.unparse()).replace("{x}", "x") String::from(self.derivative_1.unparse()).replace("{x}", "x")
} }
pub fn get(&self, x: f64) -> f64 { self.function.eval(&[x]).unwrap_or(f64::NAN) } pub fn get(&self, x: f64) -> f64 { self.function.eval(&[x]).unwrap_or(f64::NAN) }
pub fn get_derivative_1(&self, x: f64) -> f64 { pub fn get_derivative_1(&self, x: f64) -> f64 {
self.derivative_1.eval(&[x]).unwrap_or(f64::NAN) self.derivative_1.eval(&[x]).unwrap_or(f64::NAN)
} }
pub fn get_derivative_2(&self, x: f64) -> f64 { pub fn get_derivative_2(&self, x: f64) -> f64 {
self.derivative_2.eval(&[x]).unwrap_or(f64::NAN) self.derivative_2.eval(&[x]).unwrap_or(f64::NAN)
} }
} }
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref VALID_VARIABLES: Vec<char> = "xXeEπ".chars().collect(); static ref VALID_VARIABLES: Vec<char> = "xXeEπ".chars().collect();
static ref LETTERS: Vec<char> = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" static ref LETTERS: Vec<char> = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
.chars() .chars()
.collect(); .collect();
static ref NUMBERS: Vec<char> = "0123456789".chars().collect(); static ref NUMBERS: Vec<char> = "0123456789".chars().collect();
} }
/* /*
@ -57,173 +57,173 @@ One limitation though, variables with multiple characters like `pi` cannot be mu
In the future I may want to completely rewrite this or implement this natively in exmex. In the future I may want to completely rewrite this or implement this natively in exmex.
*/ */
pub fn process_func_str(function_in: String) -> String { pub fn process_func_str(function_in: String) -> String {
let function = function_in.replace("log10(", "log(").replace("pi", "π"); // pi -> π and log10 -> log let function = function_in.replace("log10(", "log(").replace("pi", "π"); // pi -> π and log10 -> log
let function_chars: Vec<char> = function.chars().collect(); let function_chars: Vec<char> = function.chars().collect();
let mut output_string: String = String::new(); let mut output_string: String = String::new();
let mut prev_chars: Vec<char> = Vec::new(); let mut prev_chars: Vec<char> = Vec::new();
for c in function_chars { for c in function_chars {
let mut add_asterisk: bool = false; let mut add_asterisk: bool = false;
let prev_chars_len = prev_chars.len(); let prev_chars_len = prev_chars.len();
let prev_prev_prev_char = if prev_chars_len >= 3 { let prev_prev_prev_char = if prev_chars_len >= 3 {
*prev_chars.get(prev_chars_len - 3).unwrap() *prev_chars.get(prev_chars_len - 3).unwrap()
} else { } else {
' ' ' '
}; };
let prev_prev_char = if prev_chars_len >= 2 { let prev_prev_char = if prev_chars_len >= 2 {
*prev_chars.get(prev_chars_len - 2).unwrap() *prev_chars.get(prev_chars_len - 2).unwrap()
} else { } else {
' ' ' '
}; };
let prev_char = if prev_chars_len >= 1 { let prev_char = if prev_chars_len >= 1 {
*prev_chars.get(prev_chars_len - 1).unwrap() *prev_chars.get(prev_chars_len - 1).unwrap()
} else { } else {
' ' ' '
}; };
if (prev_prev_prev_char == 'l') if (prev_prev_prev_char == 'l')
&& (prev_prev_char == 'o') && (prev_prev_char == 'o')
&& (prev_char == 'g') && (prev_char == 'g')
&& (NUMBERS.contains(&c)) && (NUMBERS.contains(&c))
{ {
prev_chars.push(c); prev_chars.push(c);
output_string += &c.to_string(); output_string += &c.to_string();
continue; continue;
} }
let c_letters_var = LETTERS.contains(&c) | VALID_VARIABLES.contains(&c); let c_letters_var = LETTERS.contains(&c) | VALID_VARIABLES.contains(&c);
let prev_letters_var = VALID_VARIABLES.contains(&prev_char) | LETTERS.contains(&prev_char); let prev_letters_var = VALID_VARIABLES.contains(&prev_char) | LETTERS.contains(&prev_char);
if prev_char == ')' { if prev_char == ')' {
if (c == '(') | NUMBERS.contains(&c) | c_letters_var { if (c == '(') | NUMBERS.contains(&c) | c_letters_var {
add_asterisk = true; add_asterisk = true;
} }
} else if c == '(' { } else if c == '(' {
if (VALID_VARIABLES.contains(&prev_char) if (VALID_VARIABLES.contains(&prev_char)
| (')' == prev_char) | (')' == prev_char)
| NUMBERS.contains(&prev_char)) | NUMBERS.contains(&prev_char))
&& !LETTERS.contains(&prev_prev_char) && !LETTERS.contains(&prev_prev_char)
{ {
add_asterisk = true; add_asterisk = true;
} }
} else if NUMBERS.contains(&prev_char) { } else if NUMBERS.contains(&prev_char) {
if (c == '(') | c_letters_var { if (c == '(') | c_letters_var {
add_asterisk = true; add_asterisk = true;
} }
} else if LETTERS.contains(&c) { } else if LETTERS.contains(&c) {
if NUMBERS.contains(&prev_char) if NUMBERS.contains(&prev_char)
| (VALID_VARIABLES.contains(&prev_char) && VALID_VARIABLES.contains(&c)) | (VALID_VARIABLES.contains(&prev_char) && VALID_VARIABLES.contains(&c))
{ {
add_asterisk = true; add_asterisk = true;
} }
} else if (NUMBERS.contains(&c) | c_letters_var) && prev_letters_var { } else if (NUMBERS.contains(&c) | c_letters_var) && prev_letters_var {
add_asterisk = true; add_asterisk = true;
} }
if add_asterisk { if add_asterisk {
output_string += "*"; output_string += "*";
} }
prev_chars.push(c); prev_chars.push(c);
output_string += &c.to_string(); output_string += &c.to_string();
} }
output_string.replace("log(", "log10(") output_string.replace("log(", "log10(")
} }
// Tests function to make sure it's able to be parsed. Returns the string of the Error produced, or an empty string if it runs successfully. // Tests function to make sure it's able to be parsed. Returns the string of the Error produced, or an empty string if it runs successfully.
pub fn test_func(function_string: &str) -> Option<String> { pub fn test_func(function_string: &str) -> Option<String> {
let parse_result = exmex::parse::<f64>(function_string); let parse_result = exmex::parse::<f64>(function_string);
match parse_result { match parse_result {
Err(e) => Some(e.to_string()), Err(e) => Some(e.to_string()),
Ok(_) => { Ok(_) => {
let var_names = parse_result.unwrap().var_names().to_vec(); let var_names = parse_result.unwrap().var_names().to_vec();
if var_names != ["x"] { if var_names != ["x"] {
let var_names_not_x: Vec<String> = var_names let var_names_not_x: Vec<String> = var_names
.iter() .iter()
.filter(|ele| *ele != &"x".to_owned()) .filter(|ele| *ele != &"x".to_owned())
.cloned() .cloned()
.collect::<Vec<String>>(); .collect::<Vec<String>>();
return match var_names_not_x.len() { return match var_names_not_x.len() {
1 => { 1 => {
let var_name = &var_names_not_x[0]; let var_name = &var_names_not_x[0];
if var_name == "e" { if var_name == "e" {
Some(String::from( Some(String::from(
"If trying to use Euler's number, please use an uppercase E", "If trying to use Euler's number, please use an uppercase E",
)) ))
} else { } else {
Some(format!("Error: invalid variable: {}", var_name)) Some(format!("Error: invalid variable: {}", var_name))
} }
} }
_ => Some(format!("Error: invalid variables: {:?}", var_names_not_x)), _ => Some(format!("Error: invalid variables: {:?}", var_names_not_x)),
}; };
} }
None None
} }
} }
} }
// Used for testing: passes function to `add_asterisks` before running `test_func` // Used for testing: passes function to `add_asterisks` before running `test_func`
#[cfg(test)] #[cfg(test)]
fn test_func_helper(function_string: &str) -> Option<String> { fn test_func_helper(function_string: &str) -> Option<String> {
test_func(&process_func_str(function_string.to_string())) test_func(&process_func_str(function_string.to_string()))
} }
#[test] #[test]
fn test_func_test() { fn test_func_test() {
// These shouldn't fail // These shouldn't fail
assert!(test_func_helper("x^2").is_none()); assert!(test_func_helper("x^2").is_none());
assert!(test_func_helper("2x").is_none()); assert!(test_func_helper("2x").is_none());
// assert!(test_func_helper("e^x").is_none()); // need to fix!!! PR to exmex // assert!(test_func_helper("e^x").is_none()); // need to fix!!! PR to exmex
assert!(test_func_helper("E^x").is_none()); assert!(test_func_helper("E^x").is_none());
assert!(test_func_helper("log10(x)").is_none()); assert!(test_func_helper("log10(x)").is_none());
assert!(test_func_helper("xxxxx").is_none()); assert!(test_func_helper("xxxxx").is_none());
// Expect these to fail // Expect these to fail
assert!(test_func_helper("a").is_some()); assert!(test_func_helper("a").is_some());
assert!(test_func_helper("l^2").is_some()); assert!(test_func_helper("l^2").is_some());
assert!(test_func_helper("log222(x)").is_some()); assert!(test_func_helper("log222(x)").is_some());
assert!(test_func_helper("abcdef").is_some()); assert!(test_func_helper("abcdef").is_some());
} }
// Tests to make sure my cursed function works as intended // Tests to make sure my cursed function works as intended
#[test] #[test]
fn func_process_test() { fn func_process_test() {
assert_eq!(&process_func_str("2x".to_string()), "2*x"); assert_eq!(&process_func_str("2x".to_string()), "2*x");
assert_eq!(&process_func_str("x2".to_string()), "x*2"); assert_eq!(&process_func_str("x2".to_string()), "x*2");
assert_eq!(&process_func_str("x(1+3)".to_string()), "x*(1+3)"); assert_eq!(&process_func_str("x(1+3)".to_string()), "x*(1+3)");
assert_eq!(&process_func_str("(1+3)x".to_string()), "(1+3)*x"); assert_eq!(&process_func_str("(1+3)x".to_string()), "(1+3)*x");
assert_eq!(&process_func_str("sin(x)".to_string()), "sin(x)"); assert_eq!(&process_func_str("sin(x)".to_string()), "sin(x)");
assert_eq!(&process_func_str("2sin(x)".to_string()), "2*sin(x)"); assert_eq!(&process_func_str("2sin(x)".to_string()), "2*sin(x)");
assert_eq!(&process_func_str("max(x)".to_string()), "max(x)"); assert_eq!(&process_func_str("max(x)".to_string()), "max(x)");
assert_eq!(&process_func_str("2e^x".to_string()), "2*e^x"); assert_eq!(&process_func_str("2e^x".to_string()), "2*e^x");
assert_eq!(&process_func_str("2max(x)".to_string()), "2*max(x)"); assert_eq!(&process_func_str("2max(x)".to_string()), "2*max(x)");
assert_eq!(&process_func_str("cos(sin(x))".to_string()), "cos(sin(x))"); assert_eq!(&process_func_str("cos(sin(x))".to_string()), "cos(sin(x))");
assert_eq!(&process_func_str("x^(1+2x)".to_string()), "x^(1+2*x)"); assert_eq!(&process_func_str("x^(1+2x)".to_string()), "x^(1+2*x)");
assert_eq!( assert_eq!(
&process_func_str("(x+2)x(1+3)".to_string()), &process_func_str("(x+2)x(1+3)".to_string()),
"(x+2)*x*(1+3)" "(x+2)*x*(1+3)"
); );
assert_eq!(&process_func_str("(x+2)(1+3)".to_string()), "(x+2)*(1+3)"); assert_eq!(&process_func_str("(x+2)(1+3)".to_string()), "(x+2)*(1+3)");
assert_eq!(&process_func_str("xxx".to_string()), "x*x*x"); assert_eq!(&process_func_str("xxx".to_string()), "x*x*x");
assert_eq!(&process_func_str("eee".to_string()), "e*e*e"); assert_eq!(&process_func_str("eee".to_string()), "e*e*e");
assert_eq!(&process_func_str("pi(x+2)".to_string()), "π*(x+2)"); assert_eq!(&process_func_str("pi(x+2)".to_string()), "π*(x+2)");
assert_eq!(&process_func_str("(x)pi".to_string()), "(x)*π"); assert_eq!(&process_func_str("(x)pi".to_string()), "(x)*π");
assert_eq!(&process_func_str("2e".to_string()), "2*e"); assert_eq!(&process_func_str("2e".to_string()), "2*e");
assert_eq!(&process_func_str("2log10(x)".to_string()), "2*log10(x)"); assert_eq!(&process_func_str("2log10(x)".to_string()), "2*log10(x)");
assert_eq!(&process_func_str("2log(x)".to_string()), "2*log10(x)"); assert_eq!(&process_func_str("2log(x)".to_string()), "2*log10(x)");
assert_eq!(&process_func_str("x!".to_string()), "x!"); assert_eq!(&process_func_str("x!".to_string()), "x!");
assert_eq!(&process_func_str("pipipipipipi".to_string()), "π*π*π*π*π*π"); assert_eq!(&process_func_str("pipipipipipi".to_string()), "π*π*π*π*π*π");
assert_eq!(&process_func_str("10pi".to_string()), "10*π"); assert_eq!(&process_func_str("10pi".to_string()), "10*π");
assert_eq!(&process_func_str("pi10".to_string()), "π*10"); assert_eq!(&process_func_str("pi10".to_string()), "π*10");
// Need to fix these checks, maybe I need to rewrite the whole asterisk adding system... (or just implement these changes into meval-rs, idk) // Need to fix these checks, maybe I need to rewrite the whole asterisk adding system... (or just implement these changes into meval-rs, idk)
// assert_eq!(&add_asterisks("emax(x)".to_string()), "e*max(x)"); // assert_eq!(&add_asterisks("emax(x)".to_string()), "e*max(x)");
// assert_eq!(&add_asterisks("pisin(x)".to_string()), "pi*sin(x)"); // assert_eq!(&add_asterisks("pisin(x)".to_string()), "pi*sin(x)");
} }

View File

@ -1,31 +1,31 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<!-- Disable zooming: --> <!-- Disable zooming: -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no" />
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>(Yet-to-be-named) Graphing Software</title> <title>(Yet-to-be-named) Graphing Software</title>
<link rel="stylesheet" href="style.css" /> <link rel="stylesheet" href="style.css" />
</head> </head>
<body> <body>
<noscript>Please enable Javascript, this page uses both WebAssembly and Javascript to run.</noscript> <noscript>Please enable Javascript, this page uses both WebAssembly and Javascript to run.</noscript>
<canvas id="canvas"></canvas> <canvas id="canvas"></canvas>
<div class="centered" id="loading"> <div class="centered" id="loading">
<p style="font-size: 16px;"> <p style="font-size: 16px;">
Loading… Loading…
</p> </p>
<div class="lds-dual-ring"></div> <div class="lds-dual-ring"></div>
</div> </div>
<script type="module"> <script type="module">
import init, { start } from "./ytbn_graphing_software.js"; import init, { start } from "./ytbn_graphing_software.js";
async function run() { async function run() {
await init(); await init();
} }
run(); run();
</script> </script>
</body> </body>
</html> </html>

View File

@ -1,81 +1,81 @@
html { html {
/* Remove touch delay: */ /* Remove touch delay: */
touch-action: manipulation; touch-action: manipulation;
} }
body { body {
/* Light mode background color for what is not covered by the egui canvas, /* Light mode background color for what is not covered by the egui canvas,
or where the egui canvas is translucent. */ or where the egui canvas is translucent. */
background: #909090; background: #909090;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
body { body {
/* Dark mode background color for what is not covered by the egui canvas, /* Dark mode background color for what is not covered by the egui canvas,
or where the egui canvas is translucent. */ or where the egui canvas is translucent. */
background: #404040; background: #404040;
} }
} }
/* Allow canvas to fill entire web page: */ /* Allow canvas to fill entire web page: */
html, html,
body { body {
overflow: hidden; overflow: hidden;
margin: 0 !important; margin: 0 !important;
padding: 0 !important; padding: 0 !important;
} }
/* Position canvas in center-top: */ /* Position canvas in center-top: */
canvas { canvas {
margin-right: auto; margin-right: auto;
margin-left: auto; margin-left: auto;
display: block; display: block;
position: absolute; position: absolute;
top: 0%; top: 0%;
left: 50%; left: 50%;
transform: translate(-50%, 0%); transform: translate(-50%, 0%);
} }
.centered { .centered {
margin-right: auto; margin-right: auto;
margin-left: auto; margin-left: auto;
display: block; display: block;
position: absolute; position: absolute;
top: 50%; top: 50%;
left: 50%; left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
color: #f0f0f0; color: #f0f0f0;
font-size: 24px; font-size: 24px;
font-family: Ubuntu-Light, Helvetica, sans-serif; font-family: Ubuntu-Light, Helvetica, sans-serif;
text-align: center; text-align: center;
} }
/* ---------------------------------------------- */ /* ---------------------------------------------- */
/* Loading animation from https://loading.io/css/ */ /* Loading animation from https://loading.io/css/ */
.lds-dual-ring { .lds-dual-ring {
display: inline-block; display: inline-block;
width: 24px; width: 24px;
height: 24px; height: 24px;
} }
.lds-dual-ring:after { .lds-dual-ring:after {
content: " "; content: " ";
display: block; display: block;
width: 24px; width: 24px;
height: 24px; height: 24px;
margin: 0px; margin: 0px;
border-radius: 50%; border-radius: 50%;
border: 3px solid #fff; border: 3px solid #fff;
border-color: #fff transparent #fff transparent; border-color: #fff transparent #fff transparent;
animation: lds-dual-ring 1.2s linear infinite; animation: lds-dual-ring 1.2s linear infinite;
} }
@keyframes lds-dual-ring { @keyframes lds-dual-ring {
0% { 0% {
transform: rotate(0deg); transform: rotate(0deg);
} }
100% { 100% {
transform: rotate(360deg); transform: rotate(360deg);
} }
} }