Skip to content

SY-3942: Range Alignment and Relative Time Axis#2251

Open
emilbon99 wants to merge 8 commits intorcfrom
sy-3942-range-alignment
Open

SY-3942: Range Alignment and Relative Time Axis#2251
emilbon99 wants to merge 8 commits intorcfrom
sy-3942-range-alignment

Conversation

@emilbon99
Copy link
Copy Markdown
Contributor

@emilbon99 emilbon99 commented Apr 22, 2026

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

  • I have performed a self-review of my code.
  • I have added relevant, automated tests to cover the changes.
  • I have updated documentation to reflect the changes.

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.

  • P1 (console/src/lineplot/LinePlot.tsx): The useAsyncEffect that 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.align stays true but the toggle is hidden — the axis is permanently stuck showing elapsed-time labels with no UI path to reset it.
  • P2 (pluto/src/lineplot/Legend.tsx): FocusedGroup (used in the Fixed dual-y-axis legend) does not forward highlightedSubGroupIndex to <Entries>, so hover-dimming silently doesn't work in that layout variant.
  • P2 (pluto/src/vis/axis/ticks.ts): formatRelativeTime can produce "X.1000s" when the millisecond rounding overflows (e.g. totalMs = 1999.5ms = 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 relativeTime after 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) and pluto/src/lineplot/Legend.tsx (FocusedGroup missing highlightedSubGroupIndex).

Important Files Changed

Filename Overview
console/src/lineplot/LinePlot.tsx Adds vis.align to the axis-type effect deps and removes the usePrevious guard; x-axis type becomes permanently stuck as "relativeTime" when ranges change away from static-only because the effect doesn't depend on vis.ranges.x1.
console/src/lineplot/buildLines.ts Adds timeOffset, subGroup, and subGroupIndex to each line props; uses range.timeRange.start directly as the nanosecond offset for aligned static ranges.
console/src/lineplot/toolbar/Data.tsx Adds the "Relative Time" toggle that is only shown when all selected ranges are static; dispatches setAlign.
pluto/src/vis/axis/ticks.ts Adds relativeTime tick type, formatRelativeTime formatter, and RelativeTimeTickFactory; millisecond carry case can produce malformed labels like "1.1000s".
pluto/src/lineplot/Legend.tsx Adds SubGroupKey component and sub-group hover logic; FocusedGroup in the Fixed multi-y layout omits highlightedSubGroupIndex, so dimming is skipped there.
pluto/src/vis/line/aether/line.ts Adds timeOffset and subGroupIndex to line state; correctly applies offset to x-bounds, find-by-value, and GL transform.
pluto/src/channel/LinePlot.tsx Extracts rangeIsIncomplete/incompleteRangeTelemSpec helpers (with tests), adds timeOffset/subGroup/subGroupIndex to BaseLineProps, and switches to a proper streaming telem source for incomplete static ranges.
pluto/src/lineplot/tooltip/aether/tooltip.ts Renders elapsed-time header and sub-group index prefix in the tooltip when xAxisTickType === "relativeTime".
pluto/src/vis/render/context.ts Enables GL alpha blending globally so dimmed lines (alpha 0.15) render correctly.
console/src/lineplot/types/v4.ts Adds optional 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]
Loading

Comments Outside Diff (4)

  1. pluto/src/lineplot/Line.tsx, line 496 (link)

    P2 subGroup object instability triggers excessive setLine calls

    buildLines creates a fresh { key, name } object on every call (it is not memoized in the console LinePlot). Because useEffect uses Object.is for dependency comparison, subGroup always looks changed, so setLine is called on every render of each Line component. setLine unconditionally rebuilds the lines array ([...prev.filter(...), meta]), which updates the LinePlot context, re-rendering all Legend/tooltip consumers on every render cycle.

    Consider memoizing the subGroup value inside Line, or stabilizing it upstream in buildLines / the parent Channel.LinePlot.

  2. console/src/lineplot/LinePlot.tsx, line 25-27 (link)

    P2 relativeTime axis type applied to non-timestamp x-channels when align is true

    When vis.align = true and the x-axis channel is not a TIMESTAMP (e.g., a numeric sensor channel), newType is still set to "relativeTime" (both the initial assignment and the override branch). The relativeTime tick 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 has align: true and the user then switches to a non-timestamp x-channel. A guard like newType = "linear" when align is true but the channel is not a timestamp would be safer.

  3. console/src/components/Controls.css, line 20-27 (link)

    P1 Duplicate selector and lost transition animation

    The PR accidentally introduces a second .console-controls--annotations-visible block (lines 20–23) immediately before the pre-existing one (lines 25–27), and removes transition: opacity 0.1s ease-in-out; from the base .console-controls rule. The duplicate rule is harmless (same top: 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 sets right: 2rem, which is already inherited from .console-controls. It looks like the intent was simply to add right: 2rem to the existing rule, not create a new one. The result should be:

    .console-controls {
        position: absolute;
        top: 2rem;
        right: 2rem;
        z-index: 20;
        opacity: 0;
        transition: opacity 0.1s ease-in-out;
    }
    
    .console-controls--annotations-visible {
        top: 0rem;
        right: 2rem;
    }
  4. pluto/src/lineplot/tooltip/aether/tooltip.ts, line 808-814 (link)

    P1 maxLabelLength measured from validValues but width also covers the header row

    maxLabelLength is now computed from validValues (lines with a non-NaN result), but values (the full list passed to draw.list) may contain entries from invalid lines whose label is longer than any validValues label. 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):

    let maxLabelLength = values.reduce((p, c) => Math.max(p, c.label?.length ?? 0), 0);

    The new code uses validValues instead. Switch back to reducing over values to cover all rendered rows.

Reviews (5): Last reviewed commit: "SY-3942: Fix tooltip width clipping long..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

Comment thread pluto/src/vis/axis/ticks.ts
Comment thread pluto/src/lineplot/tooltip/aether/tooltip.ts Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 22, 2026

Codecov Report

❌ Patch coverage is 8.64865% with 169 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.91%. Comparing base (aefd1ec) to head (2d70ae6).
⚠️ Report is 3 commits behind head on rc.

Files with missing lines Patch % Lines
pluto/src/lineplot/Legend.tsx 5.66% 45 Missing and 5 partials ⚠️
pluto/src/vis/axis/ticks.ts 4.25% 34 Missing and 11 partials ⚠️
pluto/src/vis/line/aether/line.ts 0.00% 13 Missing and 2 partials ⚠️
pluto/src/lineplot/tooltip/aether/tooltip.ts 0.00% 6 Missing and 6 partials ⚠️
console/src/lineplot/buildLines.ts 0.00% 5 Missing and 3 partials ⚠️
console/src/lineplot/toolbar/Data.tsx 0.00% 6 Missing and 1 partial ⚠️
pluto/src/vis/legend/Entries.tsx 0.00% 6 Missing and 1 partial ⚠️
pluto/src/lineplot/Line.tsx 14.28% 4 Missing and 2 partials ⚠️
console/src/lineplot/LinePlot.tsx 0.00% 0 Missing and 4 partials ⚠️
pluto/src/channel/LinePlot.tsx 71.42% 2 Missing and 2 partials ⚠️
... and 7 more
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     
Flag Coverage Δ
console 20.33% <0.00%> (-0.02%) ⬇️
pluto 55.32% <9.75%> (-0.34%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@sy-nico sy-nico self-requested a review April 22, 2026 06:56
Base automatically changed from sy-3942-range-annotation-visibility to rc April 24, 2026 15:55
Copy link
Copy Markdown
Contributor

@sy-nico sy-nico left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ------->
image

image

@sy-nico
Copy link
Copy Markdown
Contributor

sy-nico commented Apr 28, 2026

Fixed. Plots now update when a range is incomplete.

FixedIncompleteRange_PlotUpdate.mp4

Comment thread pluto/src/lineplot/range/aether/provider.ts
Comment thread console/src/lineplot/LinePlot.tsx
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants