code and user experience improvements
This commit is contained in:
parent
0f081efa6a
commit
95b57f711c
28
src/lib.rs
28
src/lib.rs
@ -30,7 +30,7 @@ pub struct ChartManager {
|
|||||||
min_y: f32,
|
min_y: f32,
|
||||||
max_y: f32,
|
max_y: f32,
|
||||||
num_interval: usize,
|
num_interval: usize,
|
||||||
resolution: i32,
|
resolution: usize,
|
||||||
back_cache: Cache<Vec<(f32, f32)>>,
|
back_cache: Cache<Vec<(f32, f32)>>,
|
||||||
front_cache: Cache<(Vec<(f32, f32, f32)>, f32)>,
|
front_cache: Cache<(Vec<(f32, f32, f32)>, f32)>,
|
||||||
}
|
}
|
||||||
@ -39,7 +39,7 @@ pub struct ChartManager {
|
|||||||
impl ChartManager {
|
impl ChartManager {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
func_str: String, min_x: f32, max_x: f32, min_y: f32, max_y: f32, num_interval: usize,
|
func_str: String, min_x: f32, max_x: f32, min_y: f32, max_y: f32, num_interval: usize,
|
||||||
resolution: i32,
|
resolution: usize,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
func_str,
|
func_str,
|
||||||
@ -63,6 +63,7 @@ impl ChartManager {
|
|||||||
func
|
func
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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: String) -> String {
|
pub fn test_func(function_string: String) -> String {
|
||||||
let new_func_str: String = add_asterisks(function_string.clone());
|
let new_func_str: String = add_asterisks(function_string.clone());
|
||||||
let expr_result = new_func_str.parse();
|
let expr_result = new_func_str.parse();
|
||||||
@ -87,13 +88,6 @@ impl ChartManager {
|
|||||||
"".to_string()
|
"".to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recommends a possible solution to an error from method `test_func`
|
|
||||||
pub fn error_recommend(error_string: String) -> String {
|
|
||||||
match error_string.as_str() {
|
|
||||||
_ => "Make sure you're using proper syntax! Check the 'Frequent issues' for possible fixes."
|
|
||||||
}.to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn draw(
|
fn draw(
|
||||||
&mut self, element: HtmlCanvasElement, dark_mode: bool
|
&mut self, element: HtmlCanvasElement, dark_mode: bool
|
||||||
@ -192,7 +186,7 @@ impl ChartManager {
|
|||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn update(
|
pub fn update(
|
||||||
&mut self, canvas: HtmlCanvasElement, func_str_new: String, min_x: f32, max_x: f32, min_y: f32,
|
&mut self, canvas: HtmlCanvasElement, func_str_new: String, min_x: f32, max_x: f32, min_y: f32,
|
||||||
max_y: f32, num_interval: usize, resolution: i32, dark_mode: bool
|
max_y: f32, num_interval: usize, resolution: usize, dark_mode: bool
|
||||||
) -> Result<ChartOutput, JsValue> {
|
) -> Result<ChartOutput, JsValue> {
|
||||||
let func_str: String = add_asterisks(func_str_new);
|
let func_str: String = add_asterisks(func_str_new);
|
||||||
|
|
||||||
@ -204,20 +198,6 @@ impl ChartManager {
|
|||||||
| (max_y != self.max_y);
|
| (max_y != self.max_y);
|
||||||
|
|
||||||
|
|
||||||
if 0 > resolution {
|
|
||||||
panic!("resolution cannot be less than 0");
|
|
||||||
}
|
|
||||||
|
|
||||||
if underlying_update {
|
|
||||||
if min_x >= max_x {
|
|
||||||
panic!("min_x is greater than (or equal to) than max_x!");
|
|
||||||
}
|
|
||||||
|
|
||||||
if min_y >= max_y {
|
|
||||||
panic!("min_y is greater than (or equal to) than max_y!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if underlying_update | (self.resolution != resolution) {
|
if underlying_update | (self.resolution != resolution) {
|
||||||
self.back_cache.invalidate();
|
self.back_cache.invalidate();
|
||||||
}
|
}
|
||||||
|
|||||||
29
src/misc.rs
29
src/misc.rs
@ -2,9 +2,11 @@ use wasm_bindgen::prelude::*;
|
|||||||
|
|
||||||
pub type DrawResult<T> = Result<T, Box<dyn std::error::Error>>;
|
pub type DrawResult<T> = Result<T, Box<dyn std::error::Error>>;
|
||||||
|
|
||||||
// EXTREMELY Janky function that tries to put asterisks in the proper places to be parsed. This is so cursed
|
// EXTREMELY Janky function that tries to put asterisks in the proper places to be parsed. This is so cursed. But it works, and I hopefully won't ever have to touch it again.
|
||||||
|
// One limitation though, variables with multiple characters like `pi` cannot be multiplied (like `pipipipi` won't result in `pi*pi*pi*pi`). But that's such a niche use case (and that same thing could be done by using exponents) that it doesn't really matter.
|
||||||
pub fn add_asterisks(function_in: String) -> String {
|
pub fn add_asterisks(function_in: String) -> String {
|
||||||
let function = function_in.replace("log10(", "log("); // replace log10 with log
|
let function = function_in.replace("log10(", "log("); // replace log10 with log
|
||||||
|
let valid_variables: Vec<char> = "xe".chars().collect();
|
||||||
let letters: Vec<char>= "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".chars().collect();
|
let letters: Vec<char>= "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".chars().collect();
|
||||||
let numbers: Vec<char> = "0123456789".chars().collect();
|
let numbers: Vec<char> = "0123456789".chars().collect();
|
||||||
let function_chars: Vec<char> = function.chars().collect();
|
let function_chars: Vec<char> = function.chars().collect();
|
||||||
@ -12,6 +14,7 @@ pub fn add_asterisks(function_in: String) -> String {
|
|||||||
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.clone() {
|
for c in function_chars.clone() {
|
||||||
|
let mut add_asterisk: bool = false;
|
||||||
let prev_chars_len = prev_chars.len();
|
let prev_chars_len = prev_chars.len();
|
||||||
let curr_i: usize = func_chars_len-prev_chars_len;
|
let curr_i: usize = func_chars_len-prev_chars_len;
|
||||||
|
|
||||||
@ -41,32 +44,37 @@ pub fn add_asterisks(function_in: String) -> String {
|
|||||||
let prev_pi = (prev_prev_char == 'p') && (prev_char == 'i');
|
let prev_pi = (prev_prev_char == 'p') && (prev_char == 'i');
|
||||||
let for_pi = (for_char == 'i') && (c == 'p');
|
let for_pi = (for_char == 'i') && (c == 'p');
|
||||||
|
|
||||||
|
|
||||||
if prev_char == ')' {
|
if prev_char == ')' {
|
||||||
if (c == '(') | numbers.contains(&c) | letters.contains(&c) {
|
if (c == '(') | numbers.contains(&c) | letters.contains(&c) {
|
||||||
output_string += "*";
|
add_asterisk = true;
|
||||||
}
|
}
|
||||||
} else if c == '(' {
|
} else if c == '(' {
|
||||||
if ((prev_char == 'x') | (prev_char == 'e') | (prev_char == ')') | numbers.contains(&prev_char)) && !letters.contains(&prev_prev_char) {
|
if (valid_variables.contains(&prev_char) | (prev_char == ')') | numbers.contains(&prev_char)) && !letters.contains(&prev_prev_char) {
|
||||||
output_string += "*";
|
add_asterisk = true;
|
||||||
} else if prev_pi {
|
} else if prev_pi {
|
||||||
output_string += "*";
|
add_asterisk = true;
|
||||||
}
|
}
|
||||||
} else if numbers.contains(&prev_char) {
|
} else if numbers.contains(&prev_char) {
|
||||||
if (c == '(') | letters.contains(&c) | for_pi {
|
if (c == '(') | letters.contains(&c) | for_pi {
|
||||||
output_string += "*";
|
add_asterisk = true;
|
||||||
}
|
}
|
||||||
} else if letters.contains(&c) {
|
} else if letters.contains(&c) {
|
||||||
if numbers.contains(&prev_char) {
|
if numbers.contains(&prev_char) {
|
||||||
output_string += "*";
|
add_asterisk = true;
|
||||||
} else if ((c == 'x') | (c == 'e')) && ((prev_char == 'x') | (prev_char == 'e')) | prev_pi {
|
} else if (valid_variables.contains(&prev_char) && valid_variables.contains(&c)) | prev_pi {
|
||||||
output_string += "*";
|
add_asterisk = true;
|
||||||
}
|
}
|
||||||
} else if numbers.contains(&c) {
|
} else if numbers.contains(&c) {
|
||||||
if letters.contains(&prev_char) | prev_pi {
|
if letters.contains(&prev_char) | prev_pi {
|
||||||
output_string += "*";
|
add_asterisk = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if add_asterisk {
|
||||||
|
output_string += "*";
|
||||||
|
}
|
||||||
|
|
||||||
prev_chars.push(c);
|
prev_chars.push(c);
|
||||||
output_string += &c.to_string();
|
output_string += &c.to_string();
|
||||||
}
|
}
|
||||||
@ -153,6 +161,7 @@ impl<T> Cache<T> {
|
|||||||
pub fn is_valid(&self) -> bool { self.valid }
|
pub fn is_valid(&self) -> bool { self.valid }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests to make sure my cursed function works as intended
|
||||||
#[test]
|
#[test]
|
||||||
fn asterisk_test() {
|
fn asterisk_test() {
|
||||||
assert_eq!(&add_asterisks("2x".to_string()), "2*x");
|
assert_eq!(&add_asterisks("2x".to_string()), "2*x");
|
||||||
|
|||||||
@ -25,9 +25,9 @@
|
|||||||
<p></p>
|
<p></p>
|
||||||
<label for="maxY">MaxY: </label> <input type="number" id="maxY" value="10">
|
<label for="maxY">MaxY: </label> <input type="number" id="maxY" value="10">
|
||||||
<p></p>
|
<p></p>
|
||||||
<label for="num_interval">Interval: </label> <input type="number" id="num_interval" value="100" min="0">
|
<label for="num_interval">Interval: </label> <input type="number" id="num_interval" value="100" min="0" step="1">
|
||||||
<p></p>
|
<p></p>
|
||||||
<label for="resolution">Number of Points </label> <input type="number" id="resolution" value="10000" min="0">
|
<label for="resolution">Number of Points </label> <input type="number" id="resolution" value="10000" min="0" step="1">
|
||||||
<p></p>
|
<p></p>
|
||||||
</div>
|
</div>
|
||||||
<div id="area-msg">Area:</div>
|
<div id="area-msg">Area:</div>
|
||||||
|
|||||||
42
www/index.js
42
www/index.js
@ -32,7 +32,8 @@ export function setup(WasmChart) {
|
|||||||
|
|
||||||
/** Add event listeners. */
|
/** Add event listeners. */
|
||||||
function setupUI() {
|
function setupUI() {
|
||||||
|
status.innerText = "WebAssembly loaded!";
|
||||||
|
|
||||||
// Handles browser color preferences
|
// Handles browser color preferences
|
||||||
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||||
darkMode = true;
|
darkMode = true;
|
||||||
@ -44,7 +45,6 @@ function setupUI() {
|
|||||||
updatePlot();
|
updatePlot();
|
||||||
});
|
});
|
||||||
|
|
||||||
status.innerText = "WebAssembly loaded!";
|
|
||||||
math_function.addEventListener("change", updatePlot);
|
math_function.addEventListener("change", updatePlot);
|
||||||
minX.addEventListener("input", updatePlot);
|
minX.addEventListener("input", updatePlot);
|
||||||
maxX.addEventListener("input", updatePlot);
|
maxX.addEventListener("input", updatePlot);
|
||||||
@ -95,9 +95,8 @@ function postNormalStatus(string) {
|
|||||||
status.innerText = string;
|
status.innerText = string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatePlot() {
|
// Checks variables put in input fields
|
||||||
postNormalStatus(`Rendering y=${math_function.value}...`);
|
function checkVariables() {
|
||||||
|
|
||||||
if (minX.value >= maxX.value) {
|
if (minX.value >= maxX.value) {
|
||||||
postErrorStatus("minX must be smaller than maxX!");
|
postErrorStatus("minX must be smaller than maxX!");
|
||||||
return;
|
return;
|
||||||
@ -114,27 +113,46 @@ function updatePlot() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (0 > resolution.value) {
|
if (0 > resolution.value) {
|
||||||
postErrorStatus("resolution (Number of Points) is smaller than 0!");
|
postErrorStatus("Number of Points is smaller than 0!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates a possible "tip" to assist the user when an error occurs.
|
||||||
|
function errorRecommend(error_string) {
|
||||||
|
if (error_string.includes("Evaluation error: unknown variable ")) {
|
||||||
|
return "This variable is not considered valid. Make sure you used a valid variable.";
|
||||||
|
} else {
|
||||||
|
return "Make sure you're using proper syntax! Check console log (f12) as well for more details.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function updatePlot() {
|
||||||
|
checkVariables();
|
||||||
|
|
||||||
if (chart_manager == null) {
|
if (chart_manager == null) {
|
||||||
chart_manager = ChartManager.new(math_function.value, Number(minX.value), Number(maxX.value), Number(minY.value), Number(maxY.value), Number(num_interval.value), Number(resolution.value));
|
try {
|
||||||
|
chart_manager = ChartManager.new(math_function.value, Number(minX.value), Number(maxX.value), Number(minY.value), Number(maxY.value), Number(num_interval.value), Number(resolution.value));
|
||||||
|
} catch(err) {
|
||||||
|
postErrorStatus("Error during ChartManager creation! Check logs for details.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const test_result = ChartManager.test_func(math_function.value);
|
const test_result = ChartManager.test_func(math_function.value);
|
||||||
if (test_result != "") {
|
if (test_result != "") {
|
||||||
const error_recommendation = ChartManager.error_recommend(test_result);
|
const error_recommendation = errorRecommend(test_result);
|
||||||
if (error_recommendation == "") {
|
let error_status_str = test_result;
|
||||||
postErrorStatus(test_result);
|
if (error_recommendation != "") {
|
||||||
} else {
|
error_status_str += `\nTip: ${error_recommendation}`;
|
||||||
postErrorStatus(`${test_result}\nTip: ${error_recommendation}`);
|
|
||||||
}
|
}
|
||||||
|
postErrorStatus(error_status_str);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
postNormalStatus(`Rendering y=${math_function.value}...`);
|
||||||
const start = performance.now();
|
const start = performance.now();
|
||||||
chart = chart_manager.update(canvas, math_function.value, Number(minX.value), Number(maxX.value), Number(minY.value), Number(maxY.value), Number(num_interval.value), Number(resolution.value), false); // TODO: improve darkmode support
|
chart = chart_manager.update(canvas, math_function.value, Number(minX.value), Number(maxX.value), Number(minY.value), Number(maxY.value), Number(num_interval.value), Number(resolution.value), false); // TODO: improve darkmode support
|
||||||
const end = performance.now();
|
const end = performance.now();
|
||||||
|
|||||||
@ -6,6 +6,7 @@ html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
|
input,
|
||||||
body {
|
body {
|
||||||
color: #c9d1d9;
|
color: #c9d1d9;
|
||||||
font-family: 'Open Sans', sans-serif;
|
font-family: 'Open Sans', sans-serif;
|
||||||
@ -23,6 +24,7 @@ html {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
@media (prefers-color-scheme: light) {
|
||||||
|
input,
|
||||||
body {
|
body {
|
||||||
color: #444;
|
color: #444;
|
||||||
font-family: 'Open Sans', sans-serif;
|
font-family: 'Open Sans', sans-serif;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user