commit 9b6ddd46d51c33a6cf48751173e0e8af468bb585
parent 85c040023dead7209b3aef6d9c5c585ae7f576c0
Author: Daniel Barlow <dan@telent.net>
Date: Tue, 12 Nov 2024 21:29:02 +0000
render cadence graph
in the wrong place and size but it's on the screen which is a start
Diffstat:
4 files changed, 112 insertions(+), 10 deletions(-)
diff --git a/elm.json b/elm.json
@@ -1,7 +1,8 @@
{
"type": "application",
"source-directories": [
- "frontend/src"
+ "frontend/src",
+ "frontend/tests"
],
"elm-version": "0.19.1",
"dependencies": {
diff --git a/frontend/src/Main.elm b/frontend/src/Main.elm
@@ -220,8 +220,41 @@ tileImg zoom tilenumber = img [ width 256,
height 256,
src (tileUrl tilenumber zoom) ] []
-trackView : List Point -> Int -> Int -> Zoom -> Svg Msg
-trackView points leftedge topedge zoom =
+
+cadenceView : List Point -> Svg Msg
+cadenceView allPoints =
+ let filteredPoints = Point.downsample 100 allPoints
+ startTime = case allPoints of
+ (p::_) -> p.time
+ _ -> 0
+ coords p = case p.cadence of
+ Just c ->
+ (String.fromFloat (p.time - startTime)) ++ "," ++
+ (String.fromInt c) ++ ", "
+ Nothing -> ""
+ string = String.concat (List.map coords filteredPoints)
+ in
+ svg
+ [ H.style "width" "100%"
+ , H.style "height" "100%"
+ , H.style "position" "absolute"
+ ]
+ [ g
+ [ fill "none"
+ , stroke "red"
+ , strokeWidth "3"
+ ]
+ [
+ polyline
+ [ fill "none"
+ , S.points string
+ ] []
+ ]
+ ]
+
+
+trackView : Int -> Int -> Zoom -> List Point -> Svg Msg
+trackView leftedge topedge zoom points =
let plot p =
let (x, y) = pixelFromCoord (toCoord p.pos.lat p.pos.lon) zoom
x_ = x - leftedge
@@ -257,6 +290,13 @@ tiles xs ys zoom =
(List.map (\ x -> tileImg zoom (TileNumber x y)) xs))
ys
+ifTrack track content =
+ case track of
+ Present t -> content t
+ Failure f -> Debug.log f (div [] [ text "failure", text f])
+ Loading -> div [] [text "loading"]
+ Empty -> div [] [text "no points"]
+
canvas centre zoom width height track =
let (mintile, maxtile) = boundingTiles centre zoom width height
@@ -272,11 +312,7 @@ canvas centre zoom width height track =
xs = List.range mintile.x maxtile.x
ys = List.range mintile.y maxtile.y
epos e = Tuple.mapBoth floor floor e.pointer.clientPos
- tv = case track of
- Present t -> trackView t leftedge topedge zoom
- Failure f -> Debug.log f (div [] [ text "failure", text f])
- Loading -> div [] [text "loading"]
- Empty -> div [] [text "no points"]
+ tv = ifTrack track (trackView leftedge topedge zoom)
in div [style "position" "absolute"
,style "width" (px pixWidth)
,style "height" (px pixHeight)
@@ -295,13 +331,15 @@ viewDiv : Model -> Html Msg
viewDiv model =
let coord = translate model.centre (pixelsToCoord model.zoom (dragDelta model.drag))
canvasV = canvas coord model.zoom portalWidth portalHeight model.track
- in div []
+ in div [ style "display" "inline-block" ]
[ (div [ style "width" (px portalWidth)
, style "height" (px portalHeight)
, style "display" "inline-block"
, style "position" "relative"
, style "overflow" "hidden"]
[canvasV])
+ , div [ style "width" (px portalWidth), style "height" "150"]
+ [ ifTrack model.track cadenceView ]
, div [] [ text (String.fromInt model.zoom ) ]
, div [] [ case model.track of
-- Present tk -> text (String.fromInt (List.length tk))
diff --git a/frontend/src/Point.elm b/frontend/src/Point.elm
@@ -1,4 +1,4 @@
-module Point exposing(Pos, Point, decoder)
+module Point exposing(Pos, Point, decoder, downsample)
import Json.Decode as D
@@ -31,3 +31,31 @@ decoder = D.map5 Point
(D.field "cadence" (D.maybe D.int))
(D.field "power" (D.maybe D.int))
(D.field "heartRate" (D.maybe D.int))
+
+
+llast x xs =
+ case xs of
+ [] -> x
+ (x_::xs_) -> llast x_ xs_
+
+tracef x = Debug.log (String.fromFloat x) x
+
+-- divide the points into n equal time buckets and return the first
+-- point in each
+downsample n points =
+ let
+ nextpoint step prev points_ =
+ case points_ of
+ (next::rest) ->
+ if next.time - prev.time > step
+ then prev :: (nextpoint step next rest)
+ else nextpoint step prev rest
+ _ -> [prev]
+ in
+ case points of
+ (first::rest) ->
+ let last = llast first rest
+ duration = last.time - first.time
+ step = duration / (toFloat n)
+ in nextpoint step first rest
+ [] -> []
diff --git a/frontend/tests/PointTest.elm b/frontend/tests/PointTest.elm
@@ -0,0 +1,35 @@
+module PointTest exposing (specs)
+
+import Point exposing (Point, Pos, downsample)
+import Test exposing (..)
+import Expect exposing (Expectation)
+
+newPoint i = Point
+ (1731437067 + (toFloat i)*1.2)
+ (Pos 52 1 (Just 10))
+ (Just (round(sin((toFloat i)/10)*10))) (Just i) Nothing
+
+
+points n = List.map newPoint (List.range 0 n)
+
+maybe default val =
+ case val of
+ Just v -> v
+ Nothing -> default
+
+specs: Test
+specs =
+ describe "downsample"
+ [ test "it returns no more points than requested" <|
+ \_ ->
+ let orig = (points 10)
+ sampled = Point.downsample 5 orig
+ in Expect.equal 5 (List.length sampled)
+ , test "it drops points which are too close together" <|
+ \_ ->
+ let orig = List.map newPoint [ 0, 1, 2, 3, 4, 10, 500, 1000 ]
+ sampled = Point.downsample 10 orig
+ idx p = maybe 0 p.power
+ in Expect.equalLists [0, 500, 1000] (List.map idx sampled)
+
+ ]