-
Notifications
You must be signed in to change notification settings - Fork 98
Commit
- Loading branch information
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,6 +18,7 @@ members = [ | |
|
||
"tools/mxpy-snippet-generator", | ||
"tools/payload-macro-generator", | ||
"tools/plotter", | ||
|
||
"vm", | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
/target | ||
.*.sw* | ||
.vscode/* |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
[package] | ||
name = "wasm-demo" | ||
version = "0.1.0" | ||
authors = ["Hao Hou <haohou302@gmail.com>"] | ||
edition = "2018" | ||
|
||
[lib] | ||
crate-type=["cdylib"] | ||
|
||
[dependencies] | ||
plotters = "^0.3.2" | ||
wasm-bindgen = "0.2.78" | ||
wee_alloc = "0.4.5" | ||
web-sys = { version = "0.3.39", features = ["HtmlCanvasElement"] } | ||
plotters-canvas = "^0.3.0" | ||
|
||
[profile.release] | ||
lto = true |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# Example Project of Plotters + WASM | ||
|
||
This is a minimal project that uses Plotters in WASM application. | ||
|
||
- For more information about Plotters project, check our [core repository](https://github.com/plotters-rs/plotters). | ||
|
||
- This demo has been deployed at this [link](https://plotters-rs.github.io/wasm-demo/www/index.html). | ||
|
||
## Try this example locally | ||
|
||
To build the demo you need [wasm-pack](https://rustwasm.github.io/docs/book/game-of-life/setup.html). | ||
|
||
Then you can run it locally either using `npm` and `webpack-dev-server` or | ||
just with static web server. | ||
|
||
The following script will install needed software and run the server via `npm`. | ||
``` | ||
./start-server.sh | ||
``` | ||
|
||
For Windows users without Bash, `start-server.bat` can be used to | ||
launch the server. | ||
|
||
``` | ||
start-server.bat | ||
``` | ||
|
||
## Developing with NPM | ||
Please use [rust-wasm guide](https://rustwasm.github.io/docs/book/game-of-life/setup.html) for initial setup . | ||
Then you can run the demo locally using `npm`: | ||
```bash | ||
wasm-pack build | ||
cd www | ||
npm install | ||
npm start | ||
``` | ||
|
||
This will start a dev server which will automatically reload your page | ||
whenever you change anything in `www` directory. To update `rust` code | ||
call `wasm-pack build` manually. | ||
|
||
## Developing without dependenices | ||
If you don't want to use `npm` here's how you can run the example | ||
using any web server. We are using rust [basic-http-server](https://github.com/brson/basic-http-server), but | ||
any web server will do. | ||
|
||
```bash | ||
# Install web server (instead you can use your local nginx for example) | ||
cargo install basic-http-server | ||
wasm-pack build --target web # Note `--target web` | ||
basic-http-server | ||
``` | ||
|
||
Then open http://127.0.0.1:4000/www |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
use crate::DrawResult; | ||
use plotters::prelude::*; | ||
use plotters_canvas::CanvasBackend; | ||
|
||
/// Draw power function f(x) = x^power. | ||
pub fn draw(canvas_id: &str, power: i32) -> DrawResult<impl Fn((i32, i32)) -> Option<(f32, f32)>> { | ||
let backend = CanvasBackend::new(canvas_id).expect("cannot find canvas"); | ||
let root = backend.into_drawing_area(); | ||
let font: FontDesc = ("sans-serif", 20.0).into(); | ||
|
||
root.fill(&WHITE)?; | ||
|
||
let mut chart = ChartBuilder::on(&root) | ||
.margin(20u32) | ||
.caption(format!("y=x^{}", power), font) | ||
.x_label_area_size(30u32) | ||
.y_label_area_size(30u32) | ||
.build_cartesian_2d(-1f32..1f32, -1.2f32..1.2f32)?; | ||
|
||
chart.configure_mesh().x_labels(3).y_labels(3).draw()?; | ||
|
||
chart.draw_series(LineSeries::new( | ||
(-50..=50) | ||
.map(|x| x as f32 / 50.0) | ||
.map(|x| (x, x.powf(power as f32))), | ||
&RED, | ||
))?; | ||
|
||
root.present()?; | ||
return Ok(chart.into_coord_trans()); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
use wasm_bindgen::prelude::*; | ||
use web_sys::HtmlCanvasElement; | ||
|
||
mod func_plot; | ||
mod mandelbrot; | ||
mod plot3d; | ||
|
||
#[global_allocator] | ||
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; | ||
|
||
/// Type alias for the result of a drawing function. | ||
pub type DrawResult<T> = Result<T, Box<dyn std::error::Error>>; | ||
|
||
/// Type used on the JS side to convert screen coordinates to chart | ||
/// coordinates. | ||
#[wasm_bindgen] | ||
pub struct Chart { | ||
convert: Box<dyn Fn((i32, i32)) -> Option<(f64, f64)>>, | ||
Check warning on line 18 in tools/plotter/src/lib.rs GitHub Actions / clippy[clippy] tools/plotter/src/lib.rs#L18
Raw output
Check warning on line 18 in tools/plotter/src/lib.rs GitHub Actions / clippy[clippy] tools/plotter/src/lib.rs#L18
Raw output
|
||
} | ||
|
||
/// Result of screen to chart coordinates conversion. | ||
#[wasm_bindgen] | ||
pub struct Point { | ||
pub x: f64, | ||
pub y: f64, | ||
} | ||
|
||
#[wasm_bindgen] | ||
impl Chart { | ||
/// Draw provided power function on the canvas element using it's id. | ||
/// Return `Chart` struct suitable for coordinate conversion. | ||
pub fn power(canvas_id: &str, power: i32) -> Result<Chart, JsValue> { | ||
let map_coord = func_plot::draw(canvas_id, power).map_err(|err| err.to_string())?; | ||
Ok(Chart { | ||
convert: Box::new(move |coord| map_coord(coord).map(|(x, y)| (x.into(), y.into()))), | ||
}) | ||
} | ||
|
||
/// Draw Mandelbrot set on the provided canvas element. | ||
/// Return `Chart` struct suitable for coordinate conversion. | ||
pub fn mandelbrot(canvas: HtmlCanvasElement) -> Result<Chart, JsValue> { | ||
let map_coord = mandelbrot::draw(canvas).map_err(|err| err.to_string())?; | ||
Ok(Chart { | ||
convert: Box::new(map_coord), | ||
}) | ||
} | ||
|
||
pub fn plot3d(canvas: HtmlCanvasElement, pitch: f64, yaw: f64) -> Result<(), JsValue> { | ||
plot3d::draw(canvas, pitch, yaw).map_err(|err| err.to_string())?; | ||
Ok(()) | ||
} | ||
|
||
/// This function can be used to convert screen coordinates to | ||
/// chart coordinates. | ||
pub fn coord(&self, x: i32, y: i32) -> Option<Point> { | ||
(self.convert)((x, y)).map(|(x, y)| Point { x, y }) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
use crate::DrawResult; | ||
use plotters::prelude::*; | ||
use plotters_canvas::CanvasBackend; | ||
use std::ops::Range; | ||
use web_sys::HtmlCanvasElement; | ||
|
||
/// Draw Mandelbrot set | ||
pub fn draw(element: HtmlCanvasElement) -> DrawResult<impl Fn((i32, i32)) -> Option<(f64, f64)>> { | ||
let backend = CanvasBackend::with_canvas_object(element).unwrap(); | ||
|
||
let root = backend.into_drawing_area(); | ||
root.fill(&WHITE)?; | ||
|
||
let mut chart = ChartBuilder::on(&root) | ||
.margin(20) | ||
.x_label_area_size(10) | ||
.y_label_area_size(10) | ||
.build_cartesian_2d(-2.1..0.6, -1.2..1.2)?; | ||
|
||
chart | ||
.configure_mesh() | ||
.disable_x_mesh() | ||
.disable_y_mesh() | ||
.draw()?; | ||
|
||
let plotting_area = chart.plotting_area(); | ||
|
||
let range = plotting_area.get_pixel_range(); | ||
let (pw, ph) = (range.0.end - range.0.start, range.1.end - range.1.start); | ||
let (xr, yr) = (chart.x_range(), chart.y_range()); | ||
|
||
for (x, y, c) in mandelbrot_set(xr, yr, (pw as usize, ph as usize), 100) { | ||
if c != 100 { | ||
plotting_area.draw_pixel((x, y), &HSLColor(c as f64 / 100.0, 1.0, 0.5))?; | ||
} else { | ||
plotting_area.draw_pixel((x, y), &BLACK)?; | ||
} | ||
} | ||
|
||
root.present()?; | ||
return Ok(Box::new(chart.into_coord_trans())); | ||
} | ||
|
||
fn mandelbrot_set( | ||
real: Range<f64>, | ||
complex: Range<f64>, | ||
samples: (usize, usize), | ||
max_iter: usize, | ||
) -> impl Iterator<Item = (f64, f64, usize)> { | ||
let step = ( | ||
(real.end - real.start) / samples.0 as f64, | ||
(complex.end - complex.start) / samples.1 as f64, | ||
); | ||
return (0..(samples.0 * samples.1)).map(move |k| { | ||
Check warning on line 54 in tools/plotter/src/mandelbrot.rs GitHub Actions / clippy[clippy] tools/plotter/src/mandelbrot.rs#L54
Raw output
Check warning on line 54 in tools/plotter/src/mandelbrot.rs GitHub Actions / clippy[clippy] tools/plotter/src/mandelbrot.rs#L54
Raw output
|
||
let c = ( | ||
real.start + step.0 * (k % samples.0) as f64, | ||
complex.start + step.1 * (k / samples.0) as f64, | ||
); | ||
let mut z = (0.0, 0.0); | ||
let mut cnt = 0; | ||
while cnt < max_iter && z.0 * z.0 + z.1 * z.1 <= 1e10 { | ||
z = (z.0 * z.0 - z.1 * z.1 + c.0, 2.0 * z.0 * z.1 + c.1); | ||
cnt += 1; | ||
} | ||
return (c.0, c.1, cnt); | ||
Check warning on line 65 in tools/plotter/src/mandelbrot.rs GitHub Actions / clippy[clippy] tools/plotter/src/mandelbrot.rs#L65
Raw output
Check warning on line 65 in tools/plotter/src/mandelbrot.rs GitHub Actions / clippy[clippy] tools/plotter/src/mandelbrot.rs#L65
Raw output
|
||
}); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
use crate::DrawResult; | ||
use plotters::prelude::*; | ||
use plotters_canvas::CanvasBackend; | ||
use web_sys::HtmlCanvasElement; | ||
|
||
pub fn draw(canvas: HtmlCanvasElement, pitch: f64, yaw: f64) -> DrawResult<()> { | ||
let area = CanvasBackend::with_canvas_object(canvas) | ||
.unwrap() | ||
.into_drawing_area(); | ||
area.fill(&WHITE)?; | ||
|
||
let x_axis = (-3.0..3.0).step(0.1); | ||
let z_axis = (-3.0..3.0).step(0.1); | ||
|
||
let mut chart = | ||
ChartBuilder::on(&area).build_cartesian_3d(x_axis.clone(), -3.0..3.0, z_axis.clone())?; | ||
|
||
chart.with_projection(|mut pb| { | ||
pb.yaw = yaw; | ||
pb.pitch = pitch; | ||
pb.scale = 0.7; | ||
pb.into_matrix() | ||
}); | ||
|
||
chart.configure_axes().draw()?; | ||
|
||
chart.draw_series( | ||
SurfaceSeries::xoz(x_axis.values(), z_axis.values(), |x:f64, z:f64| { | ||
(x * x + z * z).cos() | ||
}) | ||
.style(&BLUE.mix(0.2)), | ||
)?; | ||
|
||
chart.draw_series(LineSeries::new( | ||
(-100..100) | ||
.map(|y| y as f64 / 40.0) | ||
.map(|y| ((y * 10.0).sin(), y, (y * 10.0).cos())), | ||
&BLACK, | ||
))?; | ||
|
||
Ok(()) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
if not exist "www\pkg" mkdir www\pkg | ||
rustup target add wasm32-unknown-unknown | ||
wasm-pack build --release | ||
if errorlevel 1 cargo install wasm-pack | ||
wasm-pack build --release | ||
cd www | ||
call npm install | ||
npm start |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
#!/bin/bash | ||
set -e | ||
|
||
CONFIG=release | ||
mkdir -p www/pkg | ||
|
||
rustup target add wasm32-unknown-unknown | ||
|
||
if [ -z "$(cargo install --list | grep wasm-pack)" ] | ||
then | ||
cargo install wasm-pack | ||
fi | ||
|
||
if [ "${CONFIG}" = "release" ] | ||
then | ||
wasm-pack build | ||
else | ||
wasm-pack build --release | ||
fi | ||
|
||
cd www | ||
npm install | ||
npm start |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
node_modules | ||
dist | ||
package-lock.json |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
init(); | ||
|
||
async function init() { | ||
if (typeof process == "object") { | ||
// We run in the npm/webpack environment. | ||
const [{Chart}, {main, setup}] = await Promise.all([ | ||
import("wasm-demo"), | ||
import("./index.js"), | ||
]); | ||
setup(Chart); | ||
main(); | ||
} else { | ||
const [{Chart, default: init}, {main, setup}] = await Promise.all([ | ||
import("../pkg/wasm_demo.js"), | ||
import("./index.js"), | ||
]); | ||
await init(); | ||
setup(Chart); | ||
main(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="utf-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1"> | ||
<title>Plotters WebAssembly Demo</title> | ||
<link rel="stylesheet" href="./style.css"> | ||
</head> | ||
<body> | ||
<noscript>This page contains webassembly and javascript content, please enable javascript in your browser.</noscript> | ||
<script src="./bootstrap.js"></script> | ||
<main> | ||
<h1>Plotters WebAssembly Demo</h1> | ||
<div id="coord"></div> | ||
<canvas id="canvas" width="600" height="400"></canvas> | ||
<div id="status">Loading WebAssembly...</div> | ||
<div id="control"> | ||
<label for="plot-type">Demo: </label> | ||
<select id="plot-type"> | ||
<option value="0">Graph of y=1</option> | ||
<option value="1">Graph of y=x</option> | ||
<option value="2">Graph of y=x^2</option> | ||
<option value="3">Graph of y=x^3</option> | ||
<option value="4">Graph of y=x^4</option> | ||
<option value="5">Graph of y=x^5</option> | ||
<option value="mandelbrot">Mandelbrot Set</option> | ||
<option value="3d-plot">3D Plot Demo</option> | ||
</select> | ||
<div id="3d-control" class="hide"> | ||
<label for="pitch">Pitch: </label> <input type="range" min="0" max="157" id="pitch" value="10"> <br/> | ||
<label for="yaw">Yaw: </label> <input type="range" min="0" max="314" id="yaw" value="50"> | ||
</div> | ||
</div> | ||
</main> | ||
<footer> | ||
<a href="https://github.com/plotters-rs/plotters-wasm-demo" target="a">Source</a> | | ||
<a href="https://github.com/plotters-rs/plotters" target="a">Repo</a> | | ||
<a href="https://crates.io/crates/plotters" target="a">Crates</a> | | ||
<a href="https://docs.rs/plotters" target="a">Docs</a> | ||
</footer> | ||
</body> | ||
</html> |