Skip to content

Commit

Permalink
速心比走势分析中添加配速 (#15)
Browse files Browse the repository at this point in the history
* Stash.

* React by select.

* Fix pace order.

* Fix GMT.
  • Loading branch information
zhongl authored Sep 6, 2024
1 parent 5525a40 commit 4e0c5e1
Show file tree
Hide file tree
Showing 12 changed files with 200 additions and 112 deletions.
2 changes: 1 addition & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ rewrite.imports.groups = [

rewrite.scala3.convertToNewSyntax = true
rewrite.scala3.removeOptionalBraces = true
rewrite.scala3.insertEndMarkerMinLines = 3
rewrite.scala3.insertEndMarkerMinLines = 10

fileOverride {
"glob:**/*.sc" {
Expand Down
23 changes: 0 additions & 23 deletions src/core/DateFormat.scala

This file was deleted.

20 changes: 20 additions & 0 deletions src/core/DateTimeGMT.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package core

import scala.scalajs.js

trait DateTimeGMT[A] extends (A => js.Date):
private trait Ext extends js.Object:
def toLocaleDateString(locale: String, option: js.Any): String

extension (a: A)
inline def ymd(locale: String) =
this(a).asInstanceOf[Ext].toLocaleDateString(locale, js.undefined)

end extension
end DateTimeGMT

object DateTimeGMT:
given string: DateTimeGMT[String] = s =>
val d = new js.Date(s)
new js.Date(d.getTime() - (d.getTimezoneOffset() * 60000))
end DateTimeGMT
2 changes: 1 addition & 1 deletion src/core/metrics/Interval.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ end Interval
object Interval:
given perf(using Speed[Interval]): Performance[Interval] = i => i.speed * 60 / i.averageHR
given speed: Speed[Interval] = i => i.distance / i.duration
given pace: Pace[Interval] = i => i.duration / i.distance * 1000
given pace: Pace[Interval] = i => i.duration / i.distance
end Interval
47 changes: 47 additions & 0 deletions src/plotly/Correlate.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package plotly

import scala.scalajs.js
import scala.scalajs.js.JSConverters.*

import typings.plotlyJs.anon.PartialLayout
import typings.plotlyJs.anon.PartialLegendBgcolor
import typings.plotlyJs.mod.Data
import typings.plotlyJs.plotlyJsBooleans.`false`

trait Correlate[A]:
def data(i: Int): DataArrayFrom[A]
def layout(i: Int): Layout[A]
object Correlate:
def apply[A](using s: Correlate[A]) = s

given [A, B <: Data](
using SharedAxis[A, B],
Trace["mpb", A, B],
Trace["bpm", A, B],
Trace["spm", A, B],
Trace["/km", A, B],
Layout[A],
ColorPalette[Common]
): Correlate[A] with
private val secondaries = List(
Trace["bpm", A, B],
Trace["spm", A, B],
Trace["/km", A, B]
)
def layout(i: Int): Layout[A] = a =>
a.layout
.setShowlegend(true)
.setLegend(PartialLegendBgcolor().setX(1.1).setY(0.5).setItemclick(`false`).setItemdoubleclick(`false`))
.setXaxis(SharedAxis[A, B].axis)
.setYaxis(Trace["mpb", A, B].y(0.color))
.setYaxis2(secondaries(i - 1).y(i.color))

def data(i: Int): DataArrayFrom[A] = a =>
val h = Trace["mpb", A, B].data(a)
val t = secondaries.zipWithIndex.map:
case (t, n) if n + 1 == i => t.data(a)
case (t, _) => t.dummy(a)
(h :: t).map(SharedAxis[A, B].share(a)).toJSArray
end given

end Correlate
32 changes: 4 additions & 28 deletions src/plotly/DataArrayFrom.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,52 +7,28 @@ import typings.plotlyJs.anon.PartialScatterLine
import typings.plotlyJs.mod.Data
import typings.plotlyJs.mod.PlotType
import typings.plotlyJs.plotlyJsBooleans.`false`
import typings.plotlyJs.plotlyJsStrings.legendonly
import typings.plotlyJs.plotlyJsStrings.y
import typings.plotlyJs.plotlyJsStrings.yPlussignname

import core.DateFormat
import core.DateTimeGMT
import core.metrics.*

trait DataArrayFrom[A] extends (A => js.Array[Data]):
extension (a: A) inline def darr = this(a)

object DataArrayFrom:

given intervals(using Performance[Interval]): DataArrayFrom[Intervals] = v =>
val distances =
val (_, r) = v.foldLeft(0.0 -> List.empty[Meter]):
case ((a, t), i) => (i.distance + a) -> (i.distance + a :: t)
r.map(_.round).reverse

def scatterLine[A <: Double](name: String, fy: Interval => A) =
Data
.PartialPlotDataAutobinx()
.setName(name)
.setLine(PartialScatterLine().setWidth(1))
.setY(v.map(fy).toJSArray)
.setX(distances.toJSArray)
.setHoverinfo(yPlussignname)

js.Array(
scatterLine("mpb", _.mpb).setShowlegend(false),
scatterLine("bpm", _.averageHR.round).setVisible(true).setYaxis("y2"),
scatterLine("spm", _.averageRunCadence.round).setVisible(legendonly).setYaxis("y2")
// scatterLine("/km", _.pace).setVisible(legendonly).setYaxis("y2")
)

given history(using Performance[Interval], DateFormat[String]): DataArrayFrom[History] = m =>
given history(using Performance[Interval], DateTimeGMT[String]): DataArrayFrom[History] = m =>
inline def box: Intervals => Data = v =>
Data
.PartialBoxPlotData()
.setType(PlotType.box)
.setY(v.map(_.mpb).toJSArray)
.setHoverinfo(y)
.setName(s"${v.head.startTimeGMT}+08:00".ymd("fr-CA"))
.setName(s"${v.head.startTimeGMT}".ymd("fr-CA"))
.setBoxpoints(`false`)
.setLine(PartialScatterLine().setWidth(1))

m.filter(_.nonEmpty).map(box).toJSArray

extension (d: Double) inline def round = scala.scalajs.js.Math.round(d)

end DataArrayFrom
41 changes: 3 additions & 38 deletions src/plotly/Layout.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,61 +5,26 @@ import scala.scalajs.js.JSConverters.*

import typings.plotlyJs.anon.PartialLayout
import typings.plotlyJs.anon.PartialLayoutAxis
import typings.plotlyJs.anon.PartialLegendBgcolor
import typings.plotlyJs.anon.PartialMargin
import typings.plotlyJs.plotlyJsBooleans.`false`
import typings.plotlyJs.plotlyJsStrings.right
import typings.plotlyJs.plotlyJsStrings.y2

import core.metrics.*
import typings.plotlyJs.plotlyJsStrings.array

trait Layout[A] extends (A => PartialLayout):
extension (a: A) inline def layout = this(a)
end Layout
object Layout:
given common: Layout[Common] = _ =>
PartialLayout()
given common(using ColorPalette[Common]): Layout[Common] = _ =>
PartialLayout().setColorPalette
.setHeight(200)
.setMargin(PartialMargin().setPad(4).setL(50).setR(50).setT(50).setB(50))

given intervals(using Layout[Common], Title[Intervals], ColorPalette[Common]): Layout[Intervals] = is =>
inline def inside = PartialLegendBgcolor()
.setX(1.1)
.setY(0.5)
.setItemclick(`false`)
.setItemdoubleclick(`false`)
given [A: Title](using Layout[Common]): Layout[A] = a => Common.layout.setTitle(a.title)

inline def yAxis = PartialLayoutAxis()
.setColor(0.color)
.setTickformat(".2f")
.setOverlaying(y2)
.setTickmodeSync

inline def yAxis2 = PartialLayoutAxis()
.setColor(1.color)
.setSide(right)

Common.layout
.setTitle(is.title)
.setShowlegend(true)
.setColorPalette
.setLegend(inside)
.setXaxis(PartialLayoutAxis().setTickformat("~s").setTicksuffix("m").setTickmode(array))
.setYaxis(yAxis)
.setYaxis2(yAxis2)
end intervals

given history(using Layout[Common], Title[History], ColorPalette[Common]): Layout[History] = h =>
Common.layout
.setTitle(h.title)
.setShowlegend(false)
.setColorPalette
.setYaxis(PartialLayoutAxis().setTickformat(".2f").setColor(0.color))

extension (a: PartialLayoutAxis)
private inline def setTickmodeSync: PartialLayoutAxis =
a.asInstanceOf[js.Dynamic].tickmode = "sync"
a

end Layout
19 changes: 6 additions & 13 deletions src/plotly/Listen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,19 @@ package plotly

import scala.language.implicitConversions
import scala.scalajs.js
import scala.scalajs.js.JSConverters.*

import org.scalajs.dom.HTMLElement

import typings.plotlyJs.anon.PartialLayout
import typings.plotlyJs.anon.PartialLayoutAxis
import typings.plotlyJs.anon.PartialPlotDataAutobinx
import typings.plotlyJs.anon.PartialScatterLine
import typings.plotlyJs.mod.LegendClickEvent
import typings.plotlyJs.mod.PlotlyHTMLElement
import typings.plotlyJs.mod.PlotMouseEvent
import typings.plotlyJs.plotlyJsStrings.legendonly
import typings.plotlyJs.plotlyJsStrings.plotly_hover
import typings.plotlyJs.plotlyJsStrings.plotly_legendclick
import typings.plotlyJs.plotlyJsStrings.plotly_unhover
import typings.plotlyJs.plotlyJsStrings.right
import typings.plotlyJsDistMin.mod.relayout
import typings.plotlyJsDistMin.mod.restyle
import typings.plotlyJsDistMin.mod.react

import core.metrics.*

Expand All @@ -31,18 +26,16 @@ object Listen:
given tuple[A, H, T <: Tuple](using h: Listen[A, H], t: Listen[A, T]): Listen[A, H *: T] = (a, p) =>
h(a, p); t(a, p)

given legendclick(using ColorPalette[Common]): Listen[Intervals, plotly_legendclick] = (_, p) =>
given legendclick(using ColorPalette[Common], Correlate[Intervals]): Listen[Intervals, plotly_legendclick] = (i, p) =>
p.on(
plotly_legendclick,
accept[LegendClickEvent]: e =>
e.curveNumber.toInt match
case 0 =>
case i =>
val (l, r) = Range(1, e.data.length).partition(_ == i)
restyle(p, PartialPlotDataAutobinx().setVisible(true), l.map(_.doubleValue).toJSArray)
restyle(p, PartialPlotDataAutobinx().setVisible(legendonly), r.map(_.doubleValue).toJSArray)
val yAxis2 = PartialLayoutAxis().setColor(i.color).setSide(right)
relayout(p, PartialLayout().setYaxis2(yAxis2))
case n =>
given DataArrayFrom[Intervals] = Correlate[Intervals].data(n)
given Layout[Intervals] = Correlate[Intervals].layout(n)
react(p, i.darr, i.layout)
end match
)
end legendclick
Expand Down
26 changes: 26 additions & 0 deletions src/plotly/SharedAxis.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package plotly

import scala.scalajs.js
import scala.scalajs.js.JSConverters.*

import typings.plotlyJs.anon.PartialLayoutAxis
import typings.plotlyJs.anon.PartialPlotDataAutobinx
import typings.plotlyJs.mod.Data
import typings.plotlyJs.plotlyJsStrings.array

import core.metrics.*

trait SharedAxis[A, B <: Data]:
def axis: PartialLayoutAxis
def share(a: A): B => B

object SharedAxis:
def apply[A, B <: Data](using s: SharedAxis[A, B]) = s

given SharedAxis[Intervals, PartialPlotDataAutobinx] with
def axis: PartialLayoutAxis = PartialLayoutAxis().setTickformat("~s").setTicksuffix("m").setTickmode(array)
def share(a: Intervals): PartialPlotDataAutobinx => PartialPlotDataAutobinx =
val (_, r) = a.foldLeft(0.0 -> List.empty[Meter]):
case ((s, t), i) => (i.distance + s) -> (i.distance + s :: t)
r.map(_.round).reverse
_.setX(r.toJSArray)
7 changes: 4 additions & 3 deletions src/plotly/Title.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package plotly

import core.DateFormat
import core.DateTimeGMT
import core.metrics.*

trait Title[A] extends (A => String):
extension (a: A) inline def title = this(a)
object Title:
given date(using DateFormat[String]): Title[Intervals] = oi => s"${oi.head.startTimeGMT.ymd("zh-CN")} 速心比走势"
given Title[History] = _ => "速心比分布走势"
given date(using DateTimeGMT[String]): Title[Intervals] = oi =>
s"${(oi.head.startTimeGMT).ymd("fr-CA")} 速心比走势"
given Title[History] = _ => "速心比分布走势"
end Title
Loading

0 comments on commit 4e0c5e1

Please sign in to comment.