Skip to content

Commit 0d6b8a4

Browse files
committed
Refactor wasm-demo
1 parent af27027 commit 0d6b8a4

File tree

11 files changed

+245
-173
lines changed

11 files changed

+245
-173
lines changed

examples/wasm-demo/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ authors = ["Hao Hou <[email protected]>"]
55
edition = "2018"
66

77
[lib]
8-
crate-type=['cdylib']
8+
crate-type=["cdylib"]
99

1010
[dependencies]
1111
plotters = {path = "../.."}
12-
wasm-bindgen = "0.2.43"
12+
wasm-bindgen = "0.2"
1313
wee_alloc = "*"
14-
web-sys = { version = "0.3.4", features = ['HtmlCanvasElement'] }
14+
web-sys = { version = "0.3.4", features = ["HtmlCanvasElement"] }
1515

1616
[profile.release]
1717
lto = true

examples/wasm-demo/src/func_plot.rs

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
use plotters::prelude::*;
2-
use wasm_bindgen::prelude::*;
2+
use crate::DrawResult;
33

4-
fn start_plotting(
5-
element: &str,
6-
pow: i32,
7-
) -> Result<Box<dyn Fn((i32, i32)) -> Option<(f32, f32)>>, Box<dyn std::error::Error>> {
8-
let backend = CanvasBackend::new(element).unwrap();
4+
/// Draw power function f(x) = x^power.
5+
pub fn draw(canvas_id: &str, power: i32) -> DrawResult<impl Fn((i32, i32)) -> Option<(f32, f32)>> {
6+
let backend = CanvasBackend::new(canvas_id).unwrap();
97
let root = backend.into_drawing_area();
108
let font: FontDesc = ("sans-serif", 20.0).into();
119

1210
root.fill(&WHITE)?;
1311

1412
let mut chart = ChartBuilder::on(&root)
15-
.caption(format!("y=x^{}", pow), font)
13+
.caption(format!("y=x^{}", power), font)
1614
.x_label_area_size(30)
1715
.y_label_area_size(30)
1816
.build_ranged(-1f32..1f32, -1.2f32..1.2f32)?;
@@ -22,15 +20,10 @@ fn start_plotting(
2220
chart.draw_series(LineSeries::new(
2321
(-50..=50)
2422
.map(|x| x as f32 / 50.0)
25-
.map(|x| (x, x.powf(pow as f32))),
23+
.map(|x| (x, x.powf(power as f32))),
2624
&RED,
2725
))?;
2826

2927
root.present()?;
30-
return Ok(Box::new(chart.into_coord_trans()));
31-
}
32-
33-
#[wasm_bindgen]
34-
pub fn draw_func(element: &str, p: i32) -> JsValue {
35-
crate::make_coord_mapping_closure(start_plotting(element, p).ok())
28+
return Ok(chart.into_coord_trans());
3629
}

examples/wasm-demo/src/lib.rs

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,43 @@
11
use wasm_bindgen::prelude::*;
2+
use web_sys::HtmlCanvasElement;
23

34
mod func_plot;
45
mod mandelbrot;
56

67
#[global_allocator]
78
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
89

9-
pub use func_plot::draw_func;
10-
pub use mandelbrot::draw_mandelbrot;
11-
12-
pub fn make_coord_mapping_closure<T: Into<f64> + 'static>(
13-
map_func: Option<Box<dyn Fn((i32, i32)) -> Option<(T, T)>>>,
14-
) -> JsValue {
15-
if let Some(mapping_func) = map_func {
16-
let closure = Closure::wrap(Box::new(move |x: i32, y: i32, idx: u32| {
17-
if let Some((x, y)) = mapping_func((x, y)) {
18-
if idx == 0 {
19-
return x.into();
20-
}
21-
return y.into();
22-
} else {
23-
return std::f64::NAN;
24-
}
25-
}) as Box<dyn FnMut(i32, i32, u32) -> f64>);
26-
27-
let js_value = closure.as_ref().clone();
28-
closure.forget();
29-
30-
return js_value;
31-
} else {
32-
return JsValue::null();
10+
/// Type alias for the result of a drawing function.
11+
pub type DrawResult<T> = Result<T, Box<dyn std::error::Error>>;
12+
13+
#[wasm_bindgen]
14+
pub struct Chart {
15+
convert: Box<dyn Fn((i32, i32)) -> Option<(f64, f64)>>,
16+
}
17+
18+
#[wasm_bindgen]
19+
pub struct Point {
20+
pub x: f64,
21+
pub y: f64,
22+
}
23+
24+
#[wasm_bindgen]
25+
impl Chart {
26+
pub fn power(canvas_id: &str, power: i32) -> Result<Chart, JsValue> {
27+
let map_coord = func_plot::draw(canvas_id, power).map_err(|err| err.to_string())?;
28+
Ok(Chart{convert: Box::new(move |coord| {
29+
map_coord(coord).map(|(x, y)| (x.into(), y.into()))
30+
})})
31+
}
32+
33+
pub fn mandelbrot(canvas: HtmlCanvasElement) -> Result<Chart, JsValue> {
34+
let map_coord = mandelbrot::draw(canvas).map_err(|err| err.to_string())?;
35+
Ok(Chart{convert: Box::new(map_coord)})
36+
}
37+
38+
/// This function can be used to convert screen coordinates to
39+
/// graph coordinates.
40+
pub fn coord(&self, x: i32, y: i32) -> Option<Point> {
41+
(self.convert)((x, y)).map(|(x, y)| Point{x, y})
3342
}
3443
}

examples/wasm-demo/src/mandelbrot.rs

Lines changed: 27 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,10 @@
11
use plotters::prelude::*;
22
use std::ops::Range;
3-
use wasm_bindgen::prelude::*;
43
use web_sys::HtmlCanvasElement;
4+
use crate::DrawResult;
55

6-
fn mandelbrot_set(
7-
real: Range<f64>,
8-
complex: Range<f64>,
9-
samples: (usize, usize),
10-
max_iter: usize,
11-
) -> impl Iterator<Item = (f64, f64, usize)> {
12-
let step = (
13-
(real.end - real.start) / samples.0 as f64,
14-
(complex.end - complex.start) / samples.1 as f64,
15-
);
16-
return (0..(samples.0 * samples.1)).map(move |k| {
17-
let c = (
18-
real.start + step.0 * (k % samples.0) as f64,
19-
complex.start + step.1 * (k / samples.0) as f64,
20-
);
21-
let mut z = (0.0, 0.0);
22-
let mut cnt = 0;
23-
while cnt < max_iter && z.0 * z.0 + z.1 * z.1 <= 1e10 {
24-
z = (z.0 * z.0 - z.1 * z.1 + c.0, 2.0 * z.0 * z.1 + c.1);
25-
cnt += 1;
26-
}
27-
return (c.0, c.1, cnt);
28-
});
29-
}
30-
31-
fn draw_mandelbrot_impl(
32-
element: HtmlCanvasElement,
33-
) -> Result<Box<dyn Fn((i32, i32)) -> Option<(f64, f64)>>, Box<dyn std::error::Error>> {
6+
/// Draw Mandelbrot set
7+
pub fn draw(element: HtmlCanvasElement) -> DrawResult<impl Fn((i32, i32)) -> Option<(f64, f64)>> {
348
let backend = CanvasBackend::with_canvas_object(element).unwrap();
359

3610
let root = backend.into_drawing_area();
@@ -40,7 +14,7 @@ fn draw_mandelbrot_impl(
4014
.margin(20)
4115
.x_label_area_size(10)
4216
.y_label_area_size(10)
43-
.build_ranged(-2.1f64..0.6f64, -1.2f64..1.2f64)?;
17+
.build_ranged(-2.1..0.6, -1.2..1.2)?;
4418

4519
chart
4620
.configure_mesh()
@@ -66,7 +40,27 @@ fn draw_mandelbrot_impl(
6640
return Ok(Box::new(chart.into_coord_trans()));
6741
}
6842

69-
#[wasm_bindgen]
70-
pub fn draw_mandelbrot(element: HtmlCanvasElement) -> JsValue {
71-
crate::make_coord_mapping_closure(draw_mandelbrot_impl(element).ok())
43+
fn mandelbrot_set(
44+
real: Range<f64>,
45+
complex: Range<f64>,
46+
samples: (usize, usize),
47+
max_iter: usize,
48+
) -> impl Iterator<Item = (f64, f64, usize)> {
49+
let step = (
50+
(real.end - real.start) / samples.0 as f64,
51+
(complex.end - complex.start) / samples.1 as f64,
52+
);
53+
return (0..(samples.0 * samples.1)).map(move |k| {
54+
let c = (
55+
real.start + step.0 * (k % samples.0) as f64,
56+
complex.start + step.1 * (k / samples.0) as f64,
57+
);
58+
let mut z = (0.0, 0.0);
59+
let mut cnt = 0;
60+
while cnt < max_iter && z.0 * z.0 + z.1 * z.1 <= 1e10 {
61+
z = (z.0 * z.0 - z.1 * z.1 + c.0, 2.0 * z.0 * z.1 + c.1);
62+
cnt += 1;
63+
}
64+
return (c.0, c.1, cnt);
65+
});
7266
}

examples/wasm-demo/www/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
dist
3+
package-lock.json

examples/wasm-demo/www/bootstrap.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// A dependency graph that contains any wasm must all be imported
2+
// asynchronously. This `bootstrap.js` file does the single async import, so
3+
// that no one else needs to worry about it again.
4+
import("./index.js")
5+
.catch(e => console.error("Error importing `index.js`:", e));

examples/wasm-demo/www/index.html

Lines changed: 33 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,36 @@
11
<!DOCTYPE html>
22
<html lang="en">
3-
<head>
4-
<meta charset="utf-8">
5-
<title>Plotters WebAssembly Demo</title>
6-
<link rel="stylesheet" href="./style.css">
7-
</head>
8-
<body>
9-
<div>
10-
<h1 class="centered">Plotters WebAssembly Demo</h1>
11-
<div id="coord" class='centered' style='color: grey; font-size: 10px; height: 15px'></div>
12-
<canvas id="canvas" class="centered" height=400 width=600></canvas>
13-
<div id="status" class='centered' style='color: grey; font-size: 10px'>Loading WebAssembly...</div>
14-
<div style="margin-top:20px"></div>
15-
<div class="control">
16-
<b>Demo: </b>
17-
<select id="pow">
18-
<option value="0">Graph of y=1</option>
19-
<option value="1">Graph of y=x</option>
20-
<option value="2">Graph of y=x^2</option>
21-
<option value="3">Graph of y=x^3</option>
22-
<option value="4">Graph of y=x^4</option>
23-
<option value="5">Graph of y=x^5</option>
24-
<option value="mandelbrot">Mandelbrot Set</option>
25-
</select>
26-
</div>
27-
</div>
28-
<div style="margin-top:50px"></div>
29-
<div class='centered' style='font-size: 12px;'>
30-
<div class='code-link'>
31-
<a href="https://github.com/38/plotters/blob/master/examples/wasm-demo" target='a'>Source</a> |
32-
<a href="https://github.com/38/plotters" target='a'>Repo</a> |
33-
<a href="https://crates.io/crates/plotters" target='a'>Crates</a> |
34-
<a href="https://docs.rs/plotters" target='a'>Docs</a>
35-
</div>
36-
</div>
37-
38-
<script type="module">
39-
import * as wasm from "./pkg/wasm_demo.js";
40-
wasm.default("./pkg/wasm_demo_bg.wasm").then(function() {
41-
var map_coord = undefined;
42-
document.getElementById("canvas").addEventListener("mousemove", function(e) {
43-
if(map_coord === undefined) return false;
44-
var lx = map_coord(e.offsetX, e.offsetY, 0);
45-
var ly = map_coord(e.offsetX, e.offsetY, 1);
46-
if(Number.isNaN(lx) || Number.isNaN(ly)) {
47-
document.getElementById("coord").innerText = "Mouse pointer is out of range";
48-
return;
49-
}
50-
document.getElementById("coord").innerText = "(" + lx.toFixed(3) + "," + ly.toFixed(3) + ")";
51-
});
52-
var sel = document.getElementById("pow");
53-
var stat = document.getElementById("status");
54-
function update_plot() {
55-
stat.innerText = "Rendering " + sel.selectedOptions[0].innerText + "...";
56-
setTimeout(function() {
57-
map_coord = undefined;
58-
var start = performance.now();
59-
if(sel.selectedOptions[0].value != "mandelbrot") {
60-
var pow = Number(sel.selectedOptions[0].value);
61-
map_coord = wasm.draw_func("canvas", pow);
62-
} else {
63-
// Actually, you can directly use the #canvas element,
64-
// This is just a illustration for how we can do double buffering
65-
// with plotters
66-
var buffer = document.createElement("canvas");
67-
buffer.height = 400;
68-
buffer.width = 600;
69-
map_coord = wasm.draw_mandelbrot(buffer);
70-
document.getElementById("canvas").getContext("2d").drawImage(buffer, 0, 0);
71-
}
72-
var end = performance.now();
73-
stat.innerText = "Rendered " + sel.selectedOptions[0].innerText + " in " + (end - start) + "ms";
74-
}, 5);
75-
}
76-
77-
stat.innerText = "WebAssembly loaded!";
78-
79-
update_plot();
80-
81-
sel.addEventListener("change", function (e) {
82-
update_plot();
83-
});
84-
});
85-
</script>
86-
</body>
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Plotters WebAssembly Demo</title>
6+
<link rel="stylesheet" href="./style.css">
7+
</head>
8+
<body>
9+
<noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript>
10+
<script src="./bootstrap.js"></script>
11+
<div>
12+
<h1 class="centered">Plotters WebAssembly Demo {TODO}</h1>
13+
<div id="coord" class="centered"></div>
14+
<canvas id="canvas" class="centered" height=400 width=600></canvas>
15+
<div id="status" class="centered">Loading WebAssembly...</div>
16+
<div id="control">
17+
<label for="pow">Demo: </label>
18+
<select id="pow">
19+
<option value="0">Graph of y=1</option>
20+
<option value="1">Graph of y=x</option>
21+
<option value="2">Graph of y=x^2</option>
22+
<option value="3">Graph of y=x^3</option>
23+
<option value="4">Graph of y=x^4</option>
24+
<option value="5">Graph of y=x^5</option>
25+
<option value="mandelbrot">Mandelbrot Set</option>
26+
</select>
27+
</div>
28+
</div>
29+
<div id="links" class="centered">
30+
<a href="https://github.com/38/plotters/blob/master/examples/wasm-demo" target="a">Source</a> |
31+
<a href="https://github.com/38/plotters" target="a">Repo</a> |
32+
<a href="https://crates.io/crates/plotters" target="a">Crates</a> |
33+
<a href="https://docs.rs/plotters" target="a">Docs</a>
34+
</div>
35+
</body>
8736
</html>

examples/wasm-demo/www/index.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { Chart, drawPower } from "wasm-demo";
2+
3+
const canvas = document.getElementById("canvas");
4+
const ctx = canvas.getContext("2d");
5+
6+
// Setup canvas to properly handle high DPI.
7+
canvas.style.width = canvas.width + "px";
8+
canvas.style.height = canvas.height + "px";
9+
const dpr = window.devicePixelRatio || 1;
10+
const rect = canvas.getBoundingClientRect();
11+
canvas.width *= dpr;
12+
canvas.height *= dpr;
13+
ctx.scale(dpr, dpr);
14+
15+
const coord = document.getElementById("coord");
16+
17+
let chart = undefined;
18+
window.addEventListener("mousemove", (event) => {
19+
if (chart === undefined) {
20+
return;
21+
}
22+
23+
const point = chart.coord(event.offsetX, event.offsetY);
24+
25+
coord.innerText = (point)
26+
? `(${point.x.toFixed(3)}, ${point.y.toFixed(3)})`
27+
: "Mouse pointer is out of range";
28+
});
29+
30+
const select = document.getElementById("pow");
31+
const status = document.getElementById("status");
32+
33+
const updatePlot = () => {
34+
const selected = select.selectedOptions[0];
35+
status.innerText = `Rendering ${selected.innerText}...`;
36+
setTimeout(() => {
37+
chart = undefined;
38+
const start = performance.now();
39+
chart = (selected.value == "mandelbrot")
40+
? Chart.mandelbrot(canvas)
41+
: Chart.power("canvas", Number(selected.value));
42+
const end = performance.now();
43+
status.innerText = `Rendered ${selected.innerText} in ${end - start}ms`;
44+
}, 5);
45+
};
46+
47+
status.innerText = "WebAssembly loaded!";
48+
49+
updatePlot();
50+
51+
select.addEventListener("change", updatePlot);

0 commit comments

Comments
 (0)