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)