From 8679ce077e98a1d00c1e6e762471facc19fb9f5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADt=20=C5=A0est=C3=A1k?= Date: Sat, 14 Oct 2023 20:32:26 +0200 Subject: [PATCH] Rewrite all individual charts from jqPlot to Chart.js and get rid of jqPlot and jQuery (#42) --- build.sbt | 12 - .../scala/com/v6ak/zbdb/ChartJsUtils.scala | 86 +++++-- .../main/scala/com/v6ak/zbdb/HtmlUtils.scala | 5 +- .../main/scala/com/v6ak/zbdb/PlotData.scala | 5 - .../main/scala/com/v6ak/zbdb/PlotLine.scala | 9 +- .../scala/com/v6ak/zbdb/PlotRenderer.scala | 210 +++++++++--------- .../main/scala/com/v6ak/zbdb/Renderer.scala | 25 ++- .../com/v6ak/zbdb/TimeLineRenderer.scala | 19 +- .../main/scala/com/v6ak/zbdb/package.scala | 7 + server/app/assets/main.scss | 5 - 10 files changed, 203 insertions(+), 180 deletions(-) delete mode 100644 client/src/main/scala/com/v6ak/zbdb/PlotData.scala create mode 100644 client/src/main/scala/com/v6ak/zbdb/package.scala diff --git a/build.sbt b/build.sbt index edad657..97b95c5 100644 --- a/build.sbt +++ b/build.sbt @@ -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 @@ -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) @@ -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", "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", - 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.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", ), ).enablePlugins(ScalaJSPlugin, JSDependenciesPlugin, ScalaJSWeb)//.dependsOn(sharedJs) diff --git a/client/src/main/scala/com/v6ak/zbdb/ChartJsUtils.scala b/client/src/main/scala/com/v6ak/zbdb/ChartJsUtils.scala index b842753..ac1cb73 100644 --- a/client/src/main/scala/com/v6ak/zbdb/ChartJsUtils.scala +++ b/client/src/main/scala/com/v6ak/zbdb/ChartJsUtils.scala @@ -54,6 +54,35 @@ object ChartJsUtils { 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, @@ -74,32 +103,47 @@ object ChartJsUtils { ) ) - def showChartInModal(title: String = null)(f: Seq[Participant] => js.Any): (Option[String], (String, => Seq[Participant], ParticipantTable) => Unit) = - Option(title) -> ((modalBodyId: String, rowsLoader, participantTable: Any) => { - val el = window.document.getElementById(modalBodyId).asInstanceOf[HTMLElement] - el.style.maxHeight = "90vh" - el.style.maxWidth = "90vw" + def initializePlot(el: HTMLElement, plotParams: js.Any, registerDestroy: (()=>Unit) => Unit): Unit = { + console.log("plotParams", plotParams) + el.style.maxHeight = "90vh" + el.style.maxWidth = "90vw" - val can = canvas().render - el.appendChild(can) + val can = canvas().render + el.appendChild(can) - val plotParams = f(rowsLoader) - console.log("plotParams", plotParams) - val chart = new Chart(can, plotParams) - val resizeHandler: Event => Unit = _ => { - //chart.update() - chart.resize() - } - window.addEventListener("resize", resizeHandler) - - def destroy(): Unit = { - window.removeEventListener("resize", resizeHandler) - chart.destroy() - } + val chart = new Chart(can, plotParams) + val resizeHandler: Event => Unit = _ => { + //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] - modalElement.onBsModalHidden(() => destroy()) + 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, + ) + } + } diff --git a/client/src/main/scala/com/v6ak/zbdb/HtmlUtils.scala b/client/src/main/scala/com/v6ak/zbdb/HtmlUtils.scala index bc2d585..33efbee 100644 --- a/client/src/main/scala/com/v6ak/zbdb/HtmlUtils.scala +++ b/client/src/main/scala/com/v6ak/zbdb/HtmlUtils.scala @@ -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")( @@ -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 diff --git a/client/src/main/scala/com/v6ak/zbdb/PlotData.scala b/client/src/main/scala/com/v6ak/zbdb/PlotData.scala deleted file mode 100644 index 82816b9..0000000 --- a/client/src/main/scala/com/v6ak/zbdb/PlotData.scala +++ /dev/null @@ -1,5 +0,0 @@ -package com.v6ak.zbdb - -import scala.scalajs.js - -final case class PlotData(plotParams: js.Dictionary[js.Any], plotPoints: js.Any) diff --git a/client/src/main/scala/com/v6ak/zbdb/PlotLine.scala b/client/src/main/scala/com/v6ak/zbdb/PlotLine.scala index 3a9cc34..2742389 100644 --- a/client/src/main/scala/com/v6ak/zbdb/PlotLine.scala +++ b/client/src/main/scala/com/v6ak/zbdb/PlotLine.scala @@ -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]) diff --git a/client/src/main/scala/com/v6ak/zbdb/PlotRenderer.scala b/client/src/main/scala/com/v6ak/zbdb/PlotRenderer.scala index eecc9ae..6cbb016 100644 --- a/client/src/main/scala/com/v6ak/zbdb/PlotRenderer.scala +++ b/client/src/main/scala/com/v6ak/zbdb/PlotRenderer.scala @@ -1,7 +1,6 @@ package com.v6ak.zbdb import com.example.moment.{moment, _} -import com.v6ak.zbdb.`$`.jqplot.DateAxisRenderer import org.scalajs.dom import org.scalajs.dom._ @@ -13,31 +12,22 @@ import scala.scalajs.js.Dynamic.literal import scala.scalajs.js.annotation._ import Bootstrap.DialogUtils import com.example.moment._ -import com.v6ak.zbdb.TextUtils.formatLength +import com.example.RichMoment._ +import com.v6ak.zbdb.TextUtils.{formatLength, formatSpeed} -@JSGlobal @js.native object `$` extends js.Object{ - @js.native object jqplot extends js.Object{ - @js.native class LinearAxisRenderer extends js.Object{ - def createTicks(plot: js.Dynamic): Unit = js.native - } - @js.native class DateAxisRenderer extends LinearAxisRenderer{ - override def createTicks(plot: js.Dynamic): Unit = js.native - } - } -} final class PlotRenderer(participantTable: ParticipantTable) { import ChartJsUtils._ import participantTable._ - val Plots = Seq( + val IndividualPlots = Seq( ParticipantPlotGenerator("chůze", "chůzi", Glyphs.Pedestrian, generateWalkPlotData), ParticipantPlotGenerator("rychlosti", "rychlost", Glyphs.Play, generateSpeedPlotData), ParticipantPlotGenerator("pauz", "pauzy", Glyphs.Pause, generatePausesPlotData) ) - val GlobalPlots = Seq[(String, (Option[String], ((String, =>Seq[Participant], ParticipantTable) => Unit)))]( + val GlobalPlots = Seq[(String, (Option[String], ((HTMLElement, =>Seq[Participant], ParticipantTable) => Unit)))]( "Porovnání startu a času" -> startTimeToTotalDurationPlot, "Genderová struktura" -> genderStructurePlot, "Počet lidí" -> remainingParticipantsCountPlot, @@ -54,52 +44,6 @@ final class PlotRenderer(participantTable: ParticipantTable) { Gender.Male -> "#008080", ) - private val BarRenderer = dom.window.asInstanceOf[js.Dynamic].$.jqplot.BarRenderer - - private val DateAxisRenderer = dom.window.asInstanceOf[js.Dynamic].$.jqplot.DateAxisRenderer - - def generateWalkPlotData(rowsLoader: Seq[Participant]) = { - val data = rowsLoader.map(processTimes) - val series = js.Array(data.map(line => - line.seriesOptions - ): _*) - val plotPoints = js.Array(data.map(_.points): _*) - PlotData( - plotPoints = plotPoints, - plotParams = js.Dictionary( - "title" -> "Chůze účastníka", - "seriesDefaults" -> js.Dictionary( - "linePattern" -> "dashed", - "showMarker" -> true, - "markerOptions" -> Dictionary("style" -> "diamond"), - "shadow" -> false - ), - "series" -> series, - "highlighter" -> js.Dictionary( - "show" -> true, - "showTooltip" -> true, - "formatString" -> "%s|%s|%s|%s", - "bringSeriesToFront" -> true - ), - "height" -> 500, - "legend" -> Dictionary("show" -> true), - "axes" -> Dictionary( - "xaxis" -> Dictionary( - "renderer" -> DateAxisRenderer, - "tickOptions" -> Dictionary("formatString" -> "%#H:%M"), - "min" -> moment(startTime).minutes(0).toString, - "tickInterval" -> "1 hours" - ), - "yaxis" -> Dictionary( - "min" -> 0, - "max" -> parts.last.cumulativeTrackLength.toDouble, - "tickInterval" -> 10 - ) - ) - ) - ) - } - private val startTimeToTotalDurationRadius = (context: js.Dynamic) => { // It doesn't seem to be recalculated when resized, even though this function is called val baseSize = math.max( @@ -233,81 +177,129 @@ final class PlotRenderer(participantTable: ParticipantTable) { ) } - def genderStructurePlot = showChartInModal() { rows => + def genderStructurePlot = showChartInModal( + title = "Genderová struktura startujících", + ) { rows => val structure = rows.groupBy(_.gender) js.Dynamic.literal( `type` = "pie", data = dataFromTriples(structure.toSeq.map { case (gender, p) => (GenderNames(gender), p.size, GenderColors(gender)) }), - title = "Genderová struktura startujících", + ) + } + + def expectOneStr(contexts: js.Array[js.Dynamic])(f: js.Dynamic => js.Any): String = contexts.map(f).toSet.mkString(" | ") + def expectOne[F, T](contexts: js.Array[F])(f: F => T): T = contexts.map(f).toSet.toSeq match { + case Seq(x) => x + case other => sys.error(s"Expected exactly one value, got: $other") + } + + private def generateWalkPlotData(rowsLoader: Seq[Participant]) = { + val data = rowsLoader.map(processTimes) + literal( + `type` = "line", + data = plotLinesToData(data), + options = literal( + scales = literal( + x = timeAxis("čas"), + y = distanceAxis, + ), + plugins = literal( + tooltip = literal( + callbacks = literal( + label = (context: js.Dynamic) => context.dataset.label, + title = (context: js.Array[js.Dynamic]) => { + val len = expectOneStr(context)(c => formatLength(c.raw.y.asInstanceOf[BigDecimal])) + val t = expectOneStr(context)(_.raw.x.asInstanceOf[Moment].hoursAndMinutes) + s"$len v $t" + }, + ), + ), + ), + ), ) } private def generateSpeedPlotData(rows: Seq[Participant]) = { val data = rows.map{p => - PlotLine(row = p, label = p.fullName, points = js.Array( + PlotLine(row = p, label = p.fullName, points = (p.partTimes lazyZip parts).flatMap((partTime, part) => partTime.durationOption.map { duration => - js.Array(part.cumulativeTrackLength.toDouble, part.trackLength.toDouble / (duration.toDouble / 1000 / 3600)) - }): _*) + literal( + x = part.cumulativeTrackLength.toDouble, + y = part.trackLength.toDouble / (duration.toDouble / 1000 / 3600), + ): js.Any + }).toJSArray ) } - val series = js.Array(data.map(_.seriesOptions): _*) - val plotPoints = js.Array(data.map(_.points): _*) - dom.console.log("plotPoints", plotPoints) - PlotData( - plotPoints = plotPoints, - plotParams = js.Dictionary( - "title" -> "Rychlost účastníka", - "seriesDefaults" -> js.Dictionary( - "renderer" -> BarRenderer, - "rendererOptions" -> js.Dictionary( - "barWidth" -> 10 + literal( + `type` = "bar", + data = plotLinesToData(data), + options = literal( + scales = literal( + x = distanceAxis, + y = speedAxis, + ), + plugins = literal( + tooltip = literal( + callbacks = literal( + label = (context: js.Dynamic) => + s"${formatSpeed(context.raw.y.asInstanceOf[Double])} (${context.dataset.label})", + title = (context: js.Array[js.Dynamic]) => { + val i = expectOne(context)(_.dataIndex.asInstanceOf[Int]) + val from = i match { + case 0 => "Ze startu" + case i => s"Z $i. stanoviště (${participantTable.parts(i-1).place})" + } + val finishCheckpoint = participantTable.parts.size + val to = i+1 match { + case next if next == finishCheckpoint => "do cíle" + case next => s"na $next. stanoviště (${participantTable.parts(next-1).place})" + } + s"$from $to" + }, + ), ), - "pointLabels" -> true, - "showMarker" -> true ), - "series" -> series, - "height" -> 500, - "legend" -> Dictionary("show" -> true) - ) + ), ) } private def generatePausesPlotData(rows: Seq[Participant]) = { val data = rows.map{p => - PlotLine(row = p, label = p.fullName, points = js.Array((p.pauseTimes, parts).zipped.map((pause, part) => js.Array(part.cumulativeTrackLength.toDouble, pause/1000/60)): _*)) + PlotLine( + row = p, + label = p.fullName, + points = (p.pauseTimes, parts).zipped.map((pause, part) => + literal(x=part.cumulativeTrackLength.toDouble, y=pause/1000/60): js.Any + ).toJSArray + ) } - dom.console.log("rows.head.pauses = " + rows.head.pauses.toIndexedSeq.toString) - val series = js.Array(data.map(_.seriesOptions): _*) - val plotPoints = js.Array(data.map(_.points): _*) - dom.console.log("plotPoints", plotPoints) - PlotData( - plotPoints = plotPoints, - plotParams = js.Dictionary( - "title" -> "Pauzy účastníka", - "seriesDefaults" -> js.Dictionary( - "renderer" -> BarRenderer, - "rendererOptions" -> js.Dictionary( - "barWidth" -> 10 - ), - "pointLabels" -> true, - "showMarker" -> true + literal( + `type` = "bar", + data = plotLinesToData(data), + options = literal( + scales = literal( + x = distanceAxis, + y = minutesAxis("trvání pauzy (min)"), ), - "series" -> series, - "height" -> 500, - "legend" -> Dictionary("show" -> true) + plugins = literal( + tooltip = literal( + callbacks = literal( + label = (context: js.Dynamic) => + s"${context.raw.y.asInstanceOf[Int]} min (${context.dataset.label})", + title = (context: js.Array[js.Dynamic]) => { + console.log("title context", context) + val i = expectOne(context)(_.dataIndex.asInstanceOf[Int]) + s"${i+1}. stanoviště (${participantTable.parts(i).place})" + }, + ), + ), + ) ) ) } - def initializePlot(modalBodyId: String, data: PlotData): Unit ={ - dom.window.asInstanceOf[js.Dynamic].$.jqplot(modalBodyId, data.plotPoints, data.plotParams) - dom.window.asInstanceOf[js.Dynamic].$("#"+modalBodyId).on("jqplotDataClick", {(ev: js.Any, seriesIndex: js.Any, pointIndex: js.Any, data: js.Any) => - dom.console.log("click", ev, seriesIndex, pointIndex, data) - }) - } - private def processTimes(participant: Participant): PlotLine = { val data: Seq[(Moment, BigDecimal)] = (participant.partTimes lazyZip parts lazyZip previousPartCummulativeLengths) .flatMap{(ti, pi, prev) => @@ -319,7 +311,7 @@ final class PlotRenderer(participantTable: ParticipantTable) { PlotLine( row = participant, label = s"${participant.id}: ${participant.fullName}", - points = js.Array(data.map{case (x, y) => js.Array(x.toString, y.toDouble)}: _*) + points = data.map[js.Any]{case (x, y) => literal(x=x, y=y.asInstanceOf[js.Any])}.toJSArray ) } diff --git a/client/src/main/scala/com/v6ak/zbdb/Renderer.scala b/client/src/main/scala/com/v6ak/zbdb/Renderer.scala index bc67106..4b9dede 100644 --- a/client/src/main/scala/com/v6ak/zbdb/Renderer.scala +++ b/client/src/main/scala/com/v6ak/zbdb/Renderer.scala @@ -40,6 +40,7 @@ final class Renderer private(participantTable: ParticipantTable, processingError private val timeLineRenderer = new TimeLineRenderer(participantTable, plotRenderer) private val switches = new ClassSwitches(Map("details" -> "with-details")) + import ChartJsUtils._ import Renderer._ import Bootstrap._ import participantTable._ @@ -239,7 +240,13 @@ final class Renderer private(participantTable: ParticipantTable, processingError private val barElement = div(id := "button-bar")( button(`class` := "btn-close")(onclick := {(e: Event) => selection.clear() }), dropdownGroup("Porovnat vybrané účastníky…", cls:="btn btn-primary dropdown-toggle")( - for(plot <- Plots) yield chartButton(s"Porovnat ${plot.nameAccusative}", selection().toSeq, plot.generator, s"Porovnat ${plot.nameAccusative}") + for(plot <- IndividualPlots) yield + chartButton( + s"Porovnat ${plot.nameAccusative}", + selection().toSeq, + plot.generator, + s"Porovnat ${plot.nameAccusative}" + ) )(cls := "btn-group dropup"), selectedParticipantsElement ).render @@ -247,8 +254,8 @@ final class Renderer private(participantTable: ParticipantTable, processingError private val globalStats = div(id := "global-stats")( GlobalPlots.map { case (plotName, (detailedTitleOption, plotFunction)) => button(plotName)(cls := "btn btn-secondary d-print-none")(onclick := {(e: Event) => - val (dialog, modalBodyId, bsMod) = modal(detailedTitleOption.getOrElse(plotName): String) - dialog.onBsModalShown{ () => plotFunction(modalBodyId, data, participantTable) } + val (dialog, modalBody, bsMod) = modal(detailedTitleOption.getOrElse(plotName): String) + dialog.onBsModalShown{ () => plotFunction(modalBody, data, participantTable) } dom.document.body.appendChild(dialog) bsMod.show() }) @@ -266,8 +273,10 @@ final class Renderer private(participantTable: ParticipantTable, processingError private def chartButton(title: String, rowsLoader: => Seq[Participant], plotDataGenerator: Seq[Participant] => PlotData, description: Frag) = btnDefault(`class` := "btn-sm")(description)(onclick := {(_:Any) => - val (dialog, modalBodyId, bsMod) = modal(title) - dialog.onBsModalShown({() => initializePlot(modalBodyId, plotDataGenerator(rowsLoader))}) + val (dialog, modalBody, bsMod) = modal(title) + dialog.onBsModalShown({() => + initializePlot(modalBody, plotDataGenerator(rowsLoader), cb => dialog.onBsModalHidden(cb)) + }) dom.document.body.appendChild(dialog) bsMod.show() }) @@ -280,13 +289,13 @@ final class Renderer private(participantTable: ParticipantTable, processingError btnPrimary( Glyphs.Timeline.toHtml, onclick := { (_: Any) => - val (dialog, modalBodyId, bsMod) = modal(s"Časová osa pro #${row.id}: ${row.fullNameWithNick}") + val (dialog, modalBody, bsMod) = modal(s"Časová osa pro #${row.id}: ${row.fullNameWithNick}") dom.document.body.appendChild(dialog) val (timeLine, renderPlots) = timeLineRenderer.timeLine(row) - dom.document.getElementById(modalBodyId).appendChild(timeLine) + modalBody.appendChild(timeLine) timeLineRenderer.bodyClasses.foreach(dom.document.body.classList.add) bsMod.show() - dialog.onBsModalShown(renderPlots) + dialog.onBsModalShown(() => renderPlots(dialog)) } ) diff --git a/client/src/main/scala/com/v6ak/zbdb/TimeLineRenderer.scala b/client/src/main/scala/com/v6ak/zbdb/TimeLineRenderer.scala index 174d873..6c9962a 100644 --- a/client/src/main/scala/com/v6ak/zbdb/TimeLineRenderer.scala +++ b/client/src/main/scala/com/v6ak/zbdb/TimeLineRenderer.scala @@ -16,12 +16,12 @@ import org.scalajs.dom.html.TableRow import scala.annotation.tailrec import scala.scalajs.js.annotation._ - +import ChartJsUtils._ final class TimeLineRenderer(participantTable: ParticipantTable, plotRenderer: PlotRenderer) { import participantTable._ - import plotRenderer.{Plots, initializePlot} + import plotRenderer.IndividualPlots private val switches = new ClassSwitches(Map( "speed" -> "with-speed", @@ -163,7 +163,7 @@ final class TimeLineRenderer(participantTable: ParticipantTable, plotRenderer: P )( "with-overtaking", "without-overtaking" ), - for((plot, i) <- Plots.zipWithIndex) yield fseq( + for((plot, i) <- IndividualPlots.zipWithIndex) yield fseq( h2(s"Graf ${plot.nameGenitive}"), div(id := s"$idPrefix-$i") ), @@ -172,14 +172,15 @@ final class TimeLineRenderer(participantTable: ParticipantTable, plotRenderer: P } private def createRenderPlots(idPrefix: String) = - () => { - for ((plot, i) <- Plots.zipWithIndex) { - val data = plot.generator(Seq(row)) + (dialog: HTMLElement) => { + for ((plot, i) <- IndividualPlots.zipWithIndex) { + val data = plot.generator(Seq(row)).asInstanceOf[js.Dynamic] val id = s"$idPrefix-$i" - if (data.plotPoints.asInstanceOf[js.Array[js.Dynamic]](0).length == 0.asInstanceOf[js.Dynamic]) { - document.getElementById(id).appendChild("Žádná data".render) + val element = document.getElementById(id).asInstanceOf[HTMLElement] + if (data.data.datasets.asInstanceOf[js.Array[js.Dynamic]](0).data.length == 0.asInstanceOf[js.Dynamic]) { + element.appendChild("Žádná data".render) } else { - initializePlot(id, data) + initializePlot(element, data, cb => dialog.onBsModalHidden(cb)) } } } diff --git a/client/src/main/scala/com/v6ak/zbdb/package.scala b/client/src/main/scala/com/v6ak/zbdb/package.scala new file mode 100644 index 0000000..f759669 --- /dev/null +++ b/client/src/main/scala/com/v6ak/zbdb/package.scala @@ -0,0 +1,7 @@ +package com.v6ak + +import scala.scalajs.js + +package object zbdb { + type PlotData = js.Any +} diff --git a/server/app/assets/main.scss b/server/app/assets/main.scss index 5795d52..a388f49 100644 --- a/server/app/assets/main.scss +++ b/server/app/assets/main.scss @@ -1,9 +1,4 @@ @import "bootstrap-fragments"; // full Bootstrap: @import "lib/bootstrap/scss/bootstrap"; - -// jqPlot -@import /*inline*/ "lib/jqplot/jquery.jqplot.min"; - - @import "icons";