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..ea89d47
--- /dev/null
+++ b/lang-trials/scala/main.css
@@ -0,0 +1,50 @@
+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;
+}
+
+#result-display {
+ margin-top: 10px;
+ font-weight: bold;
+}
+
+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..602b2fc
--- /dev/null
+++ b/lang-trials/scala/src/main/scala/LatticeCircleApp.scala
@@ -0,0 +1,253 @@
+// 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 breeze.linalg._*/
+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 dataBusList = List.tabulate(6)(_ => new EventBus[Double])
+ val dataStream = dataBusList(0).events
+ .combineWith(dataBusList(1).events)
+ .combineWith(dataBusList(2).events)
+ .combineWith(dataBusList(3).events)
+ .combineWith(dataBusList(4).events)
+ .combineWith(dataBusList(5).events)
+ .map(dataTuple => NArray(dataTuple(0), dataTuple(1), dataTuple(2), dataTuple(3), dataTuple(4), dataTuple(5)))
+ .filter(data => data.forall(!_.isNaN()))
+ /*
+ val dataList = List(-1.0, 0.0, 0.0, -1.0, 1.0, 0.0).map(Var(_))
+ val dataStream = dataList(0).signal
+ .combineWith(dataList(1).signal)
+ .combineWith(dataList(2).signal)
+ .combineWith(dataList(3).signal)
+ .combineWith(dataList(4).signal)
+ .combineWith(dataList(5).signal)
+ .map(dataTuple => NArray(dataTuple(0), dataTuple(1), dataTuple(2), dataTuple(3), dataTuple(4), dataTuple(5)))
+ */
+ val pointStream = dataStream.map(data => Matrix[3, 2](data))
+ /* Breeze version */
+ /*
+ val pointStream = dataStream.map(data => new DenseMatrix(2, 3, Array(data(0), data(1), data(2), data(3), data(4), data(5))))
+ */
+
+ /* Breeze version */
+ /*
+ def circThru(points: DenseMatrix[Double]): 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 = DenseMatrix.horzcat(
+ 2.0*points.t,
+ DenseMatrix.ones[Double](3, 1)
+ )
+
+ // find the quadrdatic part of the circle's equation, evaluated at the given
+ // points
+ val quadPart = points(::, *).map(v => v dot v)
+
+ // find the circle's coefficient vector, and from there its center and
+ // radius
+ val coeffs = negLinPart \ quadPart
+ val centerX = coeffs(0, 0)
+ val centerY = coeffs(1, 0)
+ Circle(
+ centerX,
+ centerY,
+ math.sqrt(coeffs(2, 0) + centerX*centerX + centerY*centerY)
+ )
+ */
+
+ 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)
+ println("built neg lin part")
+ println(negLinPart)
+
+ // 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)
+ )
+ )
+ println("build quad part")
+ println(quadPart)
+
+ // 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(points: Matrix[3, 2]): 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
+ 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
+ ctx.beginPath()
+ ctx.fillStyle = pointFillStyles(n)
+ ctx.strokeStyle = pointStrokeStyles(n)
+ ctx.arc(
+ res * points(n, 0),
+ res * points(n, 1),
+ 3.0,
+ 0.0, 2.0*math.Pi
+ )
+ ctx.fill()
+ ctx.stroke()
+
+ def main(args: Array[String]): Unit =
+ lazy val app = div(
+ canvas,
+ div(
+ pointStream --> draw,
+ idAttr := "data-panel",
+ div("x"),
+ div("y"),
+ input(typ := "number", cls := "point-1", inContext(thisNode => onInput.mapTo(thisNode.ref.valueAsNumber) --> dataBusList(0))),
+ input(typ := "number", cls := "point-1", inContext(thisNode => onInput.mapTo(thisNode.ref.valueAsNumber) --> dataBusList(1))),
+ input(typ := "number", cls := "point-2", inContext(thisNode => onInput.mapTo(thisNode.ref.valueAsNumber) --> dataBusList(2))),
+ input(typ := "number", cls := "point-2", inContext(thisNode => onInput.mapTo(thisNode.ref.valueAsNumber) --> dataBusList(3))),
+ input(typ := "number", cls := "point-3", inContext(thisNode => onInput.mapTo(thisNode.ref.valueAsNumber) --> dataBusList(4))),
+ input(typ := "number", cls := "point-3", inContext(thisNode => onInput.mapTo(thisNode.ref.valueAsNumber) --> dataBusList(5)))
+ /* attempt to use controlled inputs */
+ /*
+ input(
+ typ := "number",
+ cls := "point-1",
+ controlled(
+ value <-- dataList(0).signal,
+ inContext(
+ thisNode => onInput.mapTo(thisNode.ref.valueAsNumber) --> dataList(0).writer
+ )
+ )
+ ),
+ input(
+ typ := "number",
+ cls := "point-1",
+ controlled(
+ value <-- dataList(1).signal,
+ inContext(
+ thisNode => onInput.mapTo(thisNode.ref.valueAsNumber) --> dataList(1).writer
+ )
+ )
+ ),
+ input(
+ typ := "number",
+ cls := "point-2",
+ controlled(
+ value <-- dataList(2).signal,
+ inContext(
+ thisNode => onInput.mapTo(thisNode.ref.valueAsNumber) --> dataList(2).writer
+ )
+ )
+ ),
+ input(
+ typ := "number",
+ cls := "point-2",
+ controlled(
+ value <-- dataList(3).signal,
+ inContext(
+ thisNode => onInput.mapTo(thisNode.ref.valueAsNumber) --> dataList(3).writer
+ )
+ )
+ ),
+ input(
+ typ := "number",
+ cls := "point-3",
+ controlled(
+ value <-- dataList(4).signal,
+ inContext(
+ thisNode => onInput.mapTo(thisNode.ref.valueAsNumber) --> dataList(4).writer
+ )
+ )
+ ),
+ input(
+ typ := "number",
+ cls := "point-3",
+ controlled(
+ value <-- dataList(5).signal,
+ inContext(
+ thisNode => onInput.mapTo(thisNode.ref.valueAsNumber) --> dataList(5).writer
+ )
+ )
+ )
+ */
+ )
+ )
+ renderOnDomContentLoaded(document.body, app)