show ticks on time axis

This commit is contained in:
Daniel Barlow 2024-11-17 16:16:46 +00:00
parent 54af226e52
commit e0d395ba9f
3 changed files with 148 additions and 1 deletions

View File

@ -117,6 +117,43 @@ change depending on the nature of the training effort. e.g.
for a long slow ride we show total distance, for interval training for a long slow ride we show total distance, for interval training
we show time spent in HR zones ... we show time spent in HR zones ...
----
time axis wants to show ticks which are at least (portalWidth/4)
pixels apart, and choose the smallest of the following time intervals
which are greater than that width
1 second
5 seconds
15 seconds
30 seconds
60 seconds
5 minutes
15 minutes
1 hour
3 hours
6 hours
24 hours
if the full width is 240 seconds, show a tick every 60 second
if the full width is 20 seconds, show a tick every 5 seconds
if the width grows past 20 seconds, the distance between points shrinks
and therefore we put ticks less often
if width <= 4 * 5, 5 second tick
if width <= 4 * 15, 15 second tick
etc ...
to convert time to pixels, multiply by portalWidth / duration
portalWidth / duration
## Postgres ## Postgres

View File

@ -2,7 +2,7 @@
"type": "application", "type": "application",
"source-directories": [ "source-directories": [
"frontend/src", "frontend/src",
"frontend/tests" "frontend/tests"
], ],
"elm-version": "0.19.1", "elm-version": "0.19.1",
"dependencies": { "dependencies": {
@ -15,6 +15,7 @@
"elm/svg": "1.0.1", "elm/svg": "1.0.1",
"elm/time": "1.0.0", "elm/time": "1.0.0",
"elm/url": "1.0.0", "elm/url": "1.0.0",
"elm-community/list-extra": "8.7.0",
"elm-explorations/test": "2.2.0", "elm-explorations/test": "2.2.0",
"mpizenberg/elm-pointer-events": "5.0.0", "mpizenberg/elm-pointer-events": "5.0.0",
"rtfeldman/elm-iso8601-date-strings": "1.1.4", "rtfeldman/elm-iso8601-date-strings": "1.1.4",

View File

@ -8,6 +8,7 @@ import Html.Events exposing (onClick, on)
import Html.Events.Extra.Pointer as Pointer import Html.Events.Extra.Pointer as Pointer
import Maybe exposing (Maybe) import Maybe exposing (Maybe)
import Lib exposing(..) import Lib exposing(..)
import List.Extra exposing(find)
import Json.Decode as D import Json.Decode as D
import Http import Http
import Point exposing(Point, Pos ,decoder) import Point exposing(Point, Pos ,decoder)
@ -23,6 +24,7 @@ import Svg.Attributes as S exposing
, fill , fill
, points , points
, stroke, strokeWidth, strokeOpacity) , stroke, strokeWidth, strokeOpacity)
import Time exposing(Posix)
import Url.Parser exposing (Parser, (</>), (<?>), int, map, oneOf, s, string) import Url.Parser exposing (Parser, (</>), (<?>), int, map, oneOf, s, string)
import Url.Parser.Query as Query import Url.Parser.Query as Query
import Url exposing (Url) import Url exposing (Url)
@ -238,8 +240,40 @@ newModel msg model =
NewUrlRequest -> model NewUrlRequest -> model
UrlChanged -> model UrlChanged -> model
-- VIEW -- VIEW
formatTime epoch =
let utc = Time.utc
time = Time.millisToPosix <| floor(epoch * 1000)
zeroed i = String.padLeft 2 '0' (String.fromInt i)
in String.fromInt (Time.toHour utc time)
++ ":" ++
zeroed (Time.toMinute utc time)
++ ":" ++
zeroed (Time.toSecond utc time)
timeTick duration =
let width = duration / 6
candidates =
[ 1
, 3
, 5
, 10
, 15
, 30
, 60
, 60 * 3
, 60 * 5
, 60 * 10
, 60 * 15
, 60 * 30
, 60 * 60
]
in case List.Extra.find (\ candidate -> width <= candidate) candidates of
Just n -> n
Nothing -> width
tileUrl : TileNumber -> ZoomLevel -> String tileUrl : TileNumber -> ZoomLevel -> String
tileUrl {x,y} z = tileUrl {x,y} z =
String.concat ["https://a.tile.openstreetmap.org", String.concat ["https://a.tile.openstreetmap.org",
@ -272,6 +306,8 @@ measureView title colour fn allPoints =
rangeYaxis = maxYaxis - minYaxis rangeYaxis = maxYaxis - minYaxis
maxX = Point.duration allPoints maxX = Point.duration allPoints
string = String.concat (List.map coords filteredPoints) string = String.concat (List.map coords filteredPoints)
ttick = timeTick maxX
firstTimeTick = (toFloat (floor(startTime / ttick))) * ttick - startTime
ybar n = line ybar n = line
[ fill "none" [ fill "none"
, style "vector-effect" "non-scaling-stroke" , style "vector-effect" "non-scaling-stroke"
@ -282,6 +318,24 @@ measureView title colour fn allPoints =
, x2 (String.fromFloat (0.95 * maxX)) , x2 (String.fromFloat (0.95 * maxX))
, y2 (String.fromFloat (minYaxis + n * tickY)) , y2 (String.fromFloat (minYaxis + n * tickY))
] [] ] []
xtick n =
let t = firstTimeTick + n * timeTick maxX
xpix = t * portalWidth/maxX
label = formatTime (t + startTime)
in
g []
[ line
[ fill "none"
, style "vector-effect" "non-scaling-stroke"
, strokeWidth "1"
, stroke "#aaa"
, x1 (String.fromFloat xpix)
, y1 "0"
, x2 (String.fromFloat xpix)
, y2 "180"
] []
]
ylabel n = Svg.text_ ylabel n = Svg.text_
[ x "99%", y (String.fromFloat (graphHeight - graphHeight * n * (tickY/rangeYaxis))) [ x "99%", y (String.fromFloat (graphHeight - graphHeight * n * (tickY/rangeYaxis)))
, style "text-anchor" "end" , style "text-anchor" "end"
@ -330,8 +384,62 @@ measureView title colour fn allPoints =
, ylabel 1 , ylabel 1
, ylabel 2 , ylabel 2
, ylabel 3 , ylabel 3
, xtick 0
, xtick 1
, xtick 2
, xtick 3
, xtick 4
, xtick 5
] ]
timeAxis allPoints =
let filteredPoints = Point.downsample 300 allPoints
graphHeight = 30
startTime = case allPoints of
(p::_) -> p.time
_ -> 0
maxX = Point.duration allPoints
ttick = timeTick maxX
firstTimeTick = (toFloat (floor(startTime / ttick))) * ttick - startTime
xtick n =
let t = firstTimeTick + (toFloat n) * timeTick maxX
xpix = t * portalWidth/maxX
label = formatTime (t + startTime)
in
g []
[ line
[ fill "none"
, style "vector-effect" "non-scaling-stroke"
, strokeWidth "1"
, stroke "#333"
, x1 (String.fromFloat xpix)
, y1 "0"
, x2 (String.fromFloat xpix)
, y2 "10"
] []
, Svg.text_ [ x (String.fromFloat xpix)
, style "text-anchor" "middle"
, style "vertical-align" "bottom"
, y "25" ]
[ Svg.text label ]
]
xticks = List.map xtick <| List.range 0 6
bg = rect
[ x "0"
, width portalWidth
, height graphHeight
, fill "#eef"
, stroke "none"
] []
in
svg
[ width portalWidth
, height graphHeight
, preserveAspectRatio "none"
]
(bg::xticks)
cadenceView : List Point -> Svg Msg cadenceView : List Point -> Svg Msg
cadenceView = cadenceView =
measureView "cadence" "#44ee44" (.cadence >> Maybe.map toFloat) measureView "cadence" "#44ee44" (.cadence >> Maybe.map toFloat)
@ -469,6 +577,7 @@ viewDiv model =
[ div [] [ ifTrack model cadenceView ] [ div [] [ ifTrack model cadenceView ]
, div [] [ ifTrack model powerView ] , div [] [ ifTrack model powerView ]
, div [] [ ifTrack model eleView ] , div [] [ ifTrack model eleView ]
, div [] [ ifTrack model timeAxis ]
] ]
] ]