cache works!
This commit is contained in:
parent
16a1076857
commit
dd6d30ef37
@ -17,4 +17,5 @@ plotters = "0.3.1"
|
||||
plotters-canvas = "0.3.0"
|
||||
wee_alloc = "0.4.5"
|
||||
web-sys = { version = "0.3.56", features = ["HtmlCanvasElement"] }
|
||||
meval = { git = "https://github.com/rekka/meval-rs.git" }
|
||||
meval = { git = "https://github.com/rekka/meval-rs.git" }
|
||||
console_error_panic_hook = { git = "https://github.com/rustwasm/console_error_panic_hook.git"}
|
||||
@ -1,86 +0,0 @@
|
||||
use crate::DrawResult;
|
||||
use meval::Expr;
|
||||
use plotters::prelude::*;
|
||||
use plotters_canvas::CanvasBackend;
|
||||
use web_sys::HtmlCanvasElement;
|
||||
|
||||
pub fn draw(
|
||||
element: HtmlCanvasElement, func_str: &str, min_x: f32, max_x: f32, min_y: f32, max_y: 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 = (max_x - min_x).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.0)
|
||||
.caption(format!("y={}", func_str), font)
|
||||
.x_label_area_size(30.0)
|
||||
.y_label_area_size(30.0)
|
||||
.build_cartesian_2d(min_x..max_x, min_y..max_y)?;
|
||||
|
||||
chart.configure_mesh().x_labels(3).y_labels(3).draw()?;
|
||||
|
||||
let data: Vec<(f32, f32)> = (1..=resolution)
|
||||
.map(|x| ((x as f32 / resolution as f32) * absrange) + min_x)
|
||||
.map(|x| (x, func(x as f64) as f32))
|
||||
.filter(|(_, y)| &min_y <= y && y <= &max_y)
|
||||
.collect();
|
||||
|
||||
chart.draw_series(LineSeries::new(data, &RED))?;
|
||||
|
||||
let (data2, area): (Vec<(f32, f32, f32)>, f32) =
|
||||
integral_rectangles(min_x, 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()?;
|
||||
Ok((chart.into_coord_trans(), area))
|
||||
}
|
||||
|
||||
// Creates and does the math for creating all the rectangles under the graph
|
||||
#[inline(always)]
|
||||
fn integral_rectangles(
|
||||
min_x: f32, step: f32, num_interval: usize, func: &dyn Fn(f64) -> f64,
|
||||
) -> (Vec<(f32, f32, f32)>, f32) {
|
||||
let data2: Vec<(f32, f32, f32)> = (0..num_interval)
|
||||
.map(|e| {
|
||||
let x: f32 = ((e as f32) * step) + min_x;
|
||||
|
||||
let x2: f32 = match x > 0.0 {
|
||||
true => x + step,
|
||||
false => x - step,
|
||||
};
|
||||
|
||||
let tmp1: f32 = func(x as f64) as f32;
|
||||
let tmp2: f32 = func(x2 as f64) as f32;
|
||||
|
||||
let y: f32 = match tmp2.abs() > tmp1.abs() {
|
||||
true => tmp1,
|
||||
false => tmp2,
|
||||
};
|
||||
|
||||
if !y.is_nan() {
|
||||
(x, x2, y)
|
||||
} else {
|
||||
(0.0, 0.0, 0.0)
|
||||
}
|
||||
})
|
||||
.filter(|ele| ele != &(0.0, 0.0, 0.0))
|
||||
.collect();
|
||||
let area: f32 = data2.iter().map(|(_, _, y)| y * step).sum(); // sum of all rectangles' areas
|
||||
(data2, area)
|
||||
}
|
||||
196
src/lib.rs
196
src/lib.rs
@ -1,12 +1,51 @@
|
||||
use meval::Expr;
|
||||
use plotters::prelude::*;
|
||||
use plotters_canvas::CanvasBackend;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::HtmlCanvasElement;
|
||||
mod func_plot;
|
||||
extern crate console_error_panic_hook;
|
||||
use console_error_panic_hook::hook;
|
||||
use std::panic;
|
||||
|
||||
#[global_allocator]
|
||||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
|
||||
|
||||
pub type DrawResult<T> = Result<T, Box<dyn std::error::Error>>;
|
||||
|
||||
// Creates and does the math for creating all the rectangles under the graph
|
||||
#[inline(always)]
|
||||
fn integral_rectangles(
|
||||
min_x: f32, step: f32, num_interval: usize, func: &dyn Fn(f64) -> f64,
|
||||
) -> (Vec<(f32, f32, f32)>, f32) {
|
||||
let data2: Vec<(f32, f32, f32)> = (0..num_interval)
|
||||
.map(|e| {
|
||||
let x: f32 = ((e as f32) * step) + min_x;
|
||||
|
||||
let x2: f32 = match x > 0.0 {
|
||||
true => x + step,
|
||||
false => x - step,
|
||||
};
|
||||
|
||||
let tmp1: f32 = func(x as f64) as f32;
|
||||
let tmp2: f32 = func(x2 as f64) as f32;
|
||||
|
||||
let y: f32 = match tmp2.abs() > tmp1.abs() {
|
||||
true => tmp1,
|
||||
false => tmp2,
|
||||
};
|
||||
|
||||
if !y.is_nan() {
|
||||
(x, x2, y)
|
||||
} else {
|
||||
(0.0, 0.0, 0.0)
|
||||
}
|
||||
})
|
||||
.filter(|ele| ele != &(0.0, 0.0, 0.0))
|
||||
.collect();
|
||||
let area: f32 = data2.iter().map(|(_, _, y)| y * step).sum(); // sum of all rectangles' areas
|
||||
(data2, area)
|
||||
}
|
||||
|
||||
/// Result of screen to chart coordinates conversion.
|
||||
#[wasm_bindgen]
|
||||
pub struct Point {
|
||||
@ -20,19 +59,27 @@ impl Point {
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct Chart {
|
||||
convert: Box<dyn Fn((i32, i32)) -> Option<(f32, f32)>>,
|
||||
area: f32,
|
||||
pub struct ChartManager {
|
||||
func_str: String,
|
||||
min_x: f32,
|
||||
max_x: f32,
|
||||
min_y: f32,
|
||||
max_y: f32,
|
||||
num_interval: usize,
|
||||
resolution: i32,
|
||||
back_cache: Option<Vec<(f32, f32)>>,
|
||||
front_cache: Option<(Vec<(f32, f32, f32)>, f32)>,
|
||||
use_back_cache: bool,
|
||||
use_front_cache: bool,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Chart {
|
||||
pub fn draw(
|
||||
canvas: HtmlCanvasElement, func_str: &str, min_x: f32, max_x: f32, min_y: f32, max_y: f32,
|
||||
num_interval: usize, resolution: i32,
|
||||
) -> Result<Chart, JsValue> {
|
||||
let draw_output = func_plot::draw(
|
||||
canvas,
|
||||
impl ChartManager {
|
||||
pub fn new(
|
||||
func_str: String, min_x: f32, max_x: f32, min_y: f32, max_y: f32, num_interval: usize,
|
||||
resolution: i32,
|
||||
) -> Self {
|
||||
Self {
|
||||
func_str,
|
||||
min_x,
|
||||
max_x,
|
||||
@ -40,16 +87,127 @@ impl Chart {
|
||||
max_y,
|
||||
num_interval,
|
||||
resolution,
|
||||
)
|
||||
.map_err(|err| err.to_string())?;
|
||||
let map_coord = draw_output.0;
|
||||
|
||||
Ok(Chart {
|
||||
convert: Box::new(move |coord| map_coord(coord).map(|(x, y)| (x, y))),
|
||||
area: draw_output.1,
|
||||
})
|
||||
back_cache: None,
|
||||
front_cache: None,
|
||||
use_back_cache: false,
|
||||
use_front_cache: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Used in order to hook into `panic!()` to log in the browser's console
|
||||
pub fn init_panic_hook() { panic::set_hook(Box::new(console_error_panic_hook::hook)); }
|
||||
|
||||
fn draw(
|
||||
&mut self, element: HtmlCanvasElement,
|
||||
) -> DrawResult<(impl Fn((i32, i32)) -> Option<(f32, f32)>, f32)> {
|
||||
let expr: Expr = self.func_str.parse().unwrap();
|
||||
let func = expr.bind("x").unwrap();
|
||||
|
||||
let absrange = (self.max_x - self.min_x).abs();
|
||||
let step = absrange / (self.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.0)
|
||||
.caption(format!("y={}", self.func_str), font)
|
||||
.x_label_area_size(30.0)
|
||||
.y_label_area_size(30.0)
|
||||
.build_cartesian_2d(self.min_x..self.max_x, self.min_y..self.max_y)?;
|
||||
|
||||
chart.configure_mesh().x_labels(3).y_labels(3).draw()?;
|
||||
|
||||
let data: Vec<(f32, f32)> = match self.use_back_cache {
|
||||
true => match &self.back_cache {
|
||||
Some(x) => x.clone(),
|
||||
None => panic!("use_back_cache is true, but back_cache is None!"),
|
||||
},
|
||||
false => {
|
||||
let output: Vec<(f32, f32)> = (1..=self.resolution)
|
||||
.map(|x| ((x as f32 / self.resolution as f32) * absrange) + self.min_x)
|
||||
.map(|x| (x, func(x as f64) as f32))
|
||||
.filter(|(_, y)| &self.min_y <= y && y <= &self.max_y)
|
||||
.collect();
|
||||
self.back_cache = Some(output.clone());
|
||||
output
|
||||
}
|
||||
};
|
||||
|
||||
chart.draw_series(LineSeries::new(data, &RED))?;
|
||||
|
||||
let (data2, area): (Vec<(f32, f32, f32)>, f32) = match self.use_front_cache {
|
||||
true => match &self.front_cache {
|
||||
Some(x) => x.clone(),
|
||||
None => panic!("use_front_cache is true, but front_cache is None!"),
|
||||
},
|
||||
false => {
|
||||
let output: (Vec<(f32, f32, f32)>, f32) =
|
||||
integral_rectangles(self.min_x, step, self.num_interval, &func);
|
||||
self.front_cache = Some(output.clone());
|
||||
output
|
||||
}
|
||||
};
|
||||
|
||||
// Draw rectangles
|
||||
chart.draw_series(
|
||||
data2
|
||||
.iter()
|
||||
.map(|(x1, x2, y)| Rectangle::new([(*x2, *y), (*x1, 0.0)], &BLUE)),
|
||||
)?;
|
||||
|
||||
root.present()?;
|
||||
Ok((chart.into_coord_trans(), area))
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
&mut self, canvas: HtmlCanvasElement, func_str: &str, min_x: f32, max_x: f32, min_y: f32,
|
||||
max_y: f32, num_interval: usize, resolution: i32,
|
||||
) -> Result<Chart, JsValue> {
|
||||
let underlying_update = (func_str.to_string() != self.func_str)
|
||||
| (min_x != self.min_x)
|
||||
| (max_x != self.max_x)
|
||||
| (min_y != self.min_y)
|
||||
| (max_y != self.max_y);
|
||||
|
||||
self.use_back_cache =
|
||||
!underlying_update && self.resolution == resolution && !self.back_cache.is_none();
|
||||
self.use_front_cache = match underlying_update {
|
||||
true => false,
|
||||
false => num_interval == self.num_interval && !self.front_cache.is_none(),
|
||||
};
|
||||
|
||||
self.func_str = func_str.to_string();
|
||||
self.min_x = min_x;
|
||||
self.max_x = max_x;
|
||||
self.min_y = min_y;
|
||||
self.max_y = max_y;
|
||||
self.num_interval = num_interval;
|
||||
self.resolution = resolution;
|
||||
|
||||
let draw_output = self.draw(canvas).map_err(|err| err.to_string())?;
|
||||
let map_coord = draw_output.0;
|
||||
|
||||
let chart = Chart {
|
||||
convert: Box::new(move |coord| map_coord(coord).map(|(x, y)| (x, y))),
|
||||
area: draw_output.1,
|
||||
};
|
||||
|
||||
Ok(chart)
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct Chart {
|
||||
convert: Box<dyn Fn((i32, i32)) -> Option<(f32, f32)>>,
|
||||
area: f32,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
impl Chart {
|
||||
pub fn get_area(&self) -> f32 { self.area }
|
||||
|
||||
pub fn coord(&self, x: i32, y: i32) -> Option<Point> {
|
||||
|
||||
8
www/bootstrap.js
vendored
8
www/bootstrap.js
vendored
@ -2,19 +2,19 @@ init();
|
||||
|
||||
async function init() {
|
||||
if (typeof process == "object") {
|
||||
const [{Chart}, {main, setup}] = await Promise.all([
|
||||
const [{ChartManager}, {main, setup}] = await Promise.all([
|
||||
import("integral_site"),
|
||||
import("./index.js"),
|
||||
]);
|
||||
setup(Chart);
|
||||
setup(ChartManager);
|
||||
main();
|
||||
} else {
|
||||
const [{Chart, default: init}, {main, setup}] = await Promise.all([
|
||||
const [{ChartManager, default: init}, {main, setup}] = await Promise.all([
|
||||
import("../pkg/integral_site.js"),
|
||||
import("./index.js"),
|
||||
]);
|
||||
await init();
|
||||
setup(Chart);
|
||||
setup(ChartManager);
|
||||
main();
|
||||
}
|
||||
}
|
||||
|
||||
12
www/index.js
12
www/index.js
@ -1,4 +1,4 @@
|
||||
class Chart {}
|
||||
class ChartManager {}
|
||||
|
||||
const canvas = document.getElementById("canvas");
|
||||
const coord = document.getElementById("coord");
|
||||
@ -14,6 +14,7 @@ const area_msg = document.getElementById("area-msg");
|
||||
const resolution = document.getElementById("resolution");
|
||||
|
||||
let chart = null;
|
||||
let chart_manager = null;
|
||||
|
||||
/** Main entry point */
|
||||
export function main() {
|
||||
@ -23,7 +24,8 @@ export function main() {
|
||||
|
||||
/** This function is used in `bootstrap.js` to setup imports. */
|
||||
export function setup(WasmChart) {
|
||||
Chart = WasmChart;
|
||||
ChartManager = WasmChart;
|
||||
ChartManager.init_panic_hook();
|
||||
}
|
||||
|
||||
/** Add event listeners. */
|
||||
@ -71,8 +73,12 @@ function onMouseMove(event) {
|
||||
function updatePlot() {
|
||||
status.innerText = `Rendering y=${math_function.value}...`;
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
const start = performance.now();
|
||||
chart = Chart.draw(canvas, math_function.value, Number(minX.value), Number(maxX.value), Number(minY.value), Number(maxY.value), Number(num_interval.value), Number(resolution.value));
|
||||
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));
|
||||
const end = performance.now();
|
||||
|
||||
area_msg.innerText = `Estimated Area: ${chart.get_area()}`;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user