Skip to content

feat(react-ui): add D3Charts alongside the Recharts charts - #1248

Merged
ankit-thesys merged 5 commits into
mainfrom
feat/react-ui-d3-charts
Sep 30, 2026
Merged

ankit-thesys merged 5 commits into
mainfrom
feat/react-ui-d3-charts

Conversation

@ankit-thesys

@ankit-thesys ankit-thesys commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

What

Adds the D3-based chart set to react-ui as components/D3Charts, next to the existing Recharts Charts/. This is the first step toward replacing the Recharts charts: the D3 charts take the same data contract (Array<Record<string, string | number>> + categoryKey / dataKey), so the genui-lib chart wrappers can switch over in a follow-up without prompt or schema changes. Nothing that exists today changes behaviour.

Changes

  • src/components/D3Charts/: Area, Line, Bar (vertical and horizontal), Pie, Radial, Radar, Scatter, Heatmap, Funnel, SegmentedBar, CalendarHeatmap and MiniArea / MiniBar / MiniLine, plus StackedLegend / LegendStoreProvider, with their unit tests (36 files, 349 tests). They use react-ui's own ThemeProvider, Button, IconButton, Separator and PrintContext through internal imports.

  • Class prefix: every class, keyframe and CSS variable is composed from CHART_CLASS_PREFIX / _prefix.scss, set to openui-d3. With plain openui, 65 class names would collide with the Recharts charts (legend, tooltip, scroll buttons, containers), because both stylesheets ship in components.css. When Recharts is removed, switching to openui is a two-line change. classPrefix.test.ts fails if anything hardcodes the prefix.

  • Scroll buttons: their rules are nested under the buttons container (specificity 0,2,0), so they win over IconButton's single-class rules whatever order the stylesheets load in. The generated components/index.scss forwards D3Charts before IconButton.

  • PPTX export: charts emit data-openui-chart, the same contract as the Recharts charts.

  • Defaults match the Recharts charts, so the D3 chart renders what the Recharts one does for the same props:

    • Stacked areas keep one running total per row (Recharts' default stackOffset), so a negative value lowers the stack rather than dropping below zero. Bars keep stacking by sign, as Recharts' stackOffset="sign" does.
    • Pie and Radial get legendVariant ("default" | "stacked", default "stacked"): a built-in stacked legend with each item's share, side by side from 400px wide and below the chart when narrower. An external legendKey still takes over.
    • SegmentedBar defaults to the compact legend, as SingleStackedBar does.
    • Radar gets variant ("line" | "area", default "line") and areaOpacity (default 0.2), dots are off by default, and the grid is solid.
  • X-axis labels (Area, Line, Bar): the chart keeps its height whatever the labels are. The label band takes at most half of it, and labels that don't fit are truncated with an ellipsis, with the full text on hover.

    • Condensed charts default to one horizontal line of labels, as the Recharts condensed charts do.
    • tickVariant="angled" is now accepted: it rotates labels 45° when they don't fit horizontally.
    • When a category is too narrow for a readable label, only every n-th label is drawn.

    This deliberately differs from Recharts, which makes the chart taller to fit tilted labels (668px in the big-labels story) and so breaks fixed-height layouts such as grids.

  • Mini charts: MiniAreaChart, MiniBarChart, MiniLineChart take the Recharts minis' props and match them point for point: the same most-recent-values-that-fit rule, square frame, gradient area, right-aligned 8px bars with the inner line, and 2px line. That includes Recharts' own rounding of the value range (recharts-scale), which rounds higher than d3's .nice() and shows directly as mark height without an axis. Unit tests pin coordinates measured from the Recharts minis.

  • Stories: every Recharts chart story (124 stories in 15 files) is copied under Components/D3Charts/…, changing only the chart import and the title. The import points at stories/rechartsStoryAdapters, which exports the Recharts chart names typed with the Recharts props and renders the D3 replacement. So each story renders the D3 chart with its args unchanged, and every copy type-checks against the Recharts props. This adds roughly 15k lines of copied story code; stories aren't part of the build.

  • Dependencies: d3-scale, d3-selection, d3-shape (+ @types). The lockfile diff contains only these entries.

  • tsconfig.build.json: excludes colocated *.test.ts(x). They were being compiled into dist/, and vitest then ran the compiled copies too. This also affects the existing Charts/shared/LineInBarShape test.

  • Exports: not added to the root index.ts. The charts are reachable through the existing ./* subpath (import { AreaChart } from "@openuidev/react-ui/D3Charts"), and their styles are in components.css and styles/d3Charts.css.

  • Changeset: @openuidev/react-ui patch.

Test Plan

  • pnpm install --frozen-lockfile, pnpm run build:packages and pnpm --filter "./packages/**" run ci all pass. Lint output is warnings only, all of which already exist on main; none are in D3Charts. tsc --noEmit is clean with both tsconfig.json and tsconfig.test.json.

  • react-ui vitest: 395 tests pass, 349 of them new.

  • Storybook parity (QA apps/visualization-bench, storybook project): each of the 124 Recharts stories is paired with its D3 copy. There are no page or console errors on the D3 side, marks are drawn wherever Recharts draws them, no Area/Bar/Line plot is squeezed below half its chart's height, and the legend and series names match in every story. Screenshots of both sides were reviewed.

  • Recharts parity (QA apps/visualization-bench, /parity): 19 Recharts/D3 pairs in light and dark, fed the props react-ui's genui-lib wrappers pass. Both sides render with no errors and show the same categories and series in the same order. All 19 pairs pass, and the Mini charts now have D3 counterparts.

  • CSS parity: the compiled d3Charts.css is identical to the original implementation's stylesheet apart from the prefix. I rendered 15 cases in Storybook and in the original implementation with the same props: all 11 charts, plus a 40-point scrolling line chart, a 14-series overflowing legend and a 16-slice pie with StackedLegend. Computed styles and boxes of all 1,622 elements match in both light and dark mode.

  • Load order: I audited every chart element that is also a react-ui Button, IconButton or Separator in default, hover and disabled states, comparing the bundled order with chart CSS loaded last. The only value that still depends on order is the background of disabled scroll buttons, which are visibility: hidden.

  • browser-bundle: none of the D3Charts JS ends up in openui-bundle.min.js. openui-styles.css grows by about 28 KB because it now includes the D3 styles.

  • Not applicable (explain why)

  • Verified locally

Checklist

  • I linked a related issue, if applicable
  • I updated docs/README when needed: not yet; D3Charts gets documented when it replaces the Recharts charts
  • I considered backwards compatibility: additive only; the Recharts charts, genui-lib and the root exports are untouched

Follow-ups (not in this PR)

  • Differences the story copies make visible, not changed here:

    • named theme palettes (theme="emerald" etc.) are ignored, so colours come from the ThemeProvider palette;
    • Pie/Radial geometry: Recharts' donut ring is thinner, its first slice starts elsewhere, and its radial rings are thinner;
    • the scrollable BarChart narrows bars to fit many categories (Recharts keeps 16px and scrolls);
    • the Area/Line "step" variant uses step-after (Recharts steps at the midpoint);
    • SegmentedBar takes its palette ramp in order rather than distributed.
  • Point the genui-lib/Charts wrappers at D3Charts.

  • The Recharts Charts/ scroll buttons have the same load-order issue (their background resolves to transparent in components.css).

  • components/ChartsV2/ on main contains only a stale ARCHITECTURE.md.

🤖 Generated with Claude Code

Bring the D3-based chart set into react-ui as components/D3Charts:
Area, Line, Bar (vertical/horizontal), Pie, Radial, Radar, Scatter,
Heatmap, Funnel, SegmentedBar, CalendarHeatmap, plus StackedLegend and
the legend store. Same data contract as the Recharts charts
(Array<Record<string, string | number>> + categoryKey/dataKey), so the
genui-lib wrappers can switch over later without prompt changes.

- Classes compose from CHART_CLASS_PREFIX / _prefix.scss, set to
  `openui-d3` so they don't collide with the Recharts `openui-*` classes
  while both ship in components.css.
- Scroll-button rules are nested under their container so they beat
  IconButton's single-class rules regardless of stylesheet order.
- Only `data-openui-chart` is emitted for the PPTX exporter.
- Adds d3-scale, d3-selection, d3-shape.
- tsconfig.build.json excludes colocated *.test.ts(x) so tests are not
  emitted into dist (vitest was picking the dist copies up).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
openui-docs Ready Ready Preview Sep 29, 2026 12:53pm UTC

Request Review

- Stacked areas keep one running total per row (Recharts' default
  stackOffset): a negative value lowers the stack instead of dropping
  below zero. Bars keep stacking by sign (Recharts' stackOffset="sign").
  The stack offset is threaded through the stack, the y domain, the scale
  and the y-axis width measurement.
- Pie and Radial get legendVariant ("default" | "stacked", default
  "stacked"): the chart with a built-in stacked legend showing each item's
  share, side by side from 400px wide, below the chart when narrower. It
  reuses the legend store, so hover and toggle work both ways; an external
  legendKey still takes over.
- SegmentedBar defaults to the compact legend, as SingleStackedBar does.
- Radar gets variant ("line" | "area", default "line") and areaOpacity
  (default 0.2); dots are off by default; the grid is solid in the
  interactive border tone. fillOpacity still overrides.
- Stacked legend swatch boxes fit their row, so a list that fits no longer
  reads as overflowing (scroll arrows on five items).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… stories

- MiniAreaChart, MiniBarChart, MiniLineChart: the Recharts minis' props
  and look — the most recent values that fit, square frame (size, min
  100px), area gradient / 1.5px line, 8px right-aligned bars with rounded
  tops and the inner line, 2px line. Geometry matches the Recharts minis
  point for point, including recharts-scale's 5-tick "nice" value range
  (it rounds above d3's .nice(), and without an axis that shows as mark
  height). Unit tests pin the measured Recharts coordinates.
- Stories: every Recharts chart story (124 in 15 files) copied under
  Components/D3Charts/… with only the chart import and the title changed.
  The import points at stories/rechartsStoryAdapters, which exports the
  Recharts chart names typed with the Recharts props and renders the D3
  replacement, so each story renders the D3 chart with its args unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Condensed Area/Bar/Line charts always angled their x-axis labels and took
the label height out of the chart's fixed height, so long category names
left no room for the plot (flat lines, no bars).

The chart height now stays fixed and the label band never takes more than
half of it; labels that don't fit are truncated with an ellipsis and show
their full text on hover. Condensed charts default to single-line
horizontal labels like the Recharts condensed charts, `tickVariant="angled"`
rotates them 45° when they don't fit, and only every n-th label is drawn
when categories are too narrow to read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@i-subham23 i-subham23 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks Good

This branch was successfully deployed

1 active deployment
Preview — b7bc12da Deployed Sep 29, 2026 by vercel[bot]
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