【免费下载链接】openui
The Open Standard for Generative UI
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、Line | X/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、XAxisTickVariant | paletteUtils(PaletteName) |
utils/dataUtils.ts | 提取数据键、构建 chart config、图例项、颜色查找 | types |
utils/paletteUtils.ts | 6 套颜色调色板(每套 11 色)、useChartPalettehook、颜色分布 | ThemeProvider |
utils/mouseUtils.ts | findNearestDataIndex(point scale)、findBandIndex(band scale) | d3-scale |
utils/scrollUtils.ts | 数据宽度计算、snap 位置、密度间距 | -- |
utils/styleUtils.ts | numberTickFormatter(K/M/B/T 缩写)、measureYAxisWidth(基于刻度的轴宽测量) | -- |
utils/polarUtils.ts | 极坐标辅助:排序、slice 悬停样式、百分比格式化、雷达角度 | types |
utils/buildContainerStyle.ts | 合并图表 CSS 变量与固定宽/高覆盖 | -- |
hooks/core/ | 与坐标系无关、所有图表共享的 hook | d3-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 scale | hooks, 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+CondensedChartLayoutD3[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 机制:
- ChartTooltip—— 基于
@floating-ui/react-dom的门户式数据 Tooltip。通过虚拟元素定位在鼠标视口坐标处,超过 10 条时截断为 5 条并提示 "Click to view all",最终 portal 到document.body并携带主题 class。 - 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-containeropenui-d3-bar-chart-hover-highlightopenui-d3-line-chart-line--animated
共享组件使用openui-chart-{component}:
openui-chart-legend-containeropenui-chart-tooltipopenui-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(单序列饼/径向)—— 因单序列模型不同,自带内联实现。
架构优势
- 关注点强分离:D3 做数学、React 做 DOM、CSS 做动画,没有
d3.select().append()与 React 虚拟 DOM 打架的问题; - 编排器带来的高复用:新增一个笛卡尔图表只需约 100 行图表专属代码,编排器承载约 80% 的逻辑;
- 一致的 API 表面:所有笛卡尔图表共享
BaseChartProps,图例、Tooltip、悬停行为跨图表完全一致; - 渐进增强:现代浏览器支持 SVG
d属性的 CSStransition,老浏览器优雅降级为即时更新; - 默认响应式:
useContainerSize基于 ResizeObserver,配合自动滚动/紧凑适配; - 打印感知:
usePrintContext在打印时禁用全部动画,保证静态输出; - 共享布局组件:
ScrollableChartLayout与CondensedChartLayout消除了 3 种图表间的布局代码重复; - 拓扑分层共享层:
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
相关推荐
猫抓浏览器扩展:3步解锁网页资源捕获的终极指南
猫抓浏览器扩展:3步解锁网页资源捕获的终极指南 你是否曾经在浏览网页时遇到心仪的视频、音频或图片资源,却苦于无法轻松下载?或者面对复杂的流媒体加密内容束手无策?
音视频OpenUI react-ui 主题系统深度指南:ThemeProvider 与 `--openui-*` 设计令牌架构全解析
OpenUI react ui 主题系统深度指南:ThemeProvider 与 openui 设计令牌架构全解析 本篇技术指南以 openui 仓库 pack
Redwood Forms 完全指南:基于 React Hook Form 的表单构建与校验体系
Redwood Forms 完全指南:基于 React Hook Form 的表单构建与校验体系 Redwood 框架通过 @redwoodjs/forms 包
后端前端Web框架开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考