// Author: Simon-Pierre Boucher — contact@spboucher.ai // // LTTB (largest-triangle-three-buckets) downsampling: preserves the visual // shape of a series — endpoints kept exactly, one representative point per // bucket chosen to maximize the triangle area with its neighbors. Used to // keep chart data at ~2× pixel width regardless of run length. import Foundation enum Downsampler { struct XY: Equatable, Sendable { var x: Double var y: Double } static func lttb(_ points: [XY], threshold: Int) -> [XY] { let n = points.count guard threshold >= 3, n > threshold else { return points } var sampled: [XY] = [] sampled.reserveCapacity(threshold) sampled.append(points[0]) let bucketSize = Double(n - 2) / Double(threshold - 2) var a = 0 // index of the previously selected point for i in 0..<(threshold - 2) { // Average of the NEXT bucket is the third triangle vertex. let nextStart = Int(Double(i + 1) * bucketSize) + 1 let nextEnd = min(Int(Double(i + 2) * bucketSize) + 1, n) var avgX = 0.0, avgY = 0.0 let span = max(nextEnd - nextStart, 1) for j in nextStart.. maxArea { maxArea = area chosen = j } } sampled.append(points[chosen]) a = chosen } sampled.append(points[n - 1]) return sampled } }