Hack together a "Hello, world" in Scala with Laminar
This commit is contained in:
parent
244f222eb0
commit
c376fcdad8
2
lang-trials/scala/.gitignore
vendored
Normal file
2
lang-trials/scala/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
target
|
||||||
|
sbt.json
|
12
lang-trials/scala/build.sbt
Normal file
12
lang-trials/scala/build.sbt
Normal file
@ -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"
|
10
lang-trials/scala/index.html
Normal file
10
lang-trials/scala/index.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Lattice circle</title>
|
||||||
|
<script type="text/javascript" src="./target/scala-3.4.2/lattice-circle-fastopt/main.js"></script>
|
||||||
|
<link rel="stylesheet" href="main.css"/>
|
||||||
|
</head>
|
||||||
|
<body></body>
|
||||||
|
</html>
|
50
lang-trials/scala/main.css
Normal file
50
lang-trials/scala/main.css
Normal file
@ -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;
|
||||||
|
}
|
1
lang-trials/scala/project/build.properties
Normal file
1
lang-trials/scala/project/build.properties
Normal file
@ -0,0 +1 @@
|
|||||||
|
sbt.version=1.10.1
|
1
lang-trials/scala/project/plugins.sbt
Normal file
1
lang-trials/scala/project/plugins.sbt
Normal file
@ -0,0 +1 @@
|
|||||||
|
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.16.0")
|
253
lang-trials/scala/src/main/scala/LatticeCircleApp.scala
Normal file
253
lang-trials/scala/src/main/scala/LatticeCircleApp.scala
Normal file
@ -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)
|
Loading…
Reference in New Issue
Block a user