diff --git a/lang-trials/rust-benchmark/.gitignore b/lang-trials/rust-benchmark/.gitignore
new file mode 100644
index 0000000..5b910ca
--- /dev/null
+++ b/lang-trials/rust-benchmark/.gitignore
@@ -0,0 +1,3 @@
+target/*
+dist/*
+Cargo.lock
\ No newline at end of file
diff --git a/lang-trials/rust-benchmark/Cargo.toml b/lang-trials/rust-benchmark/Cargo.toml
new file mode 100644
index 0000000..8dde7b0
--- /dev/null
+++ b/lang-trials/rust-benchmark/Cargo.toml
@@ -0,0 +1,35 @@
+[package]
+name = "sycamore-trial"
+version = "0.1.0"
+authors = ["Aaron"]
+edition = "2021"
+
+[features]
+default = ["console_error_panic_hook"]
+
+[dependencies]
+nalgebra = "0.33.0"
+sycamore = "0.9.0-beta.2"
+typenum = "1.17.0"
+
+# The `console_error_panic_hook` crate provides better debugging of panics by
+# logging them with `console.error`. This is great for development, but requires
+# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
+# code size when deploying.
+console_error_panic_hook = { version = "0.1.7", optional = true }
+
+[dependencies.web-sys]
+version = "0.3.69"
+features = [
+ 'CanvasRenderingContext2d',
+ 'HtmlCanvasElement',
+ 'Window',
+ 'Performance'
+]
+
+[dev-dependencies]
+wasm-bindgen-test = "0.3.34"
+
+[profile.release]
+opt-level = "s" # optimize for small code size
+debug = true # include debug symbols
diff --git a/lang-trials/rust-benchmark/index.html b/lang-trials/rust-benchmark/index.html
new file mode 100644
index 0000000..82823fc
--- /dev/null
+++ b/lang-trials/rust-benchmark/index.html
@@ -0,0 +1,9 @@
+
+
+
+
+ The circular law
+
+
+
+
diff --git a/lang-trials/rust-benchmark/main.css b/lang-trials/rust-benchmark/main.css
new file mode 100644
index 0000000..f79e62c
--- /dev/null
+++ b/lang-trials/rust-benchmark/main.css
@@ -0,0 +1,23 @@
+body {
+ margin-left: 20px;
+ margin-top: 20px;
+ color: #fcfcfc;
+ background-color: #202020;
+}
+
+#app {
+ display: flex;
+ flex-direction: column;
+ width: 600px;
+}
+
+canvas {
+ float: left;
+ background-color: #020202;
+ border-radius: 10px;
+ margin-top: 5px;
+}
+
+input {
+ margin-top: 5px;
+}
\ No newline at end of file
diff --git a/lang-trials/rust-benchmark/notes b/lang-trials/rust-benchmark/notes
new file mode 100644
index 0000000..79a4030
--- /dev/null
+++ b/lang-trials/rust-benchmark/notes
@@ -0,0 +1,13 @@
+in profiling, most time is being spent in the `reflect` method:
+
+f64:
+ sycamore_trial-3d0aca3efee8b5fd.wasm.nalgebra::geometry::reflection::Reflection::reflect::h7899977a4ba0b1d3
+ sycamore_trial-3d0aca3efee8b5fd.wasm.nalgebra::geometry::reflection::Reflection::reflect::hc337c3cb6e3b4061
+ sycamore_trial-3d0aca3efee8b5fd.wasm.nalgebra::geometry::reflection::Reflection::reflect_rows::h43d0f6838d0c2833
+
+f32:
+ sycamore_trial-3d0aca3efee8b5fd.wasm.nalgebra::geometry::reflection::Reflection::reflect::h0e8ec322f198f847
+ sycamore_trial-3d0aca3efee8b5fd.wasm.nalgebra::geometry::reflection::Reflection::reflect::h9928bdd5e72743ea
+ sycamore_trial-3d0aca3efee8b5fd.wasm.nalgebra::geometry::reflection::Reflection::reflect_rows::h49f571fd8fc9b0f2
+
+in one test, we spent 4000 ms in "WASM closure", but the enveloping "VoidFunction" takes 1300 ms longer. in another test, though, there's no overhang; the 7000 ms we spent in `rand_eigval_series` accounts for basically the entire load time, and matches the clock timing
diff --git a/lang-trials/rust-benchmark/src/engine.rs b/lang-trials/rust-benchmark/src/engine.rs
new file mode 100644
index 0000000..a2661aa
--- /dev/null
+++ b/lang-trials/rust-benchmark/src/engine.rs
@@ -0,0 +1,164 @@
+use nalgebra::{*, allocator::Allocator};
+use std::f64::consts::{PI, E};
+/*use std::ops::Sub;*/
+/*use typenum::{B1, UInt, UTerm};*/
+
+/* dynamic matrices */
+pub fn rand_eigval_series(time_res: usize) -> Vec, Dyn>>
+ where
+ N: ToTypenum + DimName + DimSub,
+ DefaultAllocator:
+ Allocator +
+ Allocator +
+ Allocator<>::Output> +
+ Allocator>::Output>
+{
+ // initialize the random matrix
+ let dim = N::try_to_usize().unwrap();
+ let mut rand_mat = DMatrix::::from_fn(dim, dim, |j, k| {
+ let n = j*dim + k;
+ E*((n*n) as f64) % 2.0 - 1.0
+ }) * (3.0 / (dim as f64)).sqrt();
+
+ // initialize the rotation step
+ let mut rot_step = DMatrix::::identity(dim, dim);
+ let max_freq = 4;
+ for n in (0..dim).step_by(2) {
+ let ang = PI * ((n % max_freq) as f64) / (time_res as f64);
+ let ang_cos = ang.cos();
+ let ang_sin = ang.sin();
+ rot_step[(n, n)] = ang_cos;
+ rot_step[(n+1, n)] = ang_sin;
+ rot_step[(n, n+1)] = -ang_sin;
+ rot_step[(n+1, n+1)] = ang_cos;
+ }
+
+ // find the eigenvalues
+ let mut eigval_series = Vec::, Dyn>>::with_capacity(time_res);
+ eigval_series.push(rand_mat.complex_eigenvalues());
+ for _ in 1..time_res {
+ rand_mat = &rot_step * rand_mat;
+ eigval_series.push(rand_mat.complex_eigenvalues());
+ }
+ eigval_series
+}
+
+/* dynamic single float matrices */
+/*pub fn rand_eigval_series(time_res: usize) -> Vec, Dyn>>
+ where
+ N: ToTypenum + DimName + DimSub,
+ DefaultAllocator:
+ Allocator +
+ Allocator +
+ Allocator<>::Output> +
+ Allocator>::Output>
+{
+ // initialize the random matrix
+ let dim = N::try_to_usize().unwrap();
+ let mut rand_mat = DMatrix::::from_fn(dim, dim, |j, k| {
+ let n = j*dim + k;
+ (E as f32)*((n*n) as f32) % 2.0_f32 - 1.0_f32
+ }) * (3.0_f32 / (dim as f32)).sqrt();
+
+ // initialize the rotation step
+ let mut rot_step = DMatrix::::identity(dim, dim);
+ let max_freq = 4;
+ for n in (0..dim).step_by(2) {
+ let ang = (PI as f32) * ((n % max_freq) as f32) / (time_res as f32);
+ let ang_cos = ang.cos();
+ let ang_sin = ang.sin();
+ rot_step[(n, n)] = ang_cos;
+ rot_step[(n+1, n)] = ang_sin;
+ rot_step[(n, n+1)] = -ang_sin;
+ rot_step[(n+1, n+1)] = ang_cos;
+ }
+
+ // find the eigenvalues
+ let mut eigval_series = Vec::, Dyn>>::with_capacity(time_res);
+ eigval_series.push(rand_mat.complex_eigenvalues());
+ for _ in 1..time_res {
+ rand_mat = &rot_step * rand_mat;
+ eigval_series.push(rand_mat.complex_eigenvalues());
+ }
+ eigval_series
+}*/
+
+/* static matrices. should only be used when the dimension is really small */
+/*pub fn rand_eigval_series(time_res: usize) -> Vec, N>>
+ where
+ N: ToTypenum + DimName + DimSub,
+ DefaultAllocator:
+ Allocator +
+ Allocator +
+ Allocator<>::Output> +
+ Allocator>::Output>
+{
+ // initialize the random matrix
+ let dim = N::try_to_usize().unwrap();
+ let mut rand_mat = OMatrix::::from_fn(|j, k| {
+ let n = j*dim + k;
+ E*((n*n) as f64) % 2.0 - 1.0
+ }) * (3.0 / (dim as f64)).sqrt();
+ /*let mut rand_mat = OMatrix::::identity();*/
+
+ // initialize the rotation step
+ let mut rot_step = OMatrix::::identity();
+ let max_freq = 4;
+ for n in (0..dim).step_by(2) {
+ let ang = PI * ((n % max_freq) as f64) / (time_res as f64);
+ let ang_cos = ang.cos();
+ let ang_sin = ang.sin();
+ rot_step[(n, n)] = ang_cos;
+ rot_step[(n+1, n)] = ang_sin;
+ rot_step[(n, n+1)] = -ang_sin;
+ rot_step[(n+1, n+1)] = ang_cos;
+ }
+
+ // find the eigenvalues
+ let mut eigval_series = Vec::, N>>::with_capacity(time_res);
+ eigval_series.push(rand_mat.complex_eigenvalues());
+ for _ in 1..time_res {
+ rand_mat = &rot_step * rand_mat;
+ eigval_series.push(rand_mat.complex_eigenvalues());
+ }
+ eigval_series
+}*/
+
+/* another attempt at static matrices. i couldn't get the types to work out */
+/*pub fn random_eigval_series(time_res: usize) -> Vec, Const>>
+ where
+ Const: ToTypenum,
+ as ToTypenum>::Typenum: Sub>,
+ < as ToTypenum>::Typenum as Sub>>::Output: ToConst
+{
+ // initialize the random matrix
+ /*let mut rand_mat = SMatrix::::zeros();
+ for n in 0..N*N {
+ rand_mat[n] = E*((n*n) as f64) % 2.0 - 1.0;
+ }*/
+ let rand_mat = OMatrix::, Const>::from_fn(|j, k| {
+ let n = j*N + k;
+ E*((n*n) as f64) % 2.0 - 1.0
+ });
+
+ // initialize the rotation step
+ let mut rot_step = OMatrix::, Const>::identity();
+ let max_freq = 4;
+ for n in (0..N).step_by(2) {
+ let ang = PI * ((n % max_freq) as f64) / (time_res as f64);
+ let ang_cos = ang.cos();
+ let ang_sin = ang.sin();
+ rot_step[(n, n)] = ang_cos;
+ rot_step[(n+1, n)] = ang_sin;
+ rot_step[(n, n+1)] = -ang_sin;
+ rot_step[(n+1, n+1)] = ang_cos;
+ }
+
+ // find the eigenvalues
+ let mut eigvals = Vec::, Const>>::with_capacity(time_res);
+ unsafe { eigvals.set_len(time_res); }
+ for t in 0..time_res {
+ eigvals[t] = rand_mat.complex_eigenvalues();
+ }
+ eigvals
+}*/
\ No newline at end of file
diff --git a/lang-trials/rust-benchmark/src/main.rs b/lang-trials/rust-benchmark/src/main.rs
new file mode 100644
index 0000000..9662dd7
--- /dev/null
+++ b/lang-trials/rust-benchmark/src/main.rs
@@ -0,0 +1,78 @@
+use nalgebra::*;
+use std::f64::consts::PI as PI;
+use sycamore::{prelude::*, rt::{JsCast, JsValue}};
+use web_sys::window;
+
+mod engine;
+
+fn main() {
+ // set up a config option that forwards panic messages to `console.error`
+ #[cfg(feature = "console_error_panic_hook")]
+ console_error_panic_hook::set_once();
+
+ sycamore::render(|| {
+ let time_res: usize = 100;
+ let time_step = create_signal(0.0);
+ let run_time_report = create_signal(-1.0);
+ let display = create_node_ref();
+
+ on_mount(move || {
+ let performance = window().unwrap().performance().unwrap();
+ let start_time = performance.now();
+ let eigval_series = engine::rand_eigval_series::(time_res);
+ let run_time = performance.now() - start_time;
+ run_time_report.set(run_time);
+
+ let canvas = display
+ .get::()
+ .unchecked_into::();
+ let ctx = canvas
+ .get_context("2d")
+ .unwrap()
+ .unwrap()
+ .dyn_into::()
+ .unwrap();
+ ctx.set_fill_style(&JsValue::from("white"));
+
+ create_effect(move || {
+ // center and normalize the coordinate system
+ let width = canvas.width() as f64;
+ let height = canvas.height() as f64;
+ ctx.set_transform(1.0, 0.0, 0.0, -1.0, 0.5*width, 0.5*height).unwrap();
+
+ // clear the previous frame
+ ctx.clear_rect(-0.5*width, -0.5*width, width, height);
+
+ // find the resolution
+ const R_DISP: f64 = 1.5;
+ let res = width / (2.0*R_DISP);
+
+ // draw the eigenvalues
+ let eigvals = &eigval_series[time_step.get() as usize];
+ for n in 0..eigvals.len() {
+ ctx.begin_path();
+ ctx.arc(
+ /* typecast only needed for single float version */
+ res * f64::from(eigvals[n].re),
+ res * f64::from(eigvals[n].im),
+ 3.0,
+ 0.0, 2.0*PI
+ ).unwrap();
+ ctx.fill();
+ }
+ });
+ });
+
+ view! {
+ div(id="app") {
+ div { (run_time_report.get()) " ms" }
+ canvas(ref=display, width="600", height="600")
+ input(
+ type="range",
+ max=(time_res - 1).to_string(),
+ bind:valueAsNumber=time_step
+ )
+ }
+ }
+ });
+}
\ No newline at end of file
diff --git a/lang-trials/rust/.gitignore b/lang-trials/rust/.gitignore
index 1d4e644..5b910ca 100644
--- a/lang-trials/rust/.gitignore
+++ b/lang-trials/rust/.gitignore
@@ -1,2 +1,3 @@
target/*
-dist/*
\ No newline at end of file
+dist/*
+Cargo.lock
\ No newline at end of file
diff --git a/lang-trials/scala-benchmark/.gitignore b/lang-trials/scala-benchmark/.gitignore
new file mode 100644
index 0000000..9f2a453
--- /dev/null
+++ b/lang-trials/scala-benchmark/.gitignore
@@ -0,0 +1,2 @@
+target
+sbt.json
\ No newline at end of file
diff --git a/lang-trials/scala-benchmark/build.sbt b/lang-trials/scala-benchmark/build.sbt
new file mode 100644
index 0000000..507885d
--- /dev/null
+++ b/lang-trials/scala-benchmark/build.sbt
@@ -0,0 +1,9 @@
+enablePlugins(ScalaJSPlugin)
+
+name := "Circular Law"
+scalaVersion := "3.4.2"
+scalaJSUseMainModuleInitializer := true
+
+libraryDependencies += "com.raquo" %%% "laminar" % "17.0.0"
+libraryDependencies += "ai.dragonfly" %%% "slash" % "0.3.1"
+libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "2.8.0"
diff --git a/lang-trials/scala-benchmark/index.html b/lang-trials/scala-benchmark/index.html
new file mode 100644
index 0000000..f9eb11c
--- /dev/null
+++ b/lang-trials/scala-benchmark/index.html
@@ -0,0 +1,10 @@
+
+
+
+
+ The circular law
+
+
+
+
+
diff --git a/lang-trials/scala-benchmark/main.css b/lang-trials/scala-benchmark/main.css
new file mode 100644
index 0000000..f79e62c
--- /dev/null
+++ b/lang-trials/scala-benchmark/main.css
@@ -0,0 +1,23 @@
+body {
+ margin-left: 20px;
+ margin-top: 20px;
+ color: #fcfcfc;
+ background-color: #202020;
+}
+
+#app {
+ display: flex;
+ flex-direction: column;
+ width: 600px;
+}
+
+canvas {
+ float: left;
+ background-color: #020202;
+ border-radius: 10px;
+ margin-top: 5px;
+}
+
+input {
+ margin-top: 5px;
+}
\ No newline at end of file
diff --git a/lang-trials/scala-benchmark/project/build.properties b/lang-trials/scala-benchmark/project/build.properties
new file mode 100644
index 0000000..ee4c672
--- /dev/null
+++ b/lang-trials/scala-benchmark/project/build.properties
@@ -0,0 +1 @@
+sbt.version=1.10.1
diff --git a/lang-trials/scala-benchmark/project/plugins.sbt b/lang-trials/scala-benchmark/project/plugins.sbt
new file mode 100644
index 0000000..e5b2699
--- /dev/null
+++ b/lang-trials/scala-benchmark/project/plugins.sbt
@@ -0,0 +1 @@
+addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.16.0")
diff --git a/lang-trials/scala-benchmark/src/main/scala/CircularLawApp.scala b/lang-trials/scala-benchmark/src/main/scala/CircularLawApp.scala
new file mode 100644
index 0000000..950e661
--- /dev/null
+++ b/lang-trials/scala-benchmark/src/main/scala/CircularLawApp.scala
@@ -0,0 +1,89 @@
+import com.raquo.laminar.api.L.{*, given}
+import narr.*
+import org.scalajs.dom
+import org.scalajs.dom.document
+import scala.math.{cos, sin}
+import slash.matrix.Matrix
+import slash.matrix.decomposition.Eigen
+
+object CircularLawApp:
+ val canvas = canvasTag(widthAttr := 600, heightAttr := 600)
+ val ctx = canvas.ref.getContext("2d").asInstanceOf[dom.CanvasRenderingContext2D]
+
+ val (eigvalSeries, runTimeReport) = randEigvalSeries[60]()
+ val timeStepState = Var("0")
+
+ def draw(timeStep: String): Unit =
+ // center and normalize the coordinate system
+ val width = canvas.ref.width
+ val height = canvas.ref.height
+ ctx.setTransform(1d, 0d, 0d, -1d, 0.5*width, 0.5*height)
+
+ // clear the previous frame
+ ctx.clearRect(-0.5*width, -0.5*width, width, height)
+
+ // find the resolution
+ val rDisp: Double = 1.5
+ val res = width / (2*rDisp)
+
+ // draw the eigenvalues
+ val eigvals = eigvalSeries(timeStep.toInt)
+ for n <- 0 to eigvals(0).length-1 do
+ ctx.beginPath()
+ ctx.arc(
+ res * eigvals(0)(n),
+ res * eigvals(1)(n),
+ 3d,
+ 0d, 2*math.Pi
+ )
+ ctx.fill()
+
+ def eigvalsRotated[N <: Int](A: Matrix[N, N], time: Double)(using ValueOf[N]): (NArray[Double], NArray[Double]) =
+ // create transformation
+ val maxFreq = 4
+ val T = Matrix.identity[N, N]
+ val dim: Int = valueOf[N]
+ for n <- 0 to dim by 2 do
+ val a = cos(math.Pi * time * (n % maxFreq))
+ val b = sin(math.Pi * time * (n % maxFreq))
+ T(n, n) = a
+ T(n+1, n) = b
+ T(n, n+1) = -b
+ T(n+1, n+1) = a
+
+ // find eigenvalues
+ val eigen = Eigen(T*A)
+ (
+ eigen.realEigenvalues.asInstanceOf[NArray[Double]],
+ eigen.imaginaryEigenvalues.asInstanceOf[NArray[Double]]
+ )
+
+ def randEigvalSeries[N <: Int]()(using ValueOf[N]): (List[(NArray[Double], NArray[Double])], String) =
+ val timeRes = 100
+ val dim: Int = valueOf[N]
+ val startTime = System.currentTimeMillis()
+ val A = new Matrix[N, N](
+ NArray.tabulate(dim*dim)(k => (math.E*k*k) % 2 - 1)
+ ).times(math.sqrt(3d / dim))
+ val series = List.tabulate(timeRes)(t => eigvalsRotated(A, t.toDouble / timeRes))
+ val runTime = System.currentTimeMillis() - startTime
+ (series, runTime.toString() + " ms")
+
+ def main(args: Array[String]): Unit =
+ ctx.fillStyle = "white"
+
+ lazy val app = div(
+ idAttr := "app",
+ div(runTimeReport),
+ canvas,
+ input(
+ typ := "range",
+ maxAttr := (eigvalSeries.length-1).toString,
+ controlled(
+ value <-- timeStepState.signal,
+ onInput.mapToValue --> timeStepState.writer
+ ),
+ timeStepState.signal --> draw
+ )
+ )
+ renderOnDomContentLoaded(document.body, app)
diff --git a/lang-trials/scala/.gitignore b/lang-trials/scala/.gitignore
new file mode 100644
index 0000000..9f2a453
--- /dev/null
+++ b/lang-trials/scala/.gitignore
@@ -0,0 +1,2 @@
+target
+sbt.json
\ No newline at end of file
diff --git a/lang-trials/scala/build.sbt b/lang-trials/scala/build.sbt
new file mode 100644
index 0000000..908e7e9
--- /dev/null
+++ b/lang-trials/scala/build.sbt
@@ -0,0 +1,12 @@
+enablePlugins(ScalaJSPlugin)
+
+name := "Lattice Circle"
+scalaVersion := "3.4.2"
+
+// This is an application with a main method
+scalaJSUseMainModuleInitializer := true
+
+libraryDependencies += "com.raquo" %%% "laminar" % "17.0.0"
+/*libraryDependencies += "org.scalanlp" %% "breeze" % "2.1.0"*/
+libraryDependencies += "ai.dragonfly" %%% "slash" % "0.3.1"
+libraryDependencies += "org.scala-js" %%% "scalajs-dom" % "2.8.0"
diff --git a/lang-trials/scala/index.html b/lang-trials/scala/index.html
new file mode 100644
index 0000000..ff30e9f
--- /dev/null
+++ b/lang-trials/scala/index.html
@@ -0,0 +1,10 @@
+
+
+
+
+ Lattice circle
+
+
+
+
+
diff --git a/lang-trials/scala/main.css b/lang-trials/scala/main.css
new file mode 100644
index 0000000..3aedb99
--- /dev/null
+++ b/lang-trials/scala/main.css
@@ -0,0 +1,45 @@
+body {
+ margin-left: 20px;
+ margin-top: 20px;
+ color: #fcfcfc;
+ background-color: #202020;
+}
+
+input {
+ color: inherit;
+ background-color: #020202;
+ border: 1px solid #606060;
+ min-width: 40px;
+ border-radius: 4px;
+}
+
+input.point-1 {
+ border-color: #ba5d09;
+}
+
+input.point-2 {
+ border-color: #0e8a06;
+}
+
+input.point-3 {
+ border-color: #8951fb;
+}
+
+#data-panel {
+ float: left;
+ margin-left: 20px;
+ display: grid;
+ grid-template-columns: auto auto;
+ gap: 10px 10px;
+ width: 120px;
+}
+
+#data-panel > div {
+ text-align: center;
+}
+
+canvas {
+ float: left;
+ background-color: #020202;
+ border-radius: 10px;
+}
diff --git a/lang-trials/scala/project/build.properties b/lang-trials/scala/project/build.properties
new file mode 100644
index 0000000..ee4c672
--- /dev/null
+++ b/lang-trials/scala/project/build.properties
@@ -0,0 +1 @@
+sbt.version=1.10.1
diff --git a/lang-trials/scala/project/plugins.sbt b/lang-trials/scala/project/plugins.sbt
new file mode 100644
index 0000000..e5b2699
--- /dev/null
+++ b/lang-trials/scala/project/plugins.sbt
@@ -0,0 +1 @@
+addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.16.0")
diff --git a/lang-trials/scala/src/main/scala/LatticeCircleApp.scala b/lang-trials/scala/src/main/scala/LatticeCircleApp.scala
new file mode 100644
index 0000000..12ee36c
--- /dev/null
+++ b/lang-trials/scala/src/main/scala/LatticeCircleApp.scala
@@ -0,0 +1,160 @@
+// based on the Laminar example app
+//
+// https://github.com/raquo/laminar-examples/blob/master/src/main/scala/App.scala
+//
+// and Li Haoyi's example canvas app
+//
+// http://www.lihaoyi.com/hands-on-scala-js/#MakingaCanvasApp
+//
+
+import com.raquo.laminar.api.L.{*, given}
+import narr.*
+import org.scalajs.dom
+import org.scalajs.dom.document
+import scala.math
+import slash.matrix.*
+
+class Circle(var centerX: Double, var centerY: Double, var radius: Double)
+
+object LatticeCircleApp:
+ val canvas = canvasTag(widthAttr := 600, heightAttr := 600)
+ val ctx = canvas.ref.getContext("2d").asInstanceOf[dom.CanvasRenderingContext2D]
+ val data = List("-1", "0", "0", "-1", "1", "0").map(Var(_))
+
+ def circThru(points: Matrix[3, 2]): Option[Circle] =
+ // build the matrix that maps the circle's coefficient vector to the
+ // negative of the linear part of the circle's equation, evaluated at the
+ // given points
+ val negLinPart = Matrix.ones[3, 3]
+ negLinPart.setMatrix(0, 0, points * 2.0)
+
+ // find the quadrdatic part of the circle's equation, evaluated at the given
+ // points
+ val quadPart = Matrix[3, 1](
+ NArray.tabulate[Double](3)(
+ k => points(k, 0)*points(k, 0) + points(k, 1)*points(k, 1)
+ )
+ )
+
+ // find the circle's coefficient vector, and from there its center and
+ // radius
+ try
+ val coeffs = negLinPart.solve(quadPart)
+ val centerX = coeffs(0, 0)
+ val centerY = coeffs(1, 0)
+ Some(Circle(
+ centerX,
+ centerY,
+ math.sqrt(coeffs(2, 0) + centerX*centerX + centerY*centerY)
+ ))
+ catch
+ _ => return None
+
+ def draw(): Unit =
+ // center and normalize the coordinate system
+ val width = canvas.ref.width
+ val height = canvas.ref.height
+ ctx.setTransform(1.0, 0.0, 0.0, -1.0, 0.5*width, 0.5*height)
+
+ // clear the previous frame
+ ctx.clearRect(-0.5*width, -0.5*width, width, height)
+
+ // find the resolution
+ val rDisp = 5.0
+ val res = width / (2.0*rDisp)
+
+ // set colors
+ val highlightStyle = "white"
+ val gridStyle = "#404040"
+ val pointFillStyles = List("#ba5d09", "#0e8a06", "#8951fb")
+ val pointStrokeStyles = List("#f89142", "#58c145", "#c396fc")
+
+ // draw the grid
+ val rGrid = (rDisp - 0.01).floor.toInt
+ val edgeScr = res * rDisp
+ ctx.strokeStyle = gridStyle
+ for t <- -rGrid to rGrid do
+ val tScr = res * t
+
+ // draw horizontal grid line
+ ctx.beginPath();
+ ctx.moveTo(-edgeScr, tScr)
+ ctx.lineTo(edgeScr, tScr)
+ ctx.stroke()
+
+ // draw vertical grid line
+ ctx.beginPath();
+ ctx.moveTo(tScr, -edgeScr)
+ ctx.lineTo(tScr, edgeScr)
+ ctx.stroke()
+
+ // find and draw the circle through the given points
+ val dataNow = NArray.tabulate(6)(n =>
+ try
+ data(n).signal.now().toDouble
+ catch
+ _ => Double.NaN
+ )
+ if dataNow.forall(t => t == t.floor) then
+ // all of the coordinates are integer and non-NaN
+ val points = Matrix[3, 2](dataNow)
+ circThru(points) match
+ case Some(circ) =>
+ ctx.beginPath()
+ ctx.strokeStyle = highlightStyle
+ ctx.arc(
+ res * circ.centerX,
+ res * circ.centerY,
+ res * circ.radius,
+ 0.0, 2.0*math.Pi
+ )
+ ctx.stroke()
+ case None =>
+
+ // draw the data points
+ for n <- 0 to 2 do
+ val indX = 2*n
+ val indY = indX + 1
+ if
+ dataNow(indX) == dataNow(indX).floor &&
+ dataNow(indY) == dataNow(indY).floor
+ then
+ ctx.beginPath()
+ ctx.fillStyle = pointFillStyles(n)
+ ctx.strokeStyle = pointStrokeStyles(n)
+ ctx.arc(
+ res * dataNow(indX),
+ res * dataNow(indY),
+ 3.0,
+ 0.0, 2.0*math.Pi
+ )
+ ctx.fill()
+ ctx.stroke()
+
+ def coordInput(n: Int): Input =
+ input(
+ typ := "number",
+ cls := s"point-${(1.0 + 0.5*n).floor.toInt}",
+ controlled(
+ value <-- data(n).signal,
+ onInput.mapToValue --> data(n).writer
+ ),
+ data(n).signal --> { _ => draw() }
+ )
+
+ def main(args: Array[String]): Unit =
+ lazy val app = div(
+ canvas,
+ div(
+ idAttr := "data-panel",
+ div("x"),
+ div("y"),
+ coordInput(0),
+ coordInput(1),
+ coordInput(2),
+ coordInput(3),
+ coordInput(4),
+ coordInput(5)
+ )
+ )
+ renderOnDomContentLoaded(document.body, app)