Skip to content

Commit

Permalink
Improved timeline (see #39)
Browse files Browse the repository at this point in the history
  • Loading branch information
v6ak committed Sep 25, 2023
1 parent ae17584 commit 61f49b0
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 48 deletions.
1 change: 1 addition & 0 deletions client/src/main/scala/com/v6ak/zbdb/Bootstrap.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ import scalatags.JsDom.all._
object Bootstrap {
val toggle = data.toggle
val dismiss = data.dismiss
def glyphicon(name: String) = span(`class`:=s"glyphicon glyphicon-${name}", aria.hidden := "true")
}
2 changes: 2 additions & 0 deletions client/src/main/scala/com/v6ak/zbdb/HtmlUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,6 @@ object HtmlUtils {
(dialog, jqModal, modalBodyId)
}

@inline def fseq(frags: Frag*): Seq[Frag] = frags

}
131 changes: 93 additions & 38 deletions client/src/main/scala/com/v6ak/zbdb/TimeLineRenderer.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.v6ak.zbdb

import com.example.RichMoment.toRichMoment
import com.example.moment.Moment
import com.example.moment.{Moment, moment}
import com.v6ak.scalajs.time.TimeInterval
import HtmlUtils._
import TextUtils._
import Bootstrap._
import org.scalajs.dom
import scalatags.JsDom.all.{i => iTag, name => _, _}
import scala.scalajs.js
Expand All @@ -20,26 +22,48 @@ final class TimeLineRenderer(participantTable: ParticipantTable, plotRenderer: P
import participantTable._
import plotRenderer.{Plots, initializePlot}

private def timePoint(time: Moment, content: Frag) = tr(
`class` := "timeline-point",
td(`class` := "timeline-point-time", time.hoursAndMinutes),
private def timePoint(time: Moment, content: Frag, className: String = "") = tr(
`class` := s"timeline-point $className",
td(`class` := "timeline-time", time.hoursAndMinutes),
td(),
td(`class` := "timeline-content", content),
)

private def process(content: Frag, className: String = "") = tr(
private def process(content: Frag, duration: TimeInterval, durationIcon: String, className: String = "") = tr(
`class` := s"timeline-process $className",
td(`class` := "timeline-process-time"),
td(`class` := "timeline-time"),
td(`class` := "timeline-duration")(
fseq(
glyphicon(durationIcon),
" ",
duration.toString
)
),
td(`class` := "timeline-content", content),
)

private def end(content: Frag, className: String = "") = tr(
`class` := s"timeline-end $className",
td(`class` := "timeline-time"),
td(`class` := "timeline-duration"),
td(`class` := "timeline-content", content),
)

private def gaveUp(content: Frag) = process(content, className = "gave-up")
private def pause(content: Frag) = process(content, className = "pause")
private def finish(content: Frag) = process(content, className = "finish")
private def pause(content: Frag, duration: TimeInterval) =
process(content, duration = duration, durationIcon = "pause", className = "pause")
private def walk(content: Frag, duration: TimeInterval) =
process(content, duration = duration, durationIcon = "play", className = "walk")
private def gaveUp(content: Frag) = end(content, className = "gave-up")
private def finish(time: Moment, content: Frag) = timePoint(time, content, className = "finish")

def timeLine(row: Participant) = {
val prevParts = Seq(None) ++ parts.map(Some(_))
val nextPartInfos: Seq[Option[PartTimeInfo]] = row.partTimes.drop(1).map(Some(_)) ++ Seq(None)
div(
div(`class` := "legend")(
h2("Legenda"),
legendTable,
),
table(
`class` := "timeline",
(
Expand All @@ -51,52 +75,83 @@ final class TimeLineRenderer(participantTable: ParticipantTable, plotRenderer: P
partTimeLine(row)(pos, fpi)
}
),
h2("Vizualizace"),
chartButtons(row),
)
}

private def legendTable = {
table(
`class` := "timeline",
walk("chůze trvající 4:20", TimeInterval(260)),
timePoint(moment("2016-01-20 15:15"), "příchod/odchod v 15:15"),
pause("pauza trvající 10 minut", TimeInterval(10)),
gaveUp("konec před dosažením cíle"),
finish(moment("2016-01-20 16:20"), "Dosažení cíle v 16:20"),
)
}

val brief = true

def verbose(f: Frag) = if(brief) "" else f
def verbose(s: String) = if(brief) "" else s

private def partTimeLine(row: Participant)(
pos: Int,
fullPartInfo: FullPartInfo
): Seq[Frag] = {
import fullPartInfo._
import row.gender
def langGaveUp = gender.inflect("vzdala", "vzdal")
def langArrived = gender.inflect("dorazila", "dorazil")
def cumLen: Frag = fseq(" (celkem ", strong(formatLength(partMeta.cumulativeTrackLength)), ")")
Seq(
timePoint(
part.startTime,
s"${gender.inflect("vyšla", "vyšel")} ${
previousPartMetaOption.fold("ze startu")(pm =>
previousPartMetaOption.fold("Start")(pm =>
verbose(s"${gender.inflect("vyšla", "vyšel")} ${
s"z $pos. stanoviště ${pm.place}"
)
}"
}")
),
className = if (previousPartMetaOption.isEmpty) "start" else ""
),
) ++ (part match {
case PartTimeInfo.Finished(_startTime, endTime, intervalTime) => Seq(
process(Seq[Frag](
strong(s"${partMeta.trackLength}km"),
s" cesta ${gender.inflect("", "mu")} trvala $intervalTime",
br(),
"Průměrná rychlost ",
strong(f"${partMeta.trackLength * 60 / intervalTime.totalMinutes}%1.2f km/h"),
" = tempo ",
strong(f"${intervalTime / partMeta.trackLength} / km"),
)),
timePoint(
endTime,
gender.inflect("dorazila", "dorazil") + s" do ${pos + 1}. stanoviště ${partMeta.place}"
),
nextPartOption.fold(
if (pos == parts.size-1) finish("cíl")
else gaveUp(gender.inflect("vzdala", "vzdal") + s" to na $pos. stanovišti")
) { nextPart =>
pause(Seq[Frag](
"čekání na stanovišti: ",
strong(s"${TimeInterval((nextPart.startTime - endTime)/60/1000)}")
))
}
)
case PartTimeInfo.Finished(_startTime, endTime, intervalTime) =>
val isFinish = pos == parts.size - 1
fseq(
walk(
fseq(
strong(formatLength(partMeta.trackLength)), br(),
"rychlost ",
strong(f"${partMeta.trackLength * 60 / intervalTime.totalMinutes}%1.2f km/h"),
" = tempo ",
strong(f"${intervalTime / partMeta.trackLength} / km"),
),
duration = intervalTime,
),
if(isFinish)
finish(
endTime,
fseq(s"Cíl: ${partMeta.place}", cumLen)
// TODO: stats
)
else
timePoint(
endTime,
verbose(s"$langArrived na ${pos + 1}. stanoviště ${partMeta.place}")
),
nextPartOption.fold[Frag](
if (isFinish) ""
else gaveUp(fseq(s"$langGaveUp to na stanovišti ", strong(s"${pos + 1}."), s" ${partMeta.place}", cumLen))
) { nextPart =>
pause(
fseq(strong(s"${pos+1}."), s" ${partMeta.place}", cumLen),
duration = TimeInterval((nextPart.startTime - endTime)/60/1000)
)
}
)
case PartTimeInfo.Unfinished(_startTime) => Seq(
gaveUp(s"${gender.inflect("Vzdala", "Vzdal")} to při cestě na další stanoviště.")
gaveUp(s"$langGaveUp to při cestě na ${pos+1}. stanoviště.")
)
})
}
Expand Down
37 changes: 27 additions & 10 deletions server/app/assets/main.less
Original file line number Diff line number Diff line change
Expand Up @@ -159,37 +159,54 @@ body{
padding-left: 5px;
}

.legend {
float: right;
border: 2px solid black;
padding: 10px;
}

.timeline {
margin-bottom: 20px;
&, td {
border-width: 0;
}
.timeline-duration {
padding-left: 5px
}
.timeline-point {
.timeline-point-time {
.timeline-time {
text-align: right;
padding-left: 10px;
padding-right: 10px;
border-right: 5px solid blue;
}
}
.timeline-process {
.timeline-process-time {
.timeline-time {
border-right: 5px dotted blue;
}
}
.timeline-process.pause {
.timeline-process-time {
border-right: 5px double blue;
.timeline-time {
border-right: 5px dotted gray;
}
}
.timeline-process.gave-up {
.timeline-process-time {
border-right: 5px double red;
.timeline-end.gave-up {
.timeline-time {
border-right: 5px solid red;
}
}
.timeline-point.finish {
.timeline-time, .timeline-content {
padding-top: 20px;
}
.timeline-time {
border-right: 5px solid green;
}
}
.timeline-process.finish {
.timeline-process-time {
border-right: 5px double green;
.timeline-point.start {
.timeline-time, .timeline-content {
padding-bottom: 20px;
}
}
.timeline-content {
Expand Down

0 comments on commit 61f49b0

Please sign in to comment.