Skip to content

Commit

Permalink
copied plotters-wasm-demo to repo
Browse files Browse the repository at this point in the history
  • Loading branch information
andrei-marinica committed Jun 17, 2024
1 parent 449ef21 commit 3387017
Show file tree
Hide file tree
Showing 18 changed files with 1,458 additions and 80 deletions.
959 changes: 879 additions & 80 deletions Cargo.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ members = [

"tools/mxpy-snippet-generator",
"tools/payload-macro-generator",
"tools/plotter",

"vm",

Expand Down
3 changes: 3 additions & 0 deletions tools/plotter/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/target
.*.sw*
.vscode/*
18 changes: 18 additions & 0 deletions tools/plotter/Cargo.toml
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
54 changes: 54 additions & 0 deletions tools/plotter/README.md
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
31 changes: 31 additions & 0 deletions tools/plotter/src/func_plot.rs
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());
}
58 changes: 58 additions & 0 deletions tools/plotter/src/lib.rs
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

View workflow job for this annotation

GitHub Actions / clippy

[clippy] tools/plotter/src/lib.rs#L18

warning: very complex type used. Consider factoring parts into `type` definitions --> tools/plotter/src/lib.rs:18:14 | 18 | convert: Box<dyn Fn((i32, i32)) -> Option<(f64, f64)>>, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity = note: `#[warn(clippy::type_complexity)]` on by default
Raw output
tools/plotter/src/lib.rs:18:14:w:warning: very complex type used. Consider factoring parts into `type` definitions
  --> tools/plotter/src/lib.rs:18:14
   |
18 |     convert: Box<dyn Fn((i32, i32)) -> Option<(f64, f64)>>,
   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity
   = note: `#[warn(clippy::type_complexity)]` on by default


__END__

Check warning on line 18 in tools/plotter/src/lib.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] tools/plotter/src/lib.rs#L18

warning: very complex type used. Consider factoring parts into `type` definitions --> tools/plotter/src/lib.rs:18:14 | 18 | convert: Box<dyn Fn((i32, i32)) -> Option<(f64, f64)>>, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity = note: `#[warn(clippy::type_complexity)]` on by default
Raw output
tools/plotter/src/lib.rs:18:14:w:warning: very complex type used. Consider factoring parts into `type` definitions
  --> tools/plotter/src/lib.rs:18:14
   |
18 |     convert: Box<dyn Fn((i32, i32)) -> Option<(f64, f64)>>,
   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#type_complexity
   = note: `#[warn(clippy::type_complexity)]` on by default


__END__
}

/// 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 })
}
}
67 changes: 67 additions & 0 deletions tools/plotter/src/mandelbrot.rs
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

View workflow job for this annotation

GitHub Actions / clippy

[clippy] tools/plotter/src/mandelbrot.rs#L54

warning: unneeded `return` statement --> tools/plotter/src/mandelbrot.rs:54:5 | 54 | / return (0..(samples.0 * samples.1)).map(move |k| { 55 | | let c = ( 56 | | real.start + step.0 * (k % samples.0) as f64, 57 | | complex.start + step.1 * (k / samples.0) as f64, ... | 65 | | return (c.0, c.1, cnt); 66 | | }); | |______^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return = note: `#[warn(clippy::needless_return)]` on by default help: remove `return` | 54 ~ (0..(samples.0 * samples.1)).map(move |k| { 55 + let c = ( 56 + real.start + step.0 * (k % samples.0) as f64, 57 + complex.start + step.1 * (k / samples.0) as f64, 58 + ); 59 + let mut z = (0.0, 0.0); 60 + let mut cnt = 0; 61 + while cnt < max_iter && z.0 * z.0 + z.1 * z.1 <= 1e10 { 62 + z = (z.0 * z.0 - z.1 * z.1 + c.0, 2.0 * z.0 * z.1 + c.1); 63 + cnt += 1; 64 + } 65 + return (c.0, c.1, cnt); 66 ~ }) |
Raw output
tools/plotter/src/mandelbrot.rs:54:5:w:warning: unneeded `return` statement
  --> tools/plotter/src/mandelbrot.rs:54:5
   |
54 | /     return (0..(samples.0 * samples.1)).map(move |k| {
55 | |         let c = (
56 | |             real.start + step.0 * (k % samples.0) as f64,
57 | |             complex.start + step.1 * (k / samples.0) as f64,
...  |
65 | |         return (c.0, c.1, cnt);
66 | |     });
   | |______^
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return
   = note: `#[warn(clippy::needless_return)]` on by default
help: remove `return`
   |
54 ~     (0..(samples.0 * samples.1)).map(move |k| {
55 +         let c = (
56 +             real.start + step.0 * (k % samples.0) as f64,
57 +             complex.start + step.1 * (k / samples.0) as f64,
58 +         );
59 +         let mut z = (0.0, 0.0);
60 +         let mut cnt = 0;
61 +         while cnt < max_iter && z.0 * z.0 + z.1 * z.1 <= 1e10 {
62 +             z = (z.0 * z.0 - z.1 * z.1 + c.0, 2.0 * z.0 * z.1 + c.1);
63 +             cnt += 1;
64 +         }
65 +         return (c.0, c.1, cnt);
66 ~     })
   |


__END__

Check warning on line 54 in tools/plotter/src/mandelbrot.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] tools/plotter/src/mandelbrot.rs#L54

warning: unneeded `return` statement --> tools/plotter/src/mandelbrot.rs:54:5 | 54 | / return (0..(samples.0 * samples.1)).map(move |k| { 55 | | let c = ( 56 | | real.start + step.0 * (k % samples.0) as f64, 57 | | complex.start + step.1 * (k / samples.0) as f64, ... | 65 | | return (c.0, c.1, cnt); 66 | | }); | |______^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return = note: `#[warn(clippy::needless_return)]` on by default help: remove `return` | 54 ~ (0..(samples.0 * samples.1)).map(move |k| { 55 + let c = ( 56 + real.start + step.0 * (k % samples.0) as f64, 57 + complex.start + step.1 * (k / samples.0) as f64, 58 + ); 59 + let mut z = (0.0, 0.0); 60 + let mut cnt = 0; 61 + while cnt < max_iter && z.0 * z.0 + z.1 * z.1 <= 1e10 { 62 + z = (z.0 * z.0 - z.1 * z.1 + c.0, 2.0 * z.0 * z.1 + c.1); 63 + cnt += 1; 64 + } 65 + return (c.0, c.1, cnt); 66 ~ }) |
Raw output
tools/plotter/src/mandelbrot.rs:54:5:w:warning: unneeded `return` statement
  --> tools/plotter/src/mandelbrot.rs:54:5
   |
54 | /     return (0..(samples.0 * samples.1)).map(move |k| {
55 | |         let c = (
56 | |             real.start + step.0 * (k % samples.0) as f64,
57 | |             complex.start + step.1 * (k / samples.0) as f64,
...  |
65 | |         return (c.0, c.1, cnt);
66 | |     });
   | |______^
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return
   = note: `#[warn(clippy::needless_return)]` on by default
help: remove `return`
   |
54 ~     (0..(samples.0 * samples.1)).map(move |k| {
55 +         let c = (
56 +             real.start + step.0 * (k % samples.0) as f64,
57 +             complex.start + step.1 * (k / samples.0) as f64,
58 +         );
59 +         let mut z = (0.0, 0.0);
60 +         let mut cnt = 0;
61 +         while cnt < max_iter && z.0 * z.0 + z.1 * z.1 <= 1e10 {
62 +             z = (z.0 * z.0 - z.1 * z.1 + c.0, 2.0 * z.0 * z.1 + c.1);
63 +             cnt += 1;
64 +         }
65 +         return (c.0, c.1, cnt);
66 ~     })
   |


__END__
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

View workflow job for this annotation

GitHub Actions / clippy

[clippy] tools/plotter/src/mandelbrot.rs#L65

warning: unneeded `return` statement --> tools/plotter/src/mandelbrot.rs:65:9 | 65 | return (c.0, c.1, cnt); | ^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return help: remove `return` | 65 - return (c.0, c.1, cnt); 65 + (c.0, c.1, cnt) |
Raw output
tools/plotter/src/mandelbrot.rs:65:9:w:warning: unneeded `return` statement
  --> tools/plotter/src/mandelbrot.rs:65:9
   |
65 |         return (c.0, c.1, cnt);
   |         ^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return
help: remove `return`
   |
65 -         return (c.0, c.1, cnt);
65 +         (c.0, c.1, cnt)
   |


__END__

Check warning on line 65 in tools/plotter/src/mandelbrot.rs

View workflow job for this annotation

GitHub Actions / clippy

[clippy] tools/plotter/src/mandelbrot.rs#L65

warning: unneeded `return` statement --> tools/plotter/src/mandelbrot.rs:65:9 | 65 | return (c.0, c.1, cnt); | ^^^^^^^^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return help: remove `return` | 65 - return (c.0, c.1, cnt); 65 + (c.0, c.1, cnt) |
Raw output
tools/plotter/src/mandelbrot.rs:65:9:w:warning: unneeded `return` statement
  --> tools/plotter/src/mandelbrot.rs:65:9
   |
65 |         return (c.0, c.1, cnt);
   |         ^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_return
help: remove `return`
   |
65 -         return (c.0, c.1, cnt);
65 +         (c.0, c.1, cnt)
   |


__END__
});
}
42 changes: 42 additions & 0 deletions tools/plotter/src/plot3d.rs
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(())
}
8 changes: 8 additions & 0 deletions tools/plotter/start-server.bat
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
23 changes: 23 additions & 0 deletions tools/plotter/start-server.sh
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
3 changes: 3 additions & 0 deletions tools/plotter/www/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
dist
package-lock.json
21 changes: 21 additions & 0 deletions tools/plotter/www/bootstrap.js
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();
}
}
42 changes: 42 additions & 0 deletions tools/plotter/www/index.html
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>
Loading

0 comments on commit 3387017

Please sign in to comment.