Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/chart.js #58

Merged
merged 11 commits into from
Oct 15, 2023
16 changes: 2 additions & 14 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ val appVersion= "1.0"

lazy val scalaV = "2.13.12"

val jqueryName: String = "jquery/2.1.4/jquery.js"

val jqPlot = "org.webjars" % "jqplot" % "1.0.8r1250"

val bootstrap = "org.webjars" % "bootstrap" % "5.3.2"

import com.typesafe.sbt.web.PathMapping
Expand Down Expand Up @@ -111,7 +107,6 @@ lazy val server = (project in file("server")).settings(
"com.vmunier" %% "scalajs-scripts" % "1.2.0",
guice,
bootstrap,
jqPlot,
specs2 % Test
),
).enablePlugins(PlayScala, JSDependenciesPlugin, SbtWeb)//.dependsOn(sharedJvm)
Expand Down Expand Up @@ -141,15 +136,8 @@ lazy val client = (project in file("client")).settings(
bootstrap / "bootstrap.bundle.min.js",
"org.webjars" % "momentjs" % "2.10.6" / "min/moment.min.js",
"org.webjars" % "moment-timezone" % "0.4.0-1" / "moment-timezone-with-data.js" dependsOn "min/moment.min.js",
"org.webjars" % "jquery" % "2.1.4" / jqueryName minified "jquery/2.1.4/jquery.min.js",
jqPlot / "jquery.jqplot.min.js" dependsOn jqueryName,
jqPlot / "jqplot.dateAxisRenderer.min.js" dependsOn "jquery.jqplot.min.js",
jqPlot / "jqplot.categoryAxisRenderer.min.js" dependsOn "jquery.jqplot.min.js",
jqPlot / "jqplot.bubbleRenderer.min.js" dependsOn "jquery.jqplot.min.js",
jqPlot / "jqplot.pieRenderer.min.js" dependsOn "jquery.jqplot.min.js",
jqPlot / "jqplot.barRenderer.min.js" dependsOn "jquery.jqplot.min.js",
jqPlot / "jqplot.pointLabels.min.js" dependsOn "jquery.jqplot.min.js",
jqPlot / "jqplot.highlighter.min.js" dependsOn "jquery.jqplot.min.js",
"org.webjars.npm" % "chart.js" % "4.4.0" / "chart.umd.js",
"org.webjars.npm" % "chartjs-adapter-moment" % "1.0.0" / "chartjs-adapter-moment.min.js" dependsOn "chart.umd.js",
),
).enablePlugins(ScalaJSPlugin, JSDependenciesPlugin, ScalaJSWeb)//.dependsOn(sharedJs)

Expand Down
1 change: 1 addition & 0 deletions client/src/main/scala/com/example/Moment.scala
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import scala.scalajs.js.Date
@js.native
trait Moment extends js.Any {
def add(time: Int, units: String): Moment = js.native
def subtract(time: Int, units: String): Moment = js.native
//def plus(time: Int, units: String): Moment = js.native


Expand Down
153 changes: 153 additions & 0 deletions client/src/main/scala/com/v6ak/zbdb/ChartJsUtils.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package com.v6ak.zbdb

import scala.scalajs.js
import scala.scalajs.js.annotation._
import org.scalajs.dom._
import scala.scalajs.js.Dynamic.literal
import scala.scalajs.js.JSConverters._
import scalatags.JsDom.all._
import Bootstrap.DialogUtils
import com.example.moment.{Moment, moment}

@JSGlobal @js.native class Chart(el: Element, data: js.Any) extends js.Object {
def resize(): Unit = js.native
def update(): Unit = js.native
def destroy(): Unit = js.native
}

object ChartJsUtils {
// Chart.register(…)

def zeroMoment = moment("2000-01-01") // We don't want to mutate it

def timeAxis(label: String, min: js.Any = js.undefined) = literal(
`type` = "time",
min = min,
time = literal(
unit = "minute",
displayFormats = literal(
minute = "HH:mm",
),
),
ticks = literal(
stepSize = 5,
),
title = literal(
display = true,
text = label,
),
)

def durationAxis(label: String, min: js.Any) = literal(
`type` = "time",
min = min,
ticks = literal(
callback = (value: Moment, _index: js.Dynamic, _ticks: js.Dynamic) => {
val diff = value - zeroMoment
val hours = diff / 1000 / 60 / 60
val minutes = diff / 1000 / 60 - hours * 60
f"$hours%02d:$minutes%02d"
},
stepSize = 30,
),
title = literal(
display = true,
text = label,
),
)
def minutesAxis(label: String) = literal(
min = 0,
title = literal(
display = true,
text = label,
),
)

def distanceAxis = {
literal(
`type` = "linear",
min = 0,
title = literal(
display = true,
text = "vzdálenost (km)",
),
)
}

def speedAxis = {
literal(
`type` = "linear",
min = 0,
title = literal(
display = true,
text = "rychlost (km/h)",
),
)
}

def dataFromPairs(seq: Seq[(Any, Any)]) = literal(
labels = seq.map(_._1).toJSArray,
datasets = js.Array(
literal(
data = seq.map(_._2).toJSArray,
)
)
)

def dataFromTriples(seq: Seq[(Any, Any, String)]) = literal(
labels = seq.map(_._1).toJSArray,
datasets = js.Array(
literal(
backgroundColor = seq.map(_._3).toJSArray,
data = seq.map(_._2).toJSArray,
)
)
)

def initializePlot(plotRoot: HTMLElement, plotParams: js.Any, registerDestroy: (()=>Unit) => Unit): Unit = {
console.log("plotParams", plotParams)

val can = canvas().render
plotRoot.appendChild(
div(`class` := s"ratio plot plot-${plotParams.asInstanceOf[js.Dynamic].`type`}")(
div(can)
).render
)

val chart = new Chart(can, plotParams)
val resizeHandler: Event => Unit = _ => {
// seems no longer needed
//chart.update()
//chart.resize()
}

window.addEventListener("resize", resizeHandler)

def destroy(): Unit = {
window.removeEventListener("resize", resizeHandler)
chart.destroy()
}

registerDestroy(destroy)
}

def showChartInModal(title: String = null)(f: Seq[Participant] => js.Any): (Option[String], (HTMLElement, => Seq[Participant], ParticipantTable) => Unit) =
Option(title) -> ((el, rowsLoader, participantTable: Any) => {
val plotParams = f(rowsLoader)
val modalElement = el.parentNode.parentNode.parentNode.asInstanceOf[Element]
initializePlot(el, plotParams, destroy => modalElement.onBsModalHidden(destroy))
}
)

def plotLinesToData(data: Seq[PlotLine]) = {
literal(
datasets = data.map(ser =>
literal(
label = s"${ser.row.id}: ${ser.row.fullNameWithNick}",
data = ser.points,
)
).toJSArray,
)
}

}
4 changes: 4 additions & 0 deletions client/src/main/scala/com/v6ak/zbdb/CollectionUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@ object CollectionUtils {
(LazyList.continually(separator) lazyZip s).flatMap{(x, y) => Seq(x, y)}.drop(1)
def causeStream(e: Throwable): LazyList[Throwable] =
LazyList.cons(e, Option(e.getCause).fold(LazyList.empty[Throwable])(causeStream))

implicit class RichMap[K, V](val map: Map[K, V]) extends AnyVal {
def mapValuesStrict[W](f: V => W): Map[K, W] = map.view.mapValues(f).toMap
}
}
19 changes: 18 additions & 1 deletion client/src/main/scala/com/v6ak/zbdb/Gender.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.v6ak.zbdb

import scala.scalajs.js
import scala.language.implicitConversions

abstract sealed class Gender(){
def inflect[T](feminine:T, masculine: T): T
}
Expand All @@ -11,4 +14,18 @@ object Gender{
case object Female extends Gender {
override def inflect[T](feminine: T, masculine: T): T = feminine
}
}
}

final class RichGenderSeq(val seq: Seq[Gender]) extends AnyVal {
def inflectCzech[T](feminine: T, masculine: T): T = if(seq.contains(Gender.Male)) masculine else feminine
def inflectCzech[T](feminineSingular: T, masculineSingular: T, femininePlural: T, masculinePlural: T): T = seq match {
case Seq(one) => one.inflect(feminine = feminineSingular, masculine = masculineSingular)
case _ => inflectCzech(feminine = femininePlural, masculine = masculinePlural)
}
}

object RichGenderSeq {
@inline implicit def toRichGenderSeq(seq: Seq[Gender]): RichGenderSeq = new RichGenderSeq(seq)
@inline implicit def toRichGenderSeq(seq: js.Array[Gender]): RichGenderSeq = new RichGenderSeq(seq.toSeq)

}
5 changes: 2 additions & 3 deletions client/src/main/scala/com/v6ak/zbdb/HtmlUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,11 @@ object HtmlUtils {
)

def modal(title: Frag, keyboard: Boolean = true) = {
val modalBodyId = IdGenerator.newId()
val modalHeader = div(`class`:="modal-header")(
h5(`class`:="modal-title")(title),
button(`type`:="button", `class`:="btn-close", dismiss := "modal", aria.label := "Zavřít"),
)
val modalBody = div(`class`:="modal-body", id := modalBodyId)
val modalBody = div(`class`:="modal-body").render
val modalFooter = div(`class`:="modal-footer")
val modalDialog = div(`class`:="modal-dialog modal-xxl")(
div(`class`:="modal-content")(
Expand All @@ -32,7 +31,7 @@ object HtmlUtils {
val dialog = div(`class`:="modal fade", attr("data-bs-keyboard") := keyboard.toString)(modalDialog).render
dialog.onBsModalHidden({() => dialog.parentNode.removeChild(dialog)})
val bsMod = new BsModal(dialog)
(dialog, modalBodyId, bsMod)
(dialog, modalBody, bsMod)
}

@inline def fseq(frags: Frag*): Seq[Frag] = frags
Expand Down
5 changes: 0 additions & 5 deletions client/src/main/scala/com/v6ak/zbdb/PlotData.scala

This file was deleted.

9 changes: 1 addition & 8 deletions client/src/main/scala/com/v6ak/zbdb/PlotLine.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,4 @@ package com.v6ak.zbdb
import scala.scalajs.js
import scala.scalajs.js.Dictionary

final private case class PlotLine(row: Participant, label: String, points: js.Array[js.Array[_]]) {
def seriesOptions = js.Dictionary(
"label" -> label,
"highlighter" -> Dictionary(
"formatString" -> s"${row.id}: %s|%s|%s|%s"
)
)
}
final private case class PlotLine(row: Participant, label: String, points: js.Array[js.Any])
Loading