☰
OpenUI ChartsV2 架构全解:基于 D3 数学内核与 React Hook 编排的下一代图表系统
2026/9/25 2:35:35 网站建设 项目流程

【免费下载链接】openui

The Open Standard for Generative UI

项目地址:https://gitcode.com/gh_mirrors/openui1/openui
点击查看免费下载

ChartsV2 是 OpenUI React 组件库(packages/react-ui)内基于 D3 构建的图表体系,它用「D3 负责数学计算、React 负责 DOM、CSS 负责动画」的职责分离思路取代了原先基于 Recharts 的Charts/包。本文以仓库内维护的架构文档(ARCHITECTURE.md)为骨架,完整拆解其 hook 编排架构、三类拓扑、共享组件分层、样式与类型系统、依赖规则及已知风险,帮助你在理解其设计后快速上手、扩展甚至迁移该图表系统。

概览:为什么要用 D3 + React 重写图表系统

ChartsV2 是一个位于 OpenUI 组件库packages/react-ui/src/components/ChartsV2/目录下的 D3 图表系统,对外提供7 种图表类型,覆盖3 种拓扑:

拓扑图表坐标系特征
Cartesian(可滚动)Area、Bar、LineX/Y 直角坐标,数据可横向溢出滚动
Cartesian(紧凑)Area、Bar、Line 的 condensed 模式X/Y 直角坐标,全部数据压缩进容器宽度
Polar(分类)Pie、Radial极坐标、单序列分类数据
Polar(雷达)Radar极坐标、多序列数据
散点Scatter数值型 X/Y 双轴

它最核心的设计原则是:

D3 严格用于数学计算(scale 刻度、path 路径生成、stack 堆叠),React 通过 JSX 完全拥有 DOM。

这意味着代码里不会出现d3.select().append()这类与 React 虚拟 DOM 直接冲突的操作——所有 SVG 元素都由 JSX 渲染,D3 只负责输出坐标、路径与数据转换结果。架构文档明确说明,该体系用于取代原先基于 Recharts 的Charts/包。这一点在仓库中可以交叉验证:旧包入口 Charts.tsx 第 4 行即import * as RechartsPrimitive from "recharts",且 package.json 中同时声明了recharts@^2.15.4、@floating-ui/react-dom@^2.1.2与@radix-ui/react-tooltip@^1.2.7,分别对应旧图表库、ChartsV2 的门户 Tooltip 与轴标签 Tooltip 的底层依赖。

整个系统的灵魂是hook 编排(hook-orchestrated)架构:每种图表把全部共享状态管理(数据、尺寸、悬停、滚动、图例、Tooltip)委托给一个独立的「编排器(orchestrator)」hook,图表组件自身只提供图表专属的渲染部分(序列几何、十字线、坐标轴变体)。

系统全景图与数据流

架构文档给出了一张完整的系统层级图,清晰地展示了从公共 API 到共享底层的调用关系:

ChartsV2/index.ts (public API) | +----------+-----------+-----------+----------+----------+ | | | | | | D3AreaChart D3BarChart D3LineChart D3PieChart D3RadialChart D3RadarChart D3ScatterChart | | | | | | | | Cartesian (X/Y) | Polar (categorical) Polar (radar) Scatter |__________|___________| |__________| | | | | | | +-----------+----------+ +----------+ +-------+ +-------+ | | | | | useChartScrollable useChartCondensed useCategorical useRadarChart useScatterChart Orchestrator Orchestrator ChartOrchestrator Orchestrator Orchestrator | | | | | +----------+-----------+ +-------+----------+ | | | | hooks/cartesian/ hooks/polar/ hooks/cartesian/ | | | hooks/core/ <--- shared by all chart types ------> hooks/core/ | utils/ + types/ | shared/core/ <--- all chart types shared/cartesian/ <--- Area, Bar, Line, Scatter

最复杂路径的数据流:Cartesian Scrollable

以可滚动折线/面积/柱状图为例,数据在组件中的流转分为三个阶段:

Props (data, categoryKey, theme, variant, stacked, ...) | v [1] useChartScrollableOrchestrator |-- useChartData --> dataKeys, colors, hiddenSeries, toggleSeries, legendItems, chartConfig, colorMap |-- useChartDimensions --> containerWidth, yAxisWidth, xAxisHeight, chartInnerHeight, svgWidth, needsScroll |-- useChartHover --> hoveredIndex, mousePos, createMouseHandlers(findIndex) |-- useChartScroll --> canScrollLeft, canScrollRight, handleScroll, scrollTo |-- useTooltipPayload --> tooltipPayload | null | v [2] Chart-specific hooks (in the chart component, not the orchestrator) |-- useXScale / useXBandScale --> xScale (D3 ScalePoint or ScaleBand) |-- useYScale --> yScale (D3 ScaleLinear) |-- useStackedData --> stackedData | null (D3 stack generator) | v [3] ScrollableChartLayout (shared layout component) |-- YAxis (separate fixed SVG) |-- Scrollable container with main SVG | |-- Grid (horizontal lines from yScale.ticks()) | |-- [Chart-specific Series] renders SVG paths/rects using scales | |-- [Chart-specific Crosshair/Hover indicator] | |-- XAxis (foreignObject labels from scale.domain()) |-- ScrollButtonsHorizontal (snap navigation) |-- DefaultLegend (expand/collapse, series toggle) |-- ChartTooltip (portal via @floating-ui, positioned at mouse viewport coords)

这条链路体现了整个体系的关键分工:第 1 阶段的编排器 hook 汇总所有共享状态;第 2 阶段由图表组件创建 D3 比例尺与堆叠数据;第 3 阶段交给共享布局组件完成 SVG 结构拼装。图表组件自身极其「薄」,只负责「数据怎么画」。

模块地图(Module Map)

架构文档按职责对全部模块做了归档,这是理解代码组织的第一张地图:

模块职责关键依赖
types/common.ts基础类型:ChartData、BaseChartProps、LegendItem、XAxisTickVariantpaletteUtils(PaletteName)
utils/dataUtils.ts提取数据键、构建 chart config、图例项、颜色查找types
utils/paletteUtils.ts6 套颜色调色板(每套 11 色)、useChartPalettehook、颜色分布ThemeProvider
utils/mouseUtils.tsfindNearestDataIndex(point scale)、findBandIndex(band scale)d3-scale
utils/scrollUtils.ts数据宽度计算、snap 位置、密度间距--
utils/styleUtils.tsnumberTickFormatter(K/M/B/T 缩写)、measureYAxisWidth(基于刻度的轴宽测量)--
utils/polarUtils.ts极坐标辅助:排序、slice 悬停样式、百分比格式化、雷达角度types
utils/buildContainerStyle.ts合并图表 CSS 变量与固定宽/高覆盖--
hooks/core/与坐标系无关、所有图表共享的 hookd3-selection, ThemeProvider
hooks/cartesian/笛卡尔专属:编排器、比例尺、坐标轴、滚动、堆叠d3-scale, d3-shape, core hooks
hooks/polar/极坐标专属:分类编排器、雷达编排器、雷达悬停core hooks
shared/core/拓扑无关、所有图表共用的组件:图例、Tooltip、截断 hook@floating-ui, @radix-ui
shared/cartesian/笛卡尔专属组件:坐标轴、网格、裁剪、十字线、布局、滚动按钮、基础 SCSS@floating-ui, lucide-react
D3AreaChart/面积图:渐变填充、堆叠/非堆叠、natural/linear/step 曲线hooks, shared, d3-shape
D3BarChart/柱状图:分组/堆叠、柱圆角、内部参考线、band scalehooks, shared, d3-scale
D3LineChart/折线图:点显示、natural/linear/step 曲线hooks, shared, d3-shape
D3PieChart/饼图/环形图:圆形/半圆形、角半径、padding 角度hooks, shared, d3-shape
D3RadialChart/径向柱状图:圆形/半圆形、比例弧条hooks, shared, d3-shape
D3RadarChart/雷达图:多边形/圆形网格、多序列叠加、轴标签hooks, shared, d3-scale
D3ScatterChart/散点图:数值 X/Y、多数据集、2D 最近点悬停hooks, shared, d3-scale

Hook 编排架构:三层 Hook 系统

所有 hook 按照「是否依赖坐标系」被分成三层,上层可以依赖下层,下层绝不可反向依赖:

hooks/ core/ -- coordinate-agnostic (all chart types) useChartData -- multi-series data: keys, colors, hidden series, legend (delegates to useSeriesVisibility) useCategoricalChartData -- single-series categorical: slices, totals, percentages useChartHover -- hover state + createMouseHandlers factory (1D: mouseX) useContainerSize -- ResizeObserver with fixed-size bypass useLegendHeight -- ResizeObserver on legend element usePrintContext -- matchMedia("print") detection useSeriesVisibility -- toggle hidden series with "keep at least one" guard (shared by useChartData + scatter) useTooltipPayload -- builds tooltip content from hovered row useTransformedKeys -- stable tk-N IDs for CSS vars (ref-cached) useCanvasContextForLabelSize -- memoized canvas 2D context for text measurement (SSR-safe stub) cartesian/ -- X/Y charts only useChartScrollableOrchestrator -- composes: data + dimensions + hover + scroll + tooltip useChartCondensedOrchestrator -- composes: data + hover + tooltip (angled labels, no scroll) useScatterChartOrchestrator -- composes: useSeriesVisibility + useChartPalette + measureYAxisWidth (numeric X/Y, 2D hover) useChartDimensions -- all layout math: sizing, axis, scroll detection useChartScroll -- scroll state + snap navigation useXScale -- D3 scalePoint (area, line) useXBandScale -- D3 scaleBand (bar) useYScale -- D3 scaleLinear (all cartesian) useStackedData -- D3 stack generator (area stacked, bar stacked) useXAxisHeight -- canvas-based label height calculation (useMemo) useYAxisWidth -- Canvas measurement of tick width useMaxLabelWidth -- Canvas measurement of category labels useAutoAngleCalculation -- Trig-based label rotation for condensed mode polar/ -- Pie, Radial, Radar useCategoricalChartOrchestrator -- composes: categorical data + container + legend + hover useRadarChartOrchestrator -- composes: chart data + radial scale + container + hover useRadarHover -- hover state + createMouseHandlers factory (2D: mouseX, mouseY)

Orchestrator Pattern:编排器即「超级 Hook」

每个编排器都是一个组合多个底层 hook 的「mega-hook」,返回一个结构统一的结果对象。编排器的返回值被刻意分为固定区块:

return { refs: { containerRef, legendRef, [mainContainerRef] }, identity: { chartId }, data: { catKey, dataKeys, colorMap, chartConfig, ... }, dimensions: { containerWidth, chartInnerHeight, totalHeight, ... }, hover: { hoveredIndex, mousePos, createMouseHandlers | handleMouseMove }, scroll: { canScrollLeft, canScrollRight, handleScroll, scrollTo }, // cartesian scrollable only legend: { legendItems, hiddenSeries, toggleSeries, isLegendExpanded, setIsLegendExpanded }, tooltip: { tooltipPayload }, style: { containerStyle }, };

这一模式带来的直接结果是:图表组件非常「薄」——它们解构编排器结果,创建图表专属的比例尺,然后把所有内容传给共享布局组件即可完成渲染。新增一个笛卡尔图表时,架构文档估计只需约 100 行图表专属代码,因为编排器已承载约 80% 的逻辑。

七种图表的分类实现

笛卡尔图表(Area / Bar / Line):一入口双模式

每种笛卡尔图表都由3 个文件构成:

  • D3[Type]Chart.tsx—— 空状态守卫 + condensed/scrollable 路由分发
  • D3[Type]ChartCondensed.tsx—— 使用useChartCondensedOrchestrator+CondensedChartLayout
  • D3[Type]ChartScrollable.tsx—— 使用useChartScrollableOrchestrator+ScrollableChartLayout

BaseChartProps中的condensed属性决定走哪条分支:

  • condensed 模式:所有数据点适配进容器宽度,X 轴标签倾斜显示(配合useAutoAngleCalculation基于三角函数的旋转角度计算),无滚动;
  • scrollable 模式(默认):每个数据点占据固定间距,数据溢出时启用横向滚动与 snap 导航。

比例尺选择规则也是固定的:

  • Area/Line 使用useXScale(D3scalePoint)—— 点居中于组宽度内;
  • Bar 使用useXBandScale(D3scaleBand)—— 带 padding 的离散带。

极坐标图表(Pie / Radial):共享分类编排器

两者都使用useCategoricalChartOrchestrator,它内部包装useCategoricalChartData,共同拥有:

  • 单序列数据模型(categoryKey+dataKey);
  • 基于 slice 的悬停(每个元素各自的onMouseMove);
  • 约束在 min/max 范围内的响应式尺寸;
  • 半圆形外观选项。

两者的差异仅在几何生成上:Pie 额外使用 D3 的pie()+arc()生成器,支持环形图变体(内半径)、padding 角度与角半径;Radial 使用比例弧条(每个 slice 即一环)与径向网格。

雷达图(Radar):极坐标上的多序列

Radar 使用useRadarChartOrchestrator,内部包装useChartData(多序列感知),它的独特之处在于:

  • 多序列数据运行在极坐标系上;
  • 多边形/圆形网格带辐条;
  • 轴标签通过三角函数定位;
  • 通过useRadarHover实现 2D 悬停(基于角度的最近轴检测)。

散点图(Scatter):唯一的数据模型特例

Scatter 使用useScatterChartOrchestrator,它组合共享 hook(useSeriesVisibility、useChartPalette、useContainerSize、useLegendHeight、usePrintContext、useCanvasContextForLabelSize)与共享工具measureYAxisWidth。D3ScatterChart按项目约定使用forwardRef+displayName,并渲染共享组件(Grid、VerticalGrid、YAxis、DefaultLegend、ChartTooltip)。它的独特之处在于:

  • 数据模型不同:使用ScatterDataset[]({name, data: ScatterPoint[]})而非ChartData;
  • X/Y 双轴均为数值轴(都是scaleLinear);
  • 2D 最近点悬停,吸附半径 30px;
  • 同时渲染横向与纵向网格(Grid与VerticalGrid均来自shared/cartesian/);
  • 不继承BaseChartProps,拥有独立 props 接口;
  • 没有 condensed/scrollable 分支(连续轴数据天然总是能全部放下,无需滚动)。

共享组件架构:按拓扑分层

shared/目录与hooks/的 core/cartesian/polar 分层一一对应:

shared/ core/ -- used by ALL chart types DefaultLegend/ -- expand/collapse legend with series toggle LabelTooltip/ -- Radix tooltip for truncated axis labels PortalTooltip/ -- @floating-ui portal tooltip for chart data useIsTruncated.ts -- ResizeObserver-based text truncation detection cartesian/ -- used by Area, Bar, Line (+ Scatter for Grid/YAxis) axes/ -- XAxis, AngledXAxis, XAxisLabel, YAxis layouts/ -- ScrollableChartLayout, CondensedChartLayout ScrollButtonsHorizontal/ -- snap-scroll navigation arrows ClipDefs.tsx -- SVG clip-path definitions Grid.tsx -- horizontal grid lines from yScale VerticalGrid.tsx -- vertical grid lines from xScale (linear, used by scatter) LineDotCrosshair.tsx -- hover crosshair + active dot (line/area) chartBase.scss -- SCSS mixins (chart-base, crosshair-styles) index.ts -- barrel re-exports from both tiers

架构文档还规定:当第一个被 2+ 种极坐标图表共享的可复用组件出现时,才创建shared/polar/层。

shared/ 组件的放置规则

条件放置位置
所有图表拓扑共用(笛卡尔 + 极坐标)shared/core/
仅 2+ 种笛卡尔图表使用shared/cartesian/
仅 2+ 种极坐标图表使用shared/polar/(首次需要时创建)
跨拓扑但不是全部shared/core/(倾向 core)
仅 1 种图表使用该图表的parts/目录

关键约定:组件永远从parts/起步,只有当第二种图表也需要它时,才「毕业」升级到shared/。这保证了共享层不会过早抽象。

布局组件:基于 slot 的渲染模板

ScrollableChartLayout与CondensedChartLayout(位于shared/cartesian/layouts/)是笛卡尔图表仅有的两个渲染模板,它们接收:

  • 编排器结果(类型为ReturnType<typeof useChart[Scrollable|Condensed]Orchestrator>);
  • 一个用于 Y 轴的yScale;
  • 鼠标事件处理器(由orch.hover.createMouseHandlers创建);
  • slot props:defs、series、xAxis(图表专属的 SVG 内容)。

这种slot 式注入让图表只提供自己独有的 SVG 元素,而布局、坐标轴、网格、图例与 Tooltip 渲染全部复用。

Tooltip 系统:门户与轴标签双轨

系统同时存在两套 Tooltip 机制:

  1. ChartTooltip—— 基于@floating-ui/react-dom的门户式数据 Tooltip。通过虚拟元素定位在鼠标视口坐标处,超过 10 条时截断为 5 条并提示 "Click to view all",最终 portal 到document.body并携带主题 class。
  2. LabelTooltip—— 基于 Radix UI 的轴标签截断提示,用LabelTooltipProvider包裹图表根节点。

仓库依赖层面可以直接印证这套双轨设计:package.json 中同时声明了@floating-ui/react-dom@^2.1.2与@radix-ui/react-tooltip@^1.2.7。

图例系统:宽度感知的智能换行

DefaultLegend是一个forwardRef+memo组件,具备以下行为:

  • 测量可用宽度,决定一行能放几个图例项;
  • 溢出时显示 "N more" 展开/折叠按钮;
  • 支持展开/折叠状态;
  • 隐藏序列以 0.3 透明度变暗;
  • 图例下方可选展示 X/Y 轴标签。

它通过useDefaultLegendhook 与 canvas 文本测量实现宽度感知的智能排布。

样式架构:SCSS 混入 + 设计令牌

SCSS Mixin 系统

shared/cartesian/chartBase.scss提供两个 SCSS mixin:

  • chart-base($prefix)—— 为指定图表前缀生成容器、内部、Y 轴容器、主容器、网格与刻度样式;
  • crosshair-styles($prefix)—— 生成十字线与激活点样式。

每个笛卡尔图表的 SCSS 文件都按相同模板引入:

@use "../shared/cartesian/chartBase" as base; @include base.chart-base("area-chart"); @include base.crosshair-styles("area-chart");

CSS 类名约定

所有类名遵循openui-d3-{chart-type}-{element}命名:

  • openui-d3-area-chart-container
  • openui-d3-bar-chart-hover-highlight
  • openui-d3-line-chart-line--animated

共享组件使用openui-chart-{component}:

  • openui-chart-legend-container
  • openui-chart-tooltip
  • openui-portal-tooltip

动画体系

每种图表定义各自的 CSS 动画,全部受isAnimationActiveprop 与usePrintContext控制(打印时禁用):

  • Area/Line:stroke-dasharray描边绘制(openui-d3-draw-line)+ 面积淡入;
  • Bar:自底部scaleY生长(openui-d3-bar-grow);
  • Pie:缩放 + 淡入(openui-d3-pie-appear);
  • Radial/Radar:淡入(openui-d3-radial-bar-appear、openui-d3-radar-polygon-appear);
  • Scatter:点出现(openui-d3-scatter-dot-appear)。

设计令牌

所有 SCSS 文件遵循 OpenUI 约定使用cssUtils令牌:

  • 颜色:cssUtils.$text-neutral-primary、cssUtils.$border-default、cssUtils.$foreground;
  • 间距:cssUtils.$space-m、cssUtils.$space-xs;
  • 字体:@include cssUtils.typography(label, extra-small);
  • 圆角:cssUtils.$radius-l、cssUtils.$radius-2xs;
  • 阴影:cssUtils.$shadow-s。

唯一的例外是paletteUtils.ts:图表数据颜色直接使用十六进制值而非设计令牌,因为它们是「数据可视化颜色」而非「UI 界面元素颜色」——这也是该模块在后面的风险清单中被点名的一个原因。

类型系统:数据模型与 Props 层级

数据模型

ChartData = Array<Record<string, string | number>> -- cartesian + radar + pie + radial ScatterDataset = { name: string; data: ScatterPoint[] } -- scatter only ScatterPoint = { x: number; y: number; [key]: string | number | undefined } D3ScatterChartData = ScatterDataset[]

Props 层级

BaseChartProps<T> -- shared by Area, Bar, Line |-- data, categoryKey, theme, customPalette |-- tickVariant, grid, legend, icons |-- isAnimationActive, showYAxis |-- xAxisLabel, yAxisLabel, className |-- height, width, fitLegendInHeight |-- condensed, density | +-- D3AreaChartProps<T> extends BaseChartProps + variant, stacked, onClick +-- D3BarChartProps<T> extends BaseChartProps + variant, barRadius, maxBarWidth, internalLine*, onClick +-- D3LineChartProps<T> extends BaseChartProps + variant, showDots, dotRadius, onClick D3PieChartProps<T> -- independent (single-series: categoryKey + dataKey) D3RadialChartProps<T> -- independent (single-series: categoryKey + dataKey) D3RadarChartProps<T> -- independent (multi-series: categoryKey, no dataKey) D3ScatterChartProps -- independent (dataset array, no categoryKey)

可以看到,BaseChartProps为三种笛卡尔图表提供了完全一致的 API 表面——这正是「图例、Tooltip、悬停行为跨图表一致」的类型保证;而 Pie/Radial/Radar/Scatter 因为数据模型不同,各自独立定义 props。

依赖规则:清晰的分层边界

允许的导入

D3[Chart]/ --> hooks/, shared/core/, shared/cartesian/, utils/, types/ hooks/cartesian/ --> hooks/core/, utils/, types/ hooks/polar/ --> hooks/core/, utils/, types/, shared/core/PortalTooltip (for TooltipItem type) hooks/core/ --> utils/, types/, ThemeProvider shared/core/ --> utils/, types/, ThemeProvider, hooks/ (for type inference only) shared/cartesian/ --> shared/core/, utils/, types/, ThemeProvider, hooks/ (for type inference only) shared/polar/ --> shared/core/, utils/, types/, ThemeProvider, hooks/ (for type inference only) utils/ --> types/, ThemeProvider (paletteUtils only) types/ --> utils/ (PaletteName only)

层级规则:shared/cartesian/可以导入shared/core/,但反过来绝对不行;shared/core/必须保持拓扑无关。

禁止的导入

  • 任何图表类型不得导入另一种图表类型;
  • ChartsV2 内任何文件不得导入旧版Charts/目录;
  • 任何 hook 不得导入组件(仅允许类型导入用于ReturnType推断);
  • hooks/core/不得导入hooks/cartesian/或hooks/polar/。

这些规则的意图非常明确:把「坐标系无关」与「坐标系相关」从 import 层面物理隔离,防止面积图与饼图之间、core 与 cartesian 之间出现意外的耦合。

五大设计模式

1. Orchestrator + Slot Layout(编排器 + 槽位布局)

最核心的组合模式。编排器负责全部共享状态,布局组件提供带槽位的 DOM 结构,图表注入专属 SVG 内容。新增图表的成本因此被压到最低。

2. Factory-Based Mouse Handlers(工厂式鼠标处理器)

useChartHover.createMouseHandlers(findIndex)是一个工厂函数,接受图表专属的「索引查找函数」,从而把悬停机制与比例尺类型解耦:

  • Area/Line 传入(mouseX) => findNearestDataIndex(xScale, mouseX)(最近点);
  • Bar 传入(mouseX) => findBandIndex(xScale, mouseX)(band 定位);
  • Radar 传入(mouseX, mouseY) => angleBasedIndex(mouseX, mouseY)(2D 角度)。

3. Canvas-Based Text Measurement(SSR 安全的 canvas 文本测量)

所有文本测量 hook(useXAxisHeight、useYAxisWidth、useMaxLabelWidth、useDefaultLegend)共用useCanvasContextForLabelSize:它创建一个以主题字体配置的、记忆化的CanvasRenderingContext2D。在 SSR 环境(typeof document === "undefined")下返回一个类型化 stub——measureText()返回零宽、但font字符串有效——这样parseLineHeight之类的字体解析逻辑在服务端仍能算出正确的行高。这既绕开了 DOM 测量的性能开销,又消除了布局抖动(layout thrashing)。

4. Condensed/Scrollable 二分路由

每种笛卡尔图表只有一个入口组件,根据condensedprop 路由到 Condensed 或 Scrollable 子组件。两种模式使用不同的编排器、不同的 X 轴组件(AngledXAxisvsXAxis)与不同的布局组件。

5. 序列可见性切换

共享的useSeriesVisibilityhook(位于hooks/core/)实现了「至少保留一个可见序列」约束:if (next.size >= seriesKeys.length - 1) return prev。它的消费方有三类:

  • useChartData(多序列笛卡尔 + 雷达)—— 将可见性委托给useSeriesVisibility;
  • useScatterChartOrchestrator—— 直接调用useSeriesVisibility;
  • useCategoricalChartData(单序列饼/径向)—— 因单序列模型不同,自带内联实现。

架构优势

  1. 关注点强分离:D3 做数学、React 做 DOM、CSS 做动画,没有d3.select().append()与 React 虚拟 DOM 打架的问题;
  2. 编排器带来的高复用:新增一个笛卡尔图表只需约 100 行图表专属代码,编排器承载约 80% 的逻辑;
  3. 一致的 API 表面:所有笛卡尔图表共享BaseChartProps,图例、Tooltip、悬停行为跨图表完全一致;
  4. 渐进增强:现代浏览器支持 SVGd属性的 CSStransition,老浏览器优雅降级为即时更新;
  5. 默认响应式:useContainerSize基于 ResizeObserver,配合自动滚动/紧凑适配;
  6. 打印感知:usePrintContext在打印时禁用全部动画,保证静态输出;
  7. 共享布局组件:ScrollableChartLayout与CondensedChartLayout消除了 3 种图表间的布局代码重复;
  8. 拓扑分层共享层:shared/core/+shared/cartesian/的划分与 hook 分层系统镜像对齐,清晰标明哪些组件拓扑无关,防止笛卡尔与极坐标关注点意外耦合。

已知风险与改进方向

架构文档对自身的短板做了坦诚的评估,按风险等级排序如下:

1. Scatter 结构差异(低风险,基本已解决)

D3ScatterChart已与其他图表共享核心基础设施(useSeriesVisibility、useChartPalette、measureYAxisWidth、共享的Grid/VerticalGrid/YAxis/DefaultLegend/ChartTooltip组件、forwardRef+displayName)。残留差异均由其根本不同的数据模型支撑:不继承BaseChartProps(使用ScatterDataset[])、无 condensed/scrollable 分支(连续轴数据总能放下)、Tooltip 内联构建(2 项:X/Y 值而非每行 N 个序列值)。文档特别指出,此前存在的 ThemeProvider 调色板绕过 bug 已解决——scatter 现在通过useChartPalette走统一颜色通道。

2. 编排器返回类型耦合(低风险)

CondensedChartLayout与ScrollableChartLayout将orchprop 类型写死为ReturnType<typeof useChartCondensedOrchestrator>/ReturnType<typeof useChartScrollableOrchestrator>。这意味着布局组件与编排器返回值的精确形状强耦合——任何返回形状的改动都需要同时更新布局组件和所有图表组件。

3. 调色板硬编码十六进制(低风险)

paletteUtils.ts全部使用十六进制字符串而非 oklch/CSS 自定义属性,导致图表数据色不参与 ThemeProvider 的 oklch 色彩体系。不过useChartPalette会先检查theme[themePaletteName](允许主题级覆盖),因此通过 ThemeProvider 提供自定义调色板的用户可以得到缓解。

4. 缺少单元测试(高风险)

ChartsV2 目录下没有任何测试文件,所有验证仅依赖 Storybook 可视化测试。而 hooks 中包含大量值得单测的逻辑(尺寸计算、比例尺构建、悬停检测、滚动状态);工具函数(scrollUtils、mouseUtils、dataUtils、polarUtils)是纯函数,天然易测。

5. 可访问性缺失(中风险)

图表 SVG 元素已有role="img"和aria-label,但仍存在:

  • 数据点无键盘导航;
  • 悬停/Tooltip 内容无屏幕阅读器播报;
  • 图例项是可点击的div,没有role="button"也没有键盘处理器;
  • 动态 Tooltip 内容没有 ARIA live region。

总结:这套架构给开发者的启示

从整体设计来看,ChartsV2 是一次「图表库内部架构」的范式升级:旧版 Charts/ 包以 Recharts 为底座(Charts.tsx 中import * as RechartsPrimitive from "recharts"可见其依赖方式),而 ChartsV2 将数据数学(D3)、DOM 渲染(React)、视觉动画(CSS)三者彻底解耦,并用「编排器 hook + 槽位布局组件 + 拓扑分层共享层」的组合把可复用性推到极致。

如果你要在这套体系中新增图表,遵循的路径是清晰的:先判断坐标系归属(core/cartesian/polar)→ 复用或组合对应编排器 → 只写图表专属的序列几何与十字线 → 注入共享布局的 slot → 用 SCSS mixin 生成样式,并遵守「parts/起步、两种图表复用才升级shared/」的演进规则。这套设计在架构文档(ARCHITECTURE.md)中有完整的模块地图、数据流与依赖边界可供对照,是理解整个 OpenUI 组件库质量基线的一份重要参考。

【免费下载链接】openui

The Open Standard for Generative UI

项目地址:https://gitcode.com/gh_mirrors/openui1/openui
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询