SY-3942: Range Alignment and Relative Time Axis#2251
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## rc #2251 +/- ##
==========================================
- Coverage 63.98% 63.91% -0.08%
==========================================
Files 2160 2160
Lines 109351 109499 +148
Branches 8288 8334 +46
==========================================
+ Hits 69967 69983 +16
- Misses 33363 33460 +97
- Partials 6021 6056 +35
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
This is awesome! One major behavioral issue is that the live data isn't plotting while the in the active range. For example, In the following image, I have "Range_Example" selected, which spans 10:05 - 10:15. The Range is selected for the plot, but it isn't updating live. On a refresh, the newest data randers.
<------- Working Screenshots ------->

|
Fixed. Plots now update when a range is incomplete. FixedIncompleteRange_PlotUpdate.mp4 |
6e7ca28 to
2d70ae6
Compare
Issue Pull Request
Linear Issue
SY-3942
Description
Adds an Align Ranges control (labeled "Relative Time") to the line plot Data toolbar. When enabled, each static range's data is shifted by its own start time, so multiple ranges overlay on a shared T-0 and the x-axis switches to a new `relativeTime` tick format that renders elapsed durations (e.g. `0s`, `5s`, `1:30`) instead of absolute timestamps. As a free side effect, a single aligned range gets a clean T0+ elapsed-time axis.
To make overlaid ranges distinguishable, the Pluto line plot now carries a `subGroup` on each line (key + display name) and a 1-based `subGroupIndex`. The Legend renders a new sub-group key showing each range's index, name, and visibility toggle; hovering a sub-group highlights its lines; the tooltip renders the sub-group index next to each sample so users can tell overlaid ranges apart without color cues.
The toggle only appears when at least one static range is selected and no dynamic (rolling) range is selected — alignment isn't meaningful in the dynamic case and would produce garbage tick labels (see discussion on branch; rolling-range support is deferred).
The toggle's state is persisted per plot via a new optional `align` field on the existing v4 line plot state, defaulted via zod so existing persisted states migrate transparently without a new version bump.
Stacked on #2250.
Basic Readiness
Greptile Summary
This PR adds a "Relative Time" axis feature to the line plot: when enabled, each static range's data is shifted by its start timestamp so multiple ranges overlay at a shared T-0, and the x-axis switches to an elapsed-duration tick format. Supporting changes include sub-group labeling in the legend, tooltip updates, an incomplete-range streaming fallback for static ranges with future end times, and GL alpha blending for the hover-dim effect.
console/src/lineplot/LinePlot.tsx): TheuseAsyncEffectthat sets the x-axis type runs only on[vis.channels.x1, vis.align]changes. If the user enables alignment (axis →"relativeTime") and then removes all static ranges or adds a dynamic range,vis.alignstaystruebut the toggle is hidden — the axis is permanently stuck showing elapsed-time labels with no UI path to reset it.pluto/src/lineplot/Legend.tsx):FocusedGroup(used in the Fixed dual-y-axis legend) does not forwardhighlightedSubGroupIndexto<Entries>, so hover-dimming silently doesn't work in that layout variant.pluto/src/vis/axis/ticks.ts):formatRelativeTimecan produce"X.1000s"when the millisecond rounding overflows (e.g.totalMs = 1999.5→ms = 1000).Confidence Score: 3/5
Mergeable with caution — one P1 leaves the x-axis in a broken state after range changes.
A confirmed P1: the axis type becomes permanently stuck as
relativeTimeafter static ranges are removed, with no UI path to recover. This affects core plot rendering. The two P2s are cosmetic/edge-case but real. The rest of the implementation is solid.console/src/lineplot/LinePlot.tsx(axis-type effect deps) andpluto/src/lineplot/Legend.tsx(FocusedGroupmissinghighlightedSubGroupIndex).Important Files Changed
vis.alignto the axis-type effect deps and removes theusePreviousguard; x-axis type becomes permanently stuck as"relativeTime"when ranges change away from static-only because the effect doesn't depend onvis.ranges.x1.timeOffset,subGroup, andsubGroupIndexto each line props; usesrange.timeRange.startdirectly as the nanosecond offset for aligned static ranges.setAlign.relativeTimetick type,formatRelativeTimeformatter, andRelativeTimeTickFactory; millisecond carry case can produce malformed labels like"1.1000s".SubGroupKeycomponent and sub-group hover logic;FocusedGroupin the Fixed multi-y layout omitshighlightedSubGroupIndex, so dimming is skipped there.timeOffsetandsubGroupIndexto line state; correctly applies offset to x-bounds, find-by-value, and GL transform.rangeIsIncomplete/incompleteRangeTelemSpechelpers (with tests), addstimeOffset/subGroup/subGroupIndextoBaseLineProps, and switches to a proper streaming telem source for incomplete static ranges.xAxisTickType === "relativeTime".align: z.boolean().default(false)to the v4 state schema; existing persisted states migrate transparently without a version bump.Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[User toggles Relative Time] --> B[dispatch setAlign] B --> C[vis.align = true/false] C --> D{useAsyncEffect\ndeps: x1 channel, align} D --> E[Retrieve channel data type] E --> F{align && TIMESTAMP?} F -- yes --> G[axis.type = relativeTime] F -- no --> H[axis.type = time/linear] I[buildLines] --> J{vis.align && static range?} J -- yes --> K[timeOffset = range.start] J -- no --> L[timeOffset = 0] K --> M[Line aether: shift x-bounds\nfindByXValue, GL transform] G --> N[RelativeTimeTickFactory\nformatRelativeTime] G --> O[Tooltip: Elapsed header\nsubGroupIndex prefix] P[User removes static ranges] --> Q[showAlign = false\ntoggle hidden] Q --> R[vis.align stays true ⚠️\nEffect NOT re-triggered\nAxis stuck as relativeTime]Comments Outside Diff (4)
pluto/src/lineplot/Line.tsx, line 496 (link)subGroupobject instability triggers excessivesetLinecallsbuildLinescreates a fresh{ key, name }object on every call (it is not memoized in the consoleLinePlot). BecauseuseEffectusesObject.isfor dependency comparison,subGroupalways looks changed, sosetLineis called on every render of eachLinecomponent.setLineunconditionally rebuilds thelinesarray ([...prev.filter(...), meta]), which updates the LinePlot context, re-rendering all Legend/tooltip consumers on every render cycle.Consider memoizing the
subGroupvalue insideLine, or stabilizing it upstream inbuildLines/ the parentChannel.LinePlot.console/src/lineplot/LinePlot.tsx, line 25-27 (link)relativeTimeaxis type applied to non-timestamp x-channels whenalignis trueWhen
vis.align = trueand the x-axis channel is not aTIMESTAMP(e.g., a numeric sensor channel),newTypeis still set to"relativeTime"(both the initial assignment and the override branch). TherelativeTimetick formatter formats values as elapsed durations, which would be meaningless for arbitrary numeric data. Since the "Relative Time" toggle is hidden when dynamic ranges are present, this case can only occur if the persisted state hasalign: trueand the user then switches to a non-timestamp x-channel. A guard likenewType = "linear"whenalignis true but the channel is not a timestamp would be safer.console/src/components/Controls.css, line 20-27 (link)The PR accidentally introduces a second
.console-controls--annotations-visibleblock (lines 20–23) immediately before the pre-existing one (lines 25–27), and removestransition: opacity 0.1s ease-in-out;from the base.console-controlsrule. The duplicate rule is harmless (sametop: 0rem), but the missing transition means the controls container now pops in/out instantly on hover instead of fading, which is a UX regression.The pre-existing rule only set
top: 0rem; the new rule additionally setsright: 2rem, which is already inherited from.console-controls. It looks like the intent was simply to addright: 2remto the existing rule, not create a new one. The result should be:pluto/src/lineplot/tooltip/aether/tooltip.ts, line 808-814 (link)maxLabelLengthmeasured fromvalidValuesbut width also covers the header rowmaxLabelLengthis now computed fromvalidValues(lines with a non-NaN result), butvalues(the full list passed todraw.list) may contain entries from invalid lines whoselabelis longer than anyvalidValueslabel. Longer labels from invalid entries are silently excluded from the width calculation, which can cause the tooltip box to be too narrow and clip those labels.Before this PR the reduction iterated
values(the full list):The new code uses
validValuesinstead. Switch back to reducing overvaluesto cover all rendered rows.Reviews (5): Last reviewed commit: "SY-3942: Fix tooltip width clipping long..." | Re-trigger Greptile