show ticks on time axis
This commit is contained in:
parent
54af226e52
commit
e0d395ba9f
37
README.md
37
README.md
@ -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
|
||||||
|
|
||||||
|
3
elm.json
3
elm.json
@ -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",
|
||||||
|
@ -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 ]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user