This commit is contained in:
Simon Gardling
2022-02-10 17:44:29 -05:00
commit 1bd2f919a8
12 changed files with 1085 additions and 0 deletions

81
src/func_plot.rs Normal file
View File

@@ -0,0 +1,81 @@
use crate::DrawResult;
use plotters::prelude::*;
use plotters_canvas::CanvasBackend;
use web_sys::HtmlCanvasElement;
use meval::Expr;
/// Draw power function f(x) = x^power.
pub fn draw(element: HtmlCanvasElement, func_str: &str, minX: f32, maxX: f32, minY: f32, maxY: f32, num_interval: usize, resolution: i32) -> DrawResult<(impl Fn((i32, i32))-> Option<(f32, f32)>, f32)> {
let expr: Expr = func_str.parse().unwrap();
let func = expr.bind("x").unwrap();
let absrange = (maxX-minX).abs();
let step = absrange/(num_interval as f32);
let backend = CanvasBackend::with_canvas_object(element).unwrap();
let root = backend.into_drawing_area();
let font: FontDesc = ("sans-serif", 20.0).into();
root.fill(&WHITE)?;
let mut chart = ChartBuilder::on(&root)
.margin(20 as f32)
.caption(format!("y={}", func_str), font)
.x_label_area_size(30 as f32)
.y_label_area_size(30 as f32)
.build_cartesian_2d(minX..maxX, minY..maxY)?;
chart.configure_mesh().x_labels(3).y_labels(3).draw()?;
let data = (1..=resolution)
.map(|x| (((x as f32/resolution as f32))*(&absrange))+&minX)
.map(|x| (x, func(x as f64) as f32))
.filter(|(_,y)| &minY <= y && y <= &maxY);
chart.draw_series(LineSeries::new(data, &RED))?;
let (data2, area) = integral_rectangles(minX, minY, maxY, absrange, step, num_interval, &func); // Get rectangle coordinates and the total area
// Draw rectangles
chart.draw_series(data2.iter().map(|(x1, x2, y)| Rectangle::new([(*x2, *y), (*x1, 0.0)], &BLUE)))?;
root.present()?;
let output = chart.into_coord_trans();
return Ok(((output), area));
}
// Creates and does the math for creating all the rectangles under the graph
#[inline(always)]
fn integral_rectangles(minX: f32, minY: f32, maxY: f32, absrange: f32, step: f32, num_interval: usize, func: &dyn Fn(f64) -> f64) -> (Vec<(f32, f32, f32)>, f32) {
let mut area: f32 = 0.0; // sum of all rectangles' areas
let mut tmp1: f32; // Top left Y value that's tested
let mut tmp2: f32; // Top right Y value that's tested
let mut x2: f32; // X value of the right side of the rectangle
let mut y: f32; // Y value of the top of the rectangle
let mut x: f32; // X value of the left side of the rectangle
let mut data2: Vec<(f32, f32, f32)> = Vec::new();
for e in 0..num_interval {
x = ((e as f32)*step)+minX;
if x > 0.0 {
x2 = x+step;
} else {
x2 = x-step;
}
tmp1 = func(x as f64) as f32;
tmp2 = func(x2 as f64) as f32;
if tmp2.abs() > tmp1.abs() {
y = tmp1;
} else {
y = tmp2;
}
// Add current rectangle's area to the total
if !y.is_nan() {
area += y*step;
data2.push((x, x2, y));
}
}
return (data2, area);
}

48
src/lib.rs Normal file
View File

@@ -0,0 +1,48 @@
use wasm_bindgen::prelude::*;
use web_sys::HtmlCanvasElement;
mod func_plot;
use meval::Expr;
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
pub type DrawResult<T> = Result<T, Box<dyn std::error::Error>>;
#[wasm_bindgen]
pub struct Chart {
convert: Box<dyn Fn((i32, i32)) -> Option<(f32, f32)>>,
area: f32
}
/// Result of screen to chart coordinates conversion.
#[wasm_bindgen]
pub struct Point {
pub x: f32,
pub y: f32,
}
#[wasm_bindgen]
impl Chart {
pub fn draw(canvas: HtmlCanvasElement, func: &str, minX: f32, maxX: f32, minY: f32, maxY: f32, num_interval: usize, resolution: i32) -> Result<Chart, JsValue> {
let output = func_plot::draw(canvas, func, minX, maxX, minY, maxY, num_interval, resolution).map_err(|err| err.to_string())?;
let map_coord = output.0;
Ok(Chart {
convert: Box::new(move |coord| map_coord(coord).map(|(x, y)| (x.into(), y.into()))),
area: output.1,
})
}
pub fn get_area(&self) -> Result<f32, JsValue> {
return Ok(self.area);
}
// Does actual calculation of antiderivative
// pub fn actual_area(power: f32, minX: f32, maxX: f32) -> Result<f32, JsValue> {
// let newpower = power + 1.0;
// return Ok((maxX.powf(newpower as f32)/newpower) - (minX.powf(newpower as f32)/newpower));
// }
pub fn coord(&self, x: i32, y: i32) -> Option<Point> {
(self.convert)((x, y)).map(|(x, y)| Point { x, y })
}
}