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

优化图例色彩关联性 #13

Merged
merged 7 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified mpb-history.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified mpb-intervals.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 5 additions & 11 deletions src/garmin/Activities.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package garmin

import java.util as ju
import java.util.NoSuchElementException as Complain

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

import org.scalajs.dom.HTMLElement
import org.scalajs.dom.document
Expand All @@ -21,14 +19,10 @@ object Activities:
Inject[History]
): Page[Activities] =
case (URL("/modern/activities", `activityType`("running")), `a.inline-edit-target`(_)) =>
val es = `a.inline-edit-target`.all(Seq(document)).toList
val fs = for case `href`(s"/modern/activity/$id") <- es yield ActivityId(id).request
if !fs.isEmpty then
for history <- Future.sequence(fs) do
inline def complain[A]: A = throw ju.NoSuchElementException("div.advanced-filtering")
val e = `div.advanced-filtering`(document).getOrElse(complain)
history.filter(_.nonEmpty).inject(`mpb`.elementAt(e.before(_)))
end if
val es = `a.inline-edit-target`.all(Seq(document)).toList
val e = `div.advanced-filtering`(document).getOrElse(throw Complain("anchor"))
val ids = for case `href`(s"/modern/activity/$id") <- es yield ActivityId(id)
ids.inject(`mpb`.elementAt(e.before(_)))
None

case (URL("/modern/activities", `activityType`("running")), _) =>
Expand Down
6 changes: 6 additions & 0 deletions src/garmin/ActivityId.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package garmin

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.scalajs.js

import org.scalajs.dom.Request

import core.*
import core.metrics.*
import core.service.Fetch

Expand All @@ -13,6 +15,9 @@ opaque type ActivityId = String
object ActivityId:
def apply(s: String): ActivityId = s

given inject(using Fetch[ActivityId, Intervals], Inject[History]): Inject[List[ActivityId]] = (e, ids) =>
for history <- Future.sequence(ids.map(_.request)) if history.nonEmpty do history.filter(_.nonEmpty).inject(e)

given fetch(using Fetch[Request, js.Dynamic], Conversion[Get, Request]): Fetch[ActivityId, Intervals] = id =>
inline def url = s"https://connect.garmin.cn/activity-service/activity/$id/splits"
inline def referrer = s"https://connect.garmin.cn/modern/activity/$id"
Expand All @@ -25,6 +30,7 @@ object ActivityId:
laps = d.asInstanceOf[Splits].lapDTOs.toList
yield for l <- laps if isActive(l.intensityType)
yield l.asInstanceOf[Interval]
end for
end fetch

private trait Lap extends js.Object:
Expand Down
22 changes: 6 additions & 16 deletions src/garmin/Profile.scala
Original file line number Diff line number Diff line change
@@ -1,35 +1,25 @@
package garmin

import java.util as ju

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import java.util.NoSuchElementException as Complain

import org.scalajs.dom.Element
import org.scalajs.dom.HTMLElement
import org.scalajs.dom.document

import core.*
import core.metrics.*
import core.service.Fetch
import sourcecode.Name

sealed trait Profile
object Profile:
given page(
using Initialize[HTMLElement],
Fetch[ActivityId, Intervals],
Inject[History]
Inject[List[ActivityId]]
): Page[Profile] =
case (URL(s"/modern/profile/$_", _), `a[data-activityid]`(_)) =>
val es = `a[data-activityid]`.all(Seq(document)).filter(isRunning).toList
val fs = for case `data-activityid`(id) <- es yield ActivityId(id).request
if !fs.isEmpty then
for history <- Future.sequence(fs) do
inline def complain[A]: A = throw ju.NoSuchElementException("anchor")
val e = `div[class^="PageContent"]`(document).getOrElse(complain)
history.filter(_.nonEmpty).inject("mpb".elementAt(e.before(_)))
end if
val es = `a[data-activityid]`.all(Seq(document)).filter(isRunning).toList
val ids = for case `data-activityid`(id) <- es yield ActivityId(id)
val e = `div[class^="PageContent"]`(document).getOrElse(throw Complain("anchor"))
ids.inject("mpb".elementAt(e.before(_)))
None

case m =>
Expand Down
2 changes: 2 additions & 0 deletions src/garmin/package.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package garmin


import org.scalajs.dom.Element
import org.scalajs.dom.URL
import org.scalajs.dom.URLSearchParams


private[garmin] object URL:
def unapply(u: URL): Option[(String, URLSearchParams)] =
Some(u.pathname, u.searchParams)
Expand Down
29 changes: 29 additions & 0 deletions src/plotly/ColorPalette.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package plotly

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

import typings.plotlyJs.anon.PartialLayout

trait ColorPalette[A]:
def list: List[String]
extension (i: Int) inline def color = list(i % list.length)
extension (a: PartialLayout) inline def setColorPalette = a.setColorway(list.toJSArray)
end ColorPalette

object ColorPalette:
given ColorPalette[Common] with
val list: List[String] = List(
"#1f77b4",
"#ff7f0e",
"#2ca02c",
"#d62728",
"#9467bd",
"#8c564b",
"#e377c2",
"#7f7f7f",
"#bcbd22",
"#17becf"
)
end given
end ColorPalette
5 changes: 4 additions & 1 deletion src/plotly/DataArrayFrom.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ 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.metrics.*
Expand All @@ -23,11 +24,13 @@ object DataArrayFrom:
Data
.PartialPlotDataAutobinx()
.setName(name)
.setLine(PartialScatterLine().setWidth(1))
.setY(v.map(fy).toJSArray)
.setX(v.indices.map(_ + 1.0).toJSArray)
.setHoverinfo(yPlussignname)

js.Array(
scatterLine("mpb", _.mpb),
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")
Expand Down
28 changes: 22 additions & 6 deletions src/plotly/Layout.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package plotly

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

import typings.plotlyJs.anon.PartialLayout
import typings.plotlyJs.anon.PartialLayoutAxis
Expand All @@ -21,24 +22,39 @@ object Layout:
.setHeight(200)
.setMargin(PartialMargin().setPad(4).setL(50).setR(50).setT(50).setB(50))

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

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().setDtick(1.0).setTitle("圈数"))
.setYaxis(PartialLayoutAxis().setOverlaying(y2).setTickmodeSync)
.setYaxis2(PartialLayoutAxis().setSide(right))
.setYaxis(yAxis)
.setYaxis2(yAxis2)
end intervals

given history(using Layout[Common], Title[History]): Layout[History] = h =>
Common.layout.setTitle(h.title).setShowlegend(false)
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 =
Expand Down
38 changes: 22 additions & 16 deletions src/plotly/Listen.scala
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
package plotly

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.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.std.stdStrings.highlight
import typings.std.stdStrings.normal
import typings.std.stdStrings.visible
import typings.plotlyJs.plotlyJsStrings.right
import typings.plotlyJsDistMin.mod.relayout
import typings.plotlyJsDistMin.mod.restyle

import core.metrics.*
import typings.plotlyJs.anon.PartialScatterLine

type Context[A] = (A, PlotlyHTMLElement)
trait Listen[A, B] extends (Context[A] => Unit)
Expand All @@ -25,36 +30,37 @@ 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
show: Restyle[visible],
hidden: Restyle[legendonly]
): Listen[Intervals, plotly_legendclick] = (_, p) =>
given legendclick(using ColorPalette[Common]): Listen[Intervals, plotly_legendclick] = (_, 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)
show(p, l)
hidden(p, r)
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))
end match
)
end legendclick

given hover(using h: Restyle[highlight], n: Restyle[normal]): Listen[History, plotly_hover] = (_, p) =>
given hover: Listen[History, plotly_hover] = (_, p) =>
val area: HTMLElement = p.querySelector(".nsewdrag")
p.on_plotlyhover(
plotly_hover,
e =>
area.style.cursor = "pointer"
h(p, Seq(e.points(0).curveNumber.intValue))
val data = PartialPlotDataAutobinx().setLine(PartialScatterLine().setWidth(2))
restyle(p, data, e.points(0).curveNumber)
)
p.on(
plotly_unhover,
accept[PlotMouseEvent]: e =>
area.style.cursor = ""
n(p, Seq(e.points(0).curveNumber.intValue))
plotly_unhover,
accept[PlotMouseEvent]: e =>
area.style.cursor = ""
val data = PartialPlotDataAutobinx().setLine(PartialScatterLine().setWidth(1))
restyle(p, data, e.points(0).curveNumber)
)
end hover

Expand Down
30 changes: 0 additions & 30 deletions src/plotly/Restyle.scala

This file was deleted.