Skip to content

Commit

Permalink
This renders something
Browse files Browse the repository at this point in the history
  • Loading branch information
Quafadas committed Jul 12, 2023
1 parent 737606c commit 838c22c
Show file tree
Hide file tree
Showing 4 changed files with 166 additions and 111 deletions.
6 changes: 1 addition & 5 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,6 @@ ThisBuild / tlSonatypeUseLegacyHost := false
ThisBuild / tlCiReleaseBranches := Seq("main")
ThisBuild / scalaVersion := scalaV

// ThisBuild / scalaJSLinkerConfig ~= (
// _.withModuleKind(ModuleKind.ESModule)
// )

lazy val generated = crossProject(JVMPlatform, JSPlatform)
.in(file("generated"))
.settings(
Expand All @@ -65,7 +61,7 @@ lazy val generated = crossProject(JVMPlatform, JSPlatform)
)
)

lazy val root = tlCrossRootProject.aggregate(core, generated, laminarIntegration, unidocs, tests)
lazy val root = tlCrossRootProject.aggregate(core, generated, laminarIntegration,calicoIntegration, unidocs, tests)

lazy val core = crossProject(JVMPlatform, JSPlatform)
.in(file("core"))
Expand Down
96 changes: 62 additions & 34 deletions calico/src/main/scala/viz/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,21 @@ import scala.scalajs.js.annotation.*

import org.scalajs.dom
import scalajs.js.JSON
import calico.*
import calico.html.io.{*, given}
import calico.unsafe.given
import calico.syntax.*
import cats.effect.*
import fs2.*
import fs2.concurrent.*
import fs2.dom.*

import viz.PlotTargets.doNothing
import viz.extensions.*
import viz.vega.facades.EmbedOptions
import viz.vega.facades.VegaView
import viz.vega.facades.EmbedResult
import cats.effect.IO

import viz.vega.facades.Helpers.*

Expand Down Expand Up @@ -63,40 +72,59 @@ object CalicoViz:
* @param embedOpt
* \- optionally, the embed options you wish to use
*/
// def viewEmbed(
// chart: Spec,
// inDivOpt: Option[Div] = None,
// embedOpt: Option[EmbedOptions] = None
// ): (Div, Signal[Option[VegaView]]) =
// val specObj = JSON.parse(chart.spec).asInstanceOf[js.Object]

// val (embeddedIn, embedResult) = (inDivOpt, embedOpt) match
// case (Some(thisDiv), Some(opts)) =>
// val p: js.Promise[EmbedResult] = viz.vega.facades.VegaEmbed(thisDiv.ref, specObj, opts)
// (thisDiv, p)
// case (Some(thisDiv), None) =>
// val specObj = JSON.parse(chart.spec).asInstanceOf[js.Object]
// val p: js.Promise[EmbedResult] = viz.vega.facades.VegaEmbed(thisDiv.ref, specObj, EmbedOptions)
// (thisDiv, p)
// case (None, Some(opts)) =>
// val newDiv = div(
// width := "40vmin",
// height := "40vmin"
// )
// val p: js.Promise[EmbedResult] = viz.vega.facades.VegaEmbed(newDiv.ref, specObj, opts)
// (newDiv, p)
// case (None, None) =>
// val newDiv = div(
// width := "40vmin",
// height := "40vmin"
// )
// val p: js.Promise[EmbedResult] = viz.vega.facades.VegaEmbed(newDiv.ref, specObj, EmbedOptions)
// (newDiv, p)

// val viewSignal: Signal[Option[VegaView]] = Signal.fromJsPromise(embedResult).map(in => in.map(_.view))
// // val viewSignal: Signal[Option[VegaView]] = Signal.fromValue(None)

// (embeddedIn, viewSignal)
def viewEmbed(
chart: Spec,
inDivOpt: Option[Resource[IO, HtmlDivElement[IO]]] = None,
embedOpt: Option[EmbedOptions] = None
): Resource[IO, (HtmlDivElement[IO], IO[VegaView])] =

val specObj = JSON.parse(chart.spec).asInstanceOf[js.Object]
val tmp = (inDivOpt, embedOpt) match
case (Some(thisDiv), Some(opts)) =>
thisDiv.map { (d: HtmlDivElement[IO]) =>
val dCheat = d.asInstanceOf[org.scalajs.dom.html.Div]
dCheat.style.height = "40vmin"
dCheat.style.width = "40vmin"
val p: js.Promise[EmbedResult] = viz.vega.facades.VegaEmbed(d.asInstanceOf[org.scalajs.dom.html.Div], specObj, opts)
val pIop = IO.fromPromise(IO(p))
(d, pIop.map(_.view))
}
//case (Some(thisDiv), None) => ???
// This case doesn't work
// thisDiv.flatMap { (d: HtmlDivElement[IO]) =>
// val dCheat = d.asInstanceOf[org.scalajs.dom.html.Div]
// dCheat.style.height = "40vmin"
// dCheat.style.width = "40vmin"
// val p: js.Promise[EmbedResult] = viz.vega.facades.VegaEmbed(d.asInstanceOf[org.scalajs.dom.html.Div], specObj, opts)
// val pIop = IO.fromPromise[EmbedResult](IO(p)).toResource
// pIop.map(_.view).map((d, _))
// }
case _ => ???
// case (Some(thisDiv), None) =>
// val specObj = JSON.parse(chart.spec).asInstanceOf[js.Object]
// val p: js.Promise[EmbedResult] = viz.vega.facades.VegaEmbed(thisDiv.ref, specObj, EmbedOptions)
// (thisDiv, p)
// case (None, Some(opts)) =>
// val newDiv = div(
// width := "40vmin",
// height := "40vmin"
// )
// val p: js.Promise[EmbedResult] = viz.vega.facades.VegaEmbed(newDiv.ref, specObj, opts)
// (newDiv, p)
// case (None, None) =>
// val newDiv = div(
// width := "40vmin",
// height := "40vmin"
// )
// val p: js.Promise[EmbedResult] = viz.vega.facades.VegaEmbed(newDiv.ref, specObj, EmbedOptions)
// (newDiv, p)
tmp

// val viewSignal: IO[Option[VegaView]] = IO.fromPromise(embedResult).map(in => in.map(_.view))
// // val viewSignal: Signal[Option[VegaView]] = Signal.fromValue(None)

// (embeddedIn, viewSignal)
end viewEmbed
// end viewEmbed

/** Embed a chart in a div. This method is a good choice if you are not at all worried about performance (mostly you
Expand Down
83 changes: 28 additions & 55 deletions jsdocs/src/main/scala/livechart/LiveChartCalico.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import scala.scalajs.js
import scala.scalajs.js.annotation.*
//import scala.util.Random

import viz.extensions.*
import viz.vega.plots.{BarChart, given}
import calico.*
import calico.html.io.{*, given}
import calico.unsafe.given
Expand All @@ -13,21 +15,19 @@ import cats.effect.std.Random
import fs2.*
import fs2.concurrent.*
import fs2.dom.*
import viz.vega.facades.EmbedOptions

object MyCalicoApp extends IOWebApp:
def render: Resource[IO, HtmlElement[IO]] =
calicoChart
end MyCalicoApp
def render: Resource[IO, HtmlElement[IO]] = calicoChart

val dataSignal = SignallingRef[IO]
.of(List(2.4, 3.4, 5.1, -2.3))
end MyCalicoApp

def calicoChart: Resource[IO, HtmlDivElement[IO]] =
def calicoChart: Resource[IO, HtmlElement[IO]] =
SignallingRef[IO]
.of(List(2.4, 3.4, 5.1, -2.3))
.product(Channel.unbounded[IO, Int])
.toResource
.flatMap { (data, diff) =>
.flatMap { (data: SignallingRef[cats.effect.IO, List[Double]], diff) =>
div(
p("We want to make it as easy as possible, to build a chart"),
span("Here's a random data set: "),
Expand All @@ -36,60 +36,33 @@ def calicoChart: Resource[IO, HtmlDivElement[IO]] =
"Add a random number",
onClick --> (
_.evalMap(_ =>
//IO.println("clicked") >>
Random.scalaUtilRandom[IO].toResource.use{r => r.nextDouble.map(_* 5)}
Random.scalaUtilRandom[IO].toResource.use(r => r.nextDouble.map(_ * 5))
).foreach(newD =>
val d = data.get
IO.println(newD) >>
d.map(_ :+ newD).map(data.set).void
data.update(_ :+ newD).void
)
)
),
p("")
// child <-- data.signal.map { data =>
// val barChart: BarChart = data.plotBarChart(List(viz.Utils.fillDiv))
// LaminarViz.simpleEmbed(barChart)
// }
)
}

def Counter(label: String, initialStep: Int): Resource[IO, HtmlDivElement[IO]] =
SignallingRef[IO].of(initialStep).product(Channel.unbounded[IO, Int]).toResource.flatMap { (step, diff) =>

val allowedSteps = List(1, 2, 3, 5, 10)

div(
p(
"Step: ",
select.withSelf { self =>
(
allowedSteps.map(step => option(value := step.toString, step.toString)),
value <-- step.map(_.toString),
onChange --> {
_.evalMap(_ => self.value.get).map(_.toIntOption).unNone.foreach(step.set)
}
p(""),
data.map { data =>
val barChart: BarChart = data.plotBarChart(
List(
viz.Utils.fillDiv,
viz.Utils.removeYAxis
)
)
}
),
p(
label + ": ",
b(diff.stream.scanMonoid.map(_.toString).holdOptionResource),
" ",
button(
"-",
onClick --> {
_.evalMap(_ => step.get).map(-1 * _).foreach(diff.send(_).void)
val chartDiv = div("")
chartDiv.flatMap{ d =>
// To my astonishment, this doesn't work...
/* val dCheat = d.asInstanceOf[org.scalajs.dom.html.Div]
dCheat.style.height = "40vmin"
dCheat.style.width = "40vmin" */
// end yuck

// I had to set the div size down in here. Then it worked. But I have no idea why.
viz.CalicoViz.viewEmbed(barChart, Some(chartDiv), Some(EmbedOptions)).map(_._1)
}
),
button(
"+",
onClick --> (_.evalMap(_ => step.get).foreach(diff.send(_).void))
)
}
)
)
}

val app: Resource[IO, HtmlDivElement[IO]] = div(
h1("Let's count!"),
Counter("Sheep", initialStep = 3)
)
}
92 changes: 75 additions & 17 deletions raw_docs/ScalaVersions/scalaJS.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,17 @@
# Scala JS

The charts in these documents, are display using scala JS :-).

What turns out to be really nice about scala JS support, is the seamless transition between exploration in a repl on the JVM, luxuriating in it's rapid feedback and typsafe tooling, and subsequent publication into a browser with scala JS. It's the same code! There is a only a little more ceremony than with a repl - we need to decide the charts position in the document. i.e. find it a parent.

<mark>Gotcha : dedav ***does not include*** the underlying JS libraries out of it's box</mark>.

I may list out some toy examples on the github readme. Here's one...
[Mill, Scala Js, Snowpack, Laminar, Dedav](https://github.com/Quafadas/scalajs-snowpack-example)

## Scala JS UI frameworks
It turns out, that scala JS Dom is simply a facade for the browser API. Dedav works, through providing a reference to a scala js dom Div element.

Due to how fundamental the statement above is, we implicitly support _all_ JS UI frameworks. It must be possible to coerce the DIV wrapper of your framework into a scala js dom Div. However, some frameworks have a little more polish...
Due to how fundamental the statement above is, we implicitly support _all_ JS UI frameworks. It must be possible to coerce the DIV wrapper of your framework into a scala js dom Div. However, as I use some frameworks myself, it's a little easier to get started ...

## Integrations

### Laminar

See the `LaminarViz.simpleEmbed` function, to get started. It returns a div, which you can put, anywhere you want in your app. Here it's just added where it's constructed for the sake of simplicity.

The only constraint, is that it must have a well defined size and hieght.
The only constraint, is that the div must have a well defined size and height.

```scala mdoc:js
import com.raquo.laminar.api.L._
Expand Down Expand Up @@ -158,7 +149,7 @@ object chartExample:
chartDiv,
p("You last clicked on : ", child.text <-- chartDataClickedBus.map(textIfObject)),
p("You last hovered on : ", child.text <-- aSignalBus.map(textIfObject)),
p()
p(),p("")
)
end apply
end chartExample
Expand All @@ -172,7 +163,71 @@ Finally, this sets out some low leverl building blocks. If you were to know they

### Calico

Most peculiar
```scala mdoc:js
import scala.scalajs.js
import scala.scalajs.js.annotation.*

import viz.extensions.*
import viz.vega.plots.{BarChart, given}
import calico.*
import calico.html.io.{*, given}
import calico.unsafe.given
import calico.syntax.*
import cats.effect.*
import cats.effect.std.Random
import fs2.*
import fs2.concurrent.*
import fs2.dom.*
import viz.vega.facades.EmbedOptions

calicoChart.renderInto(node.asInstanceOf[fs2.dom.Node[IO]]).useForever.unsafeRunAndForget()

def calicoChart: Resource[IO, HtmlElement[IO]] =
SignallingRef[IO]
.of(List(2.4, 3.4, 5.1, -2.3))
.product(Channel.unbounded[IO, Int])
.toResource
.flatMap { (data: SignallingRef[cats.effect.IO, List[Double]], diff) =>
div(
p("We want to make it as easy as possible, to build a chart"),
span("Here's a random data set: "),
data.map(in => p(in.mkString("[", ",", "]"))),
button(
"Add a random number",
onClick --> (
_.evalMap(_ =>
Random.scalaUtilRandom[IO].toResource.use(r => r.nextDouble.map(_ * 5))
).foreach(newD =>
val d = data.get
IO.println(newD) >>
data.update(_ :+ newD).void
)
)
),
p(""),
data.map { data =>
val barChart: BarChart = data.plotBarChart(
List(
viz.Utils.fillDiv,
viz.Utils.removeYAxis
)
)
val chartDiv = div("")
chartDiv.flatMap{ d =>
// To my astonishment, this doesn't work...
/* val dCheat = d.asInstanceOf[org.scalajs.dom.html.Div]
dCheat.style.height = "40vmin"
dCheat.style.width = "40vmin" */
// end yuck

// I had to set the div size down in here. Then it worked.
viz.CalicoViz.viewEmbed(barChart, Some(chartDiv), Some(EmbedOptions)).map(_._1)
}
}
)
}

```

### MDoc
Is how this documentation works. Setup mdoc with scalajs bundler, and include vega in the bundle. Read the source of this library :-).
Expand All @@ -199,11 +254,14 @@ tlSiteHeliumConfig := {
The github repo of this documentation is a successful example!


# Ecosystem support
We have two orthogonal problems
# Conclusion

The charts in these documents, are displayed using scala JS :-).

What turns out to be really nice about scala JS support, is the seamless transition between exploration in a repl on the JVM, luxuriating in it's rapid feedback and typsafe tooling, and subsequent publication into a browser with scala JS. It's the same code! There is a only a little more ceremony than with a repl - we need to decide the charts position in the document. i.e. find it a parent.

<mark>Gotcha : dedav ***does not include*** the underlying JS libraries out of it's box</mark>.

1. How to we obtain the javascript libraries?
2. How to support the bouquet of scala JS UI frameworks?

## Javascript libraries
The example dependency is set out above. It _should_ work with _any_ bundling solution, or even by directly embedding the dependancies in the header of the html. Your choice.
Expand Down

0 comments on commit 838c22c

Please sign in to comment.