Skip to content

feat: highlight the series nearest the cursor in time chart tooltips#2456

Open
alex-fedotyev wants to merge 4 commits into
mainfrom
alex/HDX-4531-tooltip-highlight-nearest-series
Open

feat: highlight the series nearest the cursor in time chart tooltips#2456
alex-fedotyev wants to merge 4 commits into
mainfrom
alex/HDX-4531-tooltip-highlight-nearest-series

Conversation

@alex-fedotyev

Copy link
Copy Markdown
Contributor

Multi-series Line and Area chart tooltips list every series at the hovered time. When a Group By produces many series, it is hard to tell which tooltip row maps to which line. This bolds the row for the series whose line is nearest the cursor and updates it as the pointer moves up and down, so a row is easy to trace back to its line.

HDX-4531

Summary

In the shared tooltip for HDXMultiSeriesTimeChart, the row for the series nearest the cursor renders at font-weight: 600 (the same weight the legend uses for a selected series). The highlighted row updates as the pointer moves vertically and clears when the pointer is farther than a small threshold from every line, so nothing is emphasized in empty space.

The nearest series is found in pure pixel space. Each Area's active dot records its active-point pixel Y, and the tooltip compares those positions against the cursor's pixel Y (coordinate.y). No axis-scale reconstruction is involved, which matters because the y domain is often [0, 'auto'].

Changes

  • ChartUtils.tsx: new pure helper findNearestSeriesKey(seriesYByKey, candidateKeys, pointerY, maxDistancePx) that returns the nearest series' key within the threshold, or undefined. Isolated so the geometry is unit-testable without rendering Recharts.
  • HDXMultiSeriesTimeChart.tsx: a small capturing active dot on the <Area> branch records each series' active-point pixel Y into a per-chart ref, keyed by dataKey. It still draws the same dot Recharts renders by default. The tooltip reads the ref to bold the nearest row. Highlighting only applies when more than one series is shown.
  • ChartTooltip.tsx: ChartTooltipItem takes an optional highlighted prop that sets the row weight to 600. Backward compatible; existing callers are unaffected.

Why a ref read during render

Recharts renders the graphical items and their active dots before it renders the tooltip in the same commit, so the captured dot positions are current when the tooltip reads them. Both the capture write and the tooltip read touch a ref during render; this mirrors the existing pattern in TimelineMouseCursor and TimelineXAxis, and the react-hooks/refs disable is annotated with the reason.

Out of scope

Stacked bar uses activeBar rather than activeDot, and vertical stacking makes "nearest line" meaningless, so it is intentionally untouched. Single-series charts have nothing to disambiguate, so highlighting is skipped there. Both cases were verified to render with no bold row.

Test plan

  • yarn lint:fix, make ci-lint
  • make ci-unit, with new unit tests for findNearestSeriesKey: nearest within threshold, none within threshold, undefined pointer, undefined map, empty candidate list, candidate absent from the map skipped, tie resolves to the first candidate, inclusive boundary at maxDistancePx
  • Manual in Chart Explorer, a Logs Line chart grouped by ServiceName with several series:
    • hovering bolds exactly one row, the series nearest the cursor
    • moving the pointer down moves the bold through the lines in order
    • nothing bolds when the pointer is far above every line or below the baseline
    • confirmed in both light and dark themes
    • stacked-bar tooltip shows no bold row; single-series tooltip shows no bold row

Before this change every tooltip row rendered at the same weight. Before/after captures in light and dark are ready to attach.

[ui-states: allow]
[viewport: allow]
[no-story: allow]

alex-fedotyev and others added 3 commits June 12, 2026 03:17
In a multi-series Line/Area chart, the shared tooltip lists every
series, so it is hard to tell which row maps to which line when a
Group By produces many series. Bold the row for the series whose line
is nearest the cursor, updating as the pointer moves vertically and
clearing emphasis when the pointer is far from every line.

Each Area active dot records its active-point pixel Y into a per-chart
ref keyed by dataKey. The tooltip compares the cursor pixel Y
(coordinate.y) against those captured positions to pick the nearest
series. The comparison is pure pixel space (no axis-scale math), and
nothing is highlighted beyond a small distance threshold. Stacked bar
and single-series charts are unaffected.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Touched these comments in the same files; replace the em-dashes with
plain punctuation to match the rest of the codebase.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
A single-series tooltip has nothing to map back to a line, so skip the
nearest-series highlight when the tooltip has one row. Keeps the
single-series and stacked-bar cases visually unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@changeset-bot

changeset-bot Bot commented Jun 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 3f84570

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@hyperdx/app Patch
@hyperdx/api Patch
@hyperdx/otel-collector Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercel Bot commented Jun 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hyperdx-oss Ready Ready Preview, Comment Jun 12, 2026 6:50pm
hyperdx-storybook Ready Ready Preview, Comment Jun 12, 2026 6:50pm

Request Review

@greptile-apps

greptile-apps Bot commented Jun 12, 2026

Copy link
Copy Markdown

Greptile Summary

Adds visual emphasis to the tooltip row and line corresponding to the series whose active-dot pixel Y is nearest the cursor in HDXMultiSeriesTimeChart. The implementation uses a shared mutable ref that CaptureActiveDot writes during Recharts' graphical-item render pass and the tooltip reads in the same commit, avoiding any axis-scale reconstruction.

  • findNearestSeriesKey (ChartUtils.tsx): New pure helper that compares pointer Y against per-series captured pixel Ys; fully unit-tested including boundary, tie-break, and missing-data cases.
  • CaptureActiveDot (HDXMultiSeriesTimeChart.tsx): Minimal custom active-dot element that records cy into a shared ref and renders the same <circle> Recharts would; MemoChart uses this for every <Area>, computes nearestSeriesKey state from onMouseMove, and applies strokeWidth/strokeOpacity to emphasize the nearest line.
  • ChartTooltipItem (ChartTooltip.tsx): Backward-compatible highlighted/dimmed props that set fontWeight: 600 or opacity: 0.5 on the row; the tooltip component derives its own nearestSeriesKey locally from the current payload, independent of state.

Confidence Score: 5/5

Safe to merge; the change is additive and confined to visual emphasis — it does not affect data fetching, charting data, or any path that existed before.

The nearest-series logic is pure and fully unit-tested. Guard conditions correctly prevent emphasis on single-series or empty-space scenarios. The stale-ref edge case was already flagged in a prior review and is a minor UX inconsistency rather than a data or correctness issue; the tooltip derives its nearest key independently from the current payload.

No files require special attention for merge safety.

Important Files Changed

Filename Overview
packages/app/src/ChartUtils.tsx Adds findNearestSeriesKey pure helper; logic is correct, boundary condition (inclusive at maxDistancePx) is intentional and tested.
packages/app/src/HDXMultiSeriesTimeChart.tsx Adds CaptureActiveDot, nearestSeriesKey state, and tooltip/line emphasis wiring; the ref is never cleared between hovers, so stale entries for hidden series can cause the onMouseMove-driven line emphasis to diverge from the tooltip's locally-computed nearest (already flagged in a prior review comment).
packages/app/src/components/charts/ChartTooltip.tsx Backward-compatible highlighted/dimmed props wired correctly; style logic handles both, neither, and one-of-each cases cleanly.
packages/app/src/tests/ChartUtils.test.ts Comprehensive unit tests for findNearestSeriesKey covering all documented edge cases including boundary, tie-break, undefined inputs, and absent candidates.
.changeset/tooltip-highlight-nearest-series.md Patch-level changeset entry for @hyperdx/app; description is accurate.

Sequence Diagram

sequenceDiagram
    participant RC as Recharts Engine
    participant CAD as CaptureActiveDot
    participant Ref as activePointYByKeyRef
    participant TT as HDXLineChartTooltip
    participant FN as findNearestSeriesKey

    RC->>CAD: render(cx, cy, dataKey) for each visible series
    CAD->>Ref: captureRef.current.set(dataKey, cy)
    RC->>TT: render(payload, coordinate.y)
    TT->>Ref: read activePointYByKeyRef.current
    TT->>FN: findNearestSeriesKey(ref, typedPayload keys, coordinate.y, 30px)
    FN-->>TT: nearestSeriesKey (or undefined)
    TT-->>RC: ChartTooltipItem(highlighted / dimmed per row)

    Note over RC,Ref: onMouseMove (prior frame)
    RC->>Ref: read activePointYByKey
    RC->>FN: findNearestSeriesKey(ref, all ref keys, chartY, 30px)
    FN-->>RC: nextNearest
    RC->>RC: setNearestSeriesKey (only if changed)
    RC-->>RC: Area strokeWidth/strokeOpacity emphasis
Loading

Reviews (2): Last reviewed commit: "feat: emphasize the nearest series' line..." | Re-trigger Greptile

@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

E2E Test Results

All tests passed • 197 passed • 3 skipped • 1381s

Status Count
✅ Passed 197
❌ Failed 0
⚠️ Flaky 6
⏭️ Skipped 3

Tests ran across 4 shards in parallel.

View full report →

Bolding the tooltip row alone still leaves the eye hunting for which
line it maps to. Lift the nearest-series key into chart state, computed
on mouse-move from the pixel Y the active dots already capture, and use
it to thicken the nearest line to 2.5px while fading the others to 50%
stroke opacity. The tooltip mirrors this: the nearest row stays bold and
the rest dim to 50%. Only applies with more than one line shown, and is
cleared on the click-frozen tooltip path, matching the existing scope.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🔵 Tier 2 — Low Risk

Small, isolated change with no API route or data model modifications.

Why this tier:

  • Standard feature/fix — introduces new logic or modifies core functionality

Review process: AI review + quick human skim (target: 5–15 min). Reviewer validates AI assessment and checks for domain-specific concerns.
SLA: Resolve within 4 business hours.

Stats
  • Production files changed: 3
  • Production lines changed: 219 (+ 57 in test files, excluded from tier calculation)
  • Branch: alex/HDX-4531-tooltip-highlight-nearest-series
  • Author: alex-fedotyev

To override this classification, remove the review/tier-2 label and apply a different review/tier-* label. Manual overrides are preserved on subsequent pushes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automerge review/tier-2 Low risk — AI review + quick human skim

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant