Skip to content

fix(chart): explicit loading and empty states with safe scaling - #1793

Open
bilhokista wants to merge 2 commits into
Arena1X:mainfrom
bilhokista:fix/1578-chart-empty-loading
Open

fix(chart): explicit loading and empty states with safe scaling#1793
bilhokista wants to merge 2 commits into
Arena1X:mainfrom
bilhokista:fix/1578-chart-empty-loading

Conversation

@bilhokista

Copy link
Copy Markdown

Closes #1578.

The bugs

Math.max() of an empty list is -Infinity, and that value flowed straight through the component:

const maxValue = Math.max(
  ...series.filter(s => visibleSeries.has(s.id)).flatMap(s => s.data.map(d => d.value))
);

With no data the y-axis rendered the literal text -Infinity, -Infinity, 0 over a full set of grid lines — a plot that reads as "the values are zero" rather than "we have no values". Three related failures came with it:

  • All-zero series. The max is a legitimate 0, so every bar height became (0 / 0) * 100NaN%.
  • Empty series and hover. Math.min(index, series[0]?.data.length - 1) evaluates undefined - 1NaN, which then poisoned every downstream index lookup.
  • Single point. The x-axis prints indices 0, floor(1/2) = 0, and 1 - 1 = 0 — the same label three times, presented as though it were a range.

There was also no loading state, and role="img" carried only aria-label={title}, so the data itself was unreachable to a screen reader (and the label was undefined when no title was passed).

The change

Confined to interactive-chart.tsx. skeleton.tsx and empty-state.tsx already existed and are used as-is — no changes needed there.

Three pure helpers are exported so the edge cases can be tested directly rather than through the DOM:

  • computeMaxValue(series) returns number | null, where null means "nothing to scale against". It filters non-finite values and treats a max of 0 as unscalable, so neither -Infinity nor NaN can reach a style attribute.
  • pickAxisLabels(points) returns 0–3 labels de-duplicated by position.
  • describeSeries(series, format) builds the plain-language summary: point count, span, range, and latest value, with separate wording for a single point so it does not imply a range.

Behaviour added:

  • Loading (isLoading prop) renders skeletons sized to the real layout, with aria-busy and a role="status" announcement.
  • Empty renders EmptyState, with emptyTitle / emptyDescription overridable.
  • Accessible summary appears as sr-only text and is folded into the plot's aria-label.
  • Bar heights are clamped to 0–100 as a belt-and-braces guard.

One deliberate design decision worth flagging: the empty state is keyed on the whole series prop, not on the visible subset. If it were keyed on maxValue, hiding the last series via the legend would replace the card with an empty state — taking away the very legend needed to turn it back on. When data exists but everything is hidden, the legend stays and an inline role="status" message explains why the plot is gone. That case is tested.

Tests

New interactive-chart.test.tsx, 21 cases: 6 on computeMaxValue (including the empty, all-zero, and non-finite traps), 4 on pickAxisLabels, 3 on describeSeries, and 8 rendering cases covering loading, empty, all-zero, custom wording, single-point, the a11y summary, and the hidden-legend case. The empty and single-point tests assert directly that no NaN or Infinity reaches a rendered height.

Honest note on verification: I could not run the frontend Vitest suite locally (no full workspace install). Both files were parsed with the TypeScript compiler API, and the three exported helpers were transpiled and executed standalone against 20 assertions — all passed, including a baseline check that Math.max(...[]) really is -Infinity, which is the root of the original bug. The eight rendering cases need CI.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CrfEY1tvXrbeMDAUzxfuk7

@vercel

vercel Bot commented Sep 10, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
insight-arena-4rll Ready Ready Preview Sep 10, 2026 1:58pm UTC

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.

[Frontend] — Interactive Chart Empty and Loading States

1 participant