Skip to content

Commit fde1c27

Browse files
committed
Widget: hold the chart's scale still while a forecast is up
The forecast is anchored to the loop cycle it came from, so its far end stays put while the timeline's entries walk the now line toward it. The scale ended at that far end, so the span shrank by a minute an entry while the width stayed the same, and the same three hours of history came out wider each time. The chart breathed rather than scrolled. Measure the far edge from the render's own moment instead, so the span is fixed for as long as a cone is drawn and the readings only translate. The cone recedes into the room held for it, which is its age at a glance. No room is held where no forecast is drawn, so a horizon set against a loop that publishes none costs nothing.
1 parent 9e790d1 commit fde1c27

1 file changed

Lines changed: 27 additions & 9 deletions

File tree

LoopFollowWidget/WidgetChartView.swift

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -427,15 +427,33 @@ struct WidgetChartView: View {
427427

428428
let start = now.addingTimeInterval(-window - edgeSlack)
429429
let lastReading = visible.last?.date ?? now
430-
// Only as far as the forecast actually reaches. Asking for an hour and
431-
// getting forty minutes leaves no empty stretch pushing history aside:
432-
// the curves the loop publishes end where they end, and nothing here
433-
// pads or extrapolates to fill a horizon that was only ever a ceiling.
434-
let tail = forecast.last?.date ?? .distantPast
435-
// A cone ends in its own taper and clipping a taper costs nothing, so
436-
// the slack is kept only where the rightmost mark was measured, which
437-
// includes a reading stamped past the end of the forecast.
438-
let end = max(now, lastReading, tail).addingTimeInterval(tail > lastReading ? 0 : edgeSlack)
430+
431+
// Room for the horizon rather than for what is left of the cone. A
432+
// forecast is anchored to the cycle it came from, so its far end stands
433+
// still while the entries walk the now line toward it, and ending the
434+
// scale there took the span in a little further every entry. The width
435+
// does not change, so the same history came out wider each time: the
436+
// chart breathed instead of scrolling. Measuring the far edge from the
437+
// render's own moment fixes the span, so the readings only translate and
438+
// the cone recedes into the room held for it, which is its age drawn to
439+
// scale. The room goes with the cone: none is held where no forecast is
440+
// drawn, so a horizon picked against a loop that publishes none costs
441+
// nothing, and a spent cone leaves the chart shaped as it was without one.
442+
let end: Date
443+
if let tail = forecast.last?.date {
444+
// A cone ends in its own taper and clipping a taper costs nothing, so
445+
// the trailing slack is still spent only on a measured mark: a reading
446+
// stamped past the held room, which is a clock running ahead. `tail`
447+
// is here for the same reason, since a forecast anchored a little
448+
// ahead of us reaches past that room too.
449+
end = max(
450+
now.addingTimeInterval(effectiveForecast),
451+
tail,
452+
lastReading.addingTimeInterval(edgeSlack)
453+
)
454+
} else {
455+
end = max(now, lastReading).addingTimeInterval(edgeSlack)
456+
}
439457

440458
// The forecast is allowed to open the scale. A predicted low clipped out
441459
// of view is the one failure worth avoiding here, and history flattening

0 commit comments

Comments
 (0)