Begin statistics creation
This commit is contained in:
115
CHDataManagement/Workouts/Double+Arithmetic.swift
Normal file
115
CHDataManagement/Workouts/Double+Arithmetic.swift
Normal file
@@ -0,0 +1,115 @@
|
||||
import Foundation
|
||||
|
||||
extension Double {
|
||||
|
||||
/**
|
||||
Move to a different value by the given ratio of their distance.
|
||||
*/
|
||||
func move(_ ratio: Double, to other: Double) -> Double {
|
||||
self + (other - self) * ratio
|
||||
}
|
||||
}
|
||||
|
||||
extension Collection where Element == Double {
|
||||
|
||||
func sum() -> Double {
|
||||
reduce(0, +)
|
||||
}
|
||||
|
||||
func average() -> Double {
|
||||
sum() / Double(count)
|
||||
}
|
||||
}
|
||||
|
||||
extension Collection where Element == Double, Index == Int {
|
||||
|
||||
func floatingMeanFiltered(windowSize: Int) -> [Double] {
|
||||
guard windowSize > 1 else {
|
||||
return self.map { $0 }
|
||||
}
|
||||
guard count >= windowSize else {
|
||||
return .init(repeating: average(), count: count)
|
||||
}
|
||||
|
||||
let firstHalf = windowSize / 2
|
||||
let secondHalf = windowSize - firstHalf
|
||||
var minSpeed: Double = 0
|
||||
var maxSpeed: Double = 0
|
||||
let averageScale = 1.0 / Double(windowSize)
|
||||
|
||||
// First calculate the filtered speeds in the normal unit
|
||||
var currentAverage = self[0..<windowSize].average()
|
||||
var result: [Double] = .init(repeating: currentAverage, count: firstHalf + 1)
|
||||
for index in firstHalf..<count-firstHalf-1 { // Index in self
|
||||
let removed = self[index-firstHalf]
|
||||
let added = self[index+secondHalf]
|
||||
currentAverage += (added - removed) * averageScale
|
||||
result.append(currentAverage)
|
||||
if currentAverage < minSpeed { minSpeed = currentAverage }
|
||||
else if currentAverage > maxSpeed { maxSpeed = currentAverage }
|
||||
}
|
||||
result.append(contentsOf: [Double](repeating: currentAverage, count: secondHalf))
|
||||
return result
|
||||
}
|
||||
|
||||
func fittingAxisLimits(desiredNumSteps: Int = 5, minLimit: Double? = nil, maxLimit: Double? = nil) -> RouteProfile {
|
||||
let (dataMin, dataMax) = minMax(minLimit: minLimit, maxLimit: maxLimit)
|
||||
|
||||
let dataRange = dataMax - dataMin
|
||||
let roughStep = dataRange / Double(desiredNumSteps)
|
||||
|
||||
let exponent = floor(log10(roughStep))
|
||||
let base = pow(10.0, exponent)
|
||||
|
||||
let step: Double
|
||||
if roughStep <= base {
|
||||
step = base
|
||||
} else if roughStep <= 2 * base {
|
||||
step = 2 * base
|
||||
} else if roughStep <= 5 * base {
|
||||
step = 5 * base
|
||||
} else {
|
||||
step = 10 * base
|
||||
}
|
||||
|
||||
let graphMin = floor(dataMin / step) * step
|
||||
let graphMax = ceil(dataMax / step) * step
|
||||
let numTicks = Int((graphMax - graphMin) / step)
|
||||
return .init(min: graphMin, max: graphMax, ticks: numTicks)
|
||||
}
|
||||
|
||||
func minMax(minLimit: Double? = nil, maxLimit: Double? = nil) -> (min: Double, max: Double) {
|
||||
var dataMin = self.min() ?? 0
|
||||
var dataMax = self.max() ?? 1
|
||||
if let minLimit {
|
||||
dataMin = Swift.max(dataMin, minLimit)
|
||||
}
|
||||
if let maxLimit {
|
||||
dataMax = Swift.min(dataMax, maxLimit)
|
||||
}
|
||||
return (dataMin, dataMax)
|
||||
}
|
||||
|
||||
func fittingTimeAxisLimits(minTicks: Int = 3, maxTicks: Int = 7, minLimit: Double? = nil, maxLimit: Double? = nil) -> RouteProfile {
|
||||
let (dataMin, dataMax) = minMax(minLimit: minLimit, maxLimit: maxLimit)
|
||||
|
||||
let dataRange = dataMax - dataMin
|
||||
let niceSteps: [Double] = [15, 30, 60, 120, 300, 600, 900, 1800, 3600] // in seconds
|
||||
|
||||
// Find the step size that gives a nice number of ticks
|
||||
var chosenStep = niceSteps.last! // fallback to largest if none fit
|
||||
for step in niceSteps {
|
||||
let numTicks = dataRange / step
|
||||
if Double(minTicks) <= numTicks && numTicks <= Double(maxTicks) {
|
||||
chosenStep = step
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let graphMin = floor(dataMin / chosenStep) * chosenStep
|
||||
let graphMax = ceil(dataMax / chosenStep) * chosenStep
|
||||
let numTicks = Int((graphMax-graphMin) / chosenStep)
|
||||
return .init(min: graphMin, max: graphMax, ticks: numTicks)
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user