From 508ccc38027203ebe6f6859fa481f6b31da2202d Mon Sep 17 00:00:00 2001 From: FuelBoard Contributor Date: Thu, 20 Aug 2026 18:40:09 +0100 Subject: [PATCH] trends: staggered per-series line draw-in The single-waterline trace-in was invisible with sparse data (2 points draw in one step). Replace it with a per-series waterline: each station's line now traces left->right on first appearance, series revealed 0.18s apart in a cascade so the draw is clearly visible. Newly-added favourites get a full reveal immediately on reload; Reduce Motion still jump-cuts. Range/mode morphs and numeric headline transition unchanged. --- FuelBoard/TrendsView.swift | 42 +++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/FuelBoard/TrendsView.swift b/FuelBoard/TrendsView.swift index c6b747c..b91fee1 100644 --- a/FuelBoard/TrendsView.swift +++ b/FuelBoard/TrendsView.swift @@ -35,10 +35,12 @@ struct TrendsView: View { @State private var loadFailed = false @State private var firstSnapshot: String? - /// Lines trace-in left→right on first appearance by revealing an - /// increasing prefix of each series' points. Stays at the full count after - /// the first reveal so range/mode switches morph instead of re-tracing. - @State private var revealCount: Int = 0 + /// Per-series draw-in waterline: maps stationID → how many leading points + /// are revealed, so each line traces left→right on first appearance. + /// Series reveal in a short staggered cascade so the draw is clearly + /// visible even with only 2 points per line. Stays full after the first + /// reveal so range/mode switches morph instead of re-tracing. + @State private var revealed: [String: Int] = [:] /// Seeded from `selectedFuel` (the fuel the tab was on) so the sheet /// opens where the user was — same pattern as FavouritesView. @@ -171,16 +173,32 @@ struct TrendsView: View { withAnimation(reduceMotion ? nil : .easeInOut(duration: 0.35)) { series = fetched } + // Newly-added favourites have no reveal entry yet — surface them + // fully so their line isn't left blank by the draw-in waterline. + for h in fetched where revealed[h.stationID] == nil { + revealed[h.stationID] = h.points.count + } } else { series = fetched - let maxPoints = fetched.reduce(0) { max($0, $1.points.count) } - if maxPoints > 0 { + let hasPoints = fetched.contains { !$0.points.isEmpty } + if hasPoints { if reduceMotion { - revealCount = maxPoints + var all: [String: Int] = [:] + for h in fetched { all[h.stationID] = h.points.count } + revealed = all } else { - revealCount = 0 - withAnimation(.easeOut(duration: 0.5)) { - revealCount = maxPoints + revealed = [:] + let cascadeNS = UInt64(0.18 * 1_000_000_000) + for (i, h) in fetched.enumerated() { + let sid = h.stationID + let total = h.points.count + Task { + try? await Task.sleep(nanoseconds: UInt64(i) * cascadeNS) + guard !Task.isCancelled else { return } + withAnimation(.easeOut(duration: 0.5)) { + revealed[sid] = total + } + } } } } @@ -419,13 +437,13 @@ struct TrendsView: View { // type-checker's budget.) Chart { ForEach(displaySeries) { history in - ForEach(history.points.prefix(revealCount)) { point in + ForEach(history.points.prefix(revealed[history.stationID] ?? 0)) { point in areaMark(point, series: history.name, color: seriesColor(index(of: history.stationID))) } } ForEach(displaySeries) { history in - ForEach(history.points.prefix(revealCount)) { point in + ForEach(history.points.prefix(revealed[history.stationID] ?? 0)) { point in lineMark(point, series: history.name, color: seriesColor(index(of: history.stationID))) }