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.
This commit is contained in:
+30
-12
@@ -35,10 +35,12 @@ struct TrendsView: View {
|
|||||||
@State private var loadFailed = false
|
@State private var loadFailed = false
|
||||||
@State private var firstSnapshot: String?
|
@State private var firstSnapshot: String?
|
||||||
|
|
||||||
/// Lines trace-in left→right on first appearance by revealing an
|
/// Per-series draw-in waterline: maps stationID → how many leading points
|
||||||
/// increasing prefix of each series' points. Stays at the full count after
|
/// are revealed, so each line traces left→right on first appearance.
|
||||||
/// the first reveal so range/mode switches morph instead of re-tracing.
|
/// Series reveal in a short staggered cascade so the draw is clearly
|
||||||
@State private var revealCount: Int = 0
|
/// 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
|
/// Seeded from `selectedFuel` (the fuel the tab was on) so the sheet
|
||||||
/// opens where the user was — same pattern as FavouritesView.
|
/// opens where the user was — same pattern as FavouritesView.
|
||||||
@@ -171,16 +173,32 @@ struct TrendsView: View {
|
|||||||
withAnimation(reduceMotion ? nil : .easeInOut(duration: 0.35)) {
|
withAnimation(reduceMotion ? nil : .easeInOut(duration: 0.35)) {
|
||||||
series = fetched
|
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 {
|
} else {
|
||||||
series = fetched
|
series = fetched
|
||||||
let maxPoints = fetched.reduce(0) { max($0, $1.points.count) }
|
let hasPoints = fetched.contains { !$0.points.isEmpty }
|
||||||
if maxPoints > 0 {
|
if hasPoints {
|
||||||
if reduceMotion {
|
if reduceMotion {
|
||||||
revealCount = maxPoints
|
var all: [String: Int] = [:]
|
||||||
|
for h in fetched { all[h.stationID] = h.points.count }
|
||||||
|
revealed = all
|
||||||
} else {
|
} else {
|
||||||
revealCount = 0
|
revealed = [:]
|
||||||
withAnimation(.easeOut(duration: 0.5)) {
|
let cascadeNS = UInt64(0.18 * 1_000_000_000)
|
||||||
revealCount = maxPoints
|
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.)
|
// type-checker's budget.)
|
||||||
Chart {
|
Chart {
|
||||||
ForEach(displaySeries) { history in
|
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,
|
areaMark(point, series: history.name,
|
||||||
color: seriesColor(index(of: history.stationID)))
|
color: seriesColor(index(of: history.stationID)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ForEach(displaySeries) { history in
|
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,
|
lineMark(point, series: history.name,
|
||||||
color: seriesColor(index(of: history.stationID)))
|
color: seriesColor(index(of: history.stationID)))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user