D3 v7 快速上手:CDN、npm 与 React/Svelte 集成的完整接入指南
【免费下载链接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:项目地址: https://gitcode.com/GitHub_Trending/d3/d3
D3(Data-Driven Documents)是一个运行在任何 JavaScript 环境中的数据可视化底层库。本篇基于官方入门文档 getting-started.md 与仓库源码,带你完整走通 D3 v7 的四种接入方式(在线环境、原生 HTML、npm、React/Svelte),并解释每种方式背后的模块组织与打包机制,读完即可在自己的项目中复制一套可运行的空白图表骨架。
一、D3 的运行形态:30 个子模块的聚合层
理解 D3 接入方式之前,先理解 D3 包本身的结构。从 package.json 可以看到,当前仓库版本为7.9.0("type": "module",要求 Node >= 12),dependencies中声明了 30 个子模块:d3-array、d3-axis、d3-brush、d3-chord、d3-color、d3-contour、d3-delaunay、d3-dispatch、d3-drag、d3-dsv、d3-ease、d3-fetch、d3-force、d3-format、d3-geo、d3-hierarchy、d3-interpolate、d3-path、d3-polygon、d3-quadtree、d3-random、d3-scale、d3-scale-chromatic、d3-selection、d3-shape、d3-time、d3-time-format、d3-timer、d3-transition、d3-zoom。
D3 主包并不重复实现这些功能,src/index.js 仅由 30 行export * from组成,把每个子模块的全部导出平铺到d3命名空间下;bundle.js 再补上从package.json导出的version字段。这也解释了为什么入门文档中d3.scaleUtc()、d3.axisBottom()、d3.line()都能直接从d3根对象访问。
测试用例 test/d3-test.js 用一条自动化断言守住了这一契约:遍历package.json的dependencies,动态import每个子模块,并断言其每个导出(除version外)都出现在d3命名空间中——从源码结构看,D3 主包的角色就是“全量子模块的再导出(re-export)层”,而不是独立的功能实现。
这个结构直接决定了后文的三种加载策略:可以整包引入(获得全部 30 个子模块的符号),也可以只从 CDN 或 npm 单独引入某个子模块。
二、第一张图:比例尺 + 坐标轴的空白图表骨架
无论使用哪种加载方式,入门文档给出的第一个示例都是同一个:用d3.create创建一个 640×400 的 SVG 容器,声明 x/y 两个比例尺,再用d3.axisBottom/d3.axisLeft挂上坐标轴,得到一个可以填充数据的空白图表。这也是文档内交互组件 ExampleBlankChart.vue 中实际渲染的代码。
核心代码(Observable 单元形式,完整继承自原文档):
{ // Declare the chart dimensions and margins. const width = 640; const height = 400; const marginTop = 20; const marginRight = 20; const marginBottom = 30; const marginLeft = 40; // Declare the x (horizontal position) scale. const x = d3.scaleUtc() .domain([new Date("2023-01-01"), new Date("2024-01-01")]) .range([marginLeft, width - marginRight]); // Declare the y (vertical position) scale. const y = d3.scaleLinear() .domain([0, 100]) .range([height - marginBottom, marginTop]); // Create the SVG container. const svg = d3.create("svg") .attr("width", width) .attr("height", height); // Add the x-axis. svg.append("g") .attr("transform", `translate(0,${height - marginBottom})`) .call(d3.axisBottom(x)); // Add the y-axis. svg.append("g") .attr("transform", `translate(${marginLeft},0)`) .call(d3.axisLeft(y)); // Return the SVG element. return svg.node(); }几个值得注意的细节:
- 边距约定(margin convention):x 轴的范围是
[marginLeft, width - marginRight],y 轴是[height - marginBottom, marginTop](注意 y 轴起点在下、终点在上,符合 SVG 坐标系),坐标轴<g>通过transform="translate(...)"平移到边距边界上; d3.create("svg")创建的是一个尚未插入文档的 SVG 元素,需要显式返回(Observable)或container.append(svg.node())(原生 HTML);- 该示例同时涉及三个子模块:d3-scale(
scaleUtc/scaleLinear)、d3-selection(create/append/call)、d3-axis(axisBottom/axisLeft)。
2.1 在线体验:Observable 环境
官方文档推荐的入门路径是在 Observable 笔记本中使用:D3 作为 Observable 标准库的一部分默认可用,只需让单元返回生成的 DOM 元素即可渲染。除了上面的空白图表,文档列出了五个可 fork 的入门模板:面积图(Area chart)、柱状图(Bar chart)、环形图(Donut chart)、直方图(Histogram)、折线图(Line chart),并在 Observable 的 D3 gallery 中收录了数百个可 fork 的笔记本作为起步参考。点击+新建单元并输入 “d3” 可以过滤出内置的 D3 片段,Observable 还提供样例数据集、CSV/JSON 上传等便利功能供练习。
2.2 原生 HTML:三种加载方式
在原生 HTML 页面中,文档给出三种等价写法,示例逻辑与上面的空白图表完全一致,区别仅在 D3 的引入方式。
方式一:ESM + CDN(文档推荐)
<!DOCTYPE html> <div id="container"></div> <script type="module"> import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm"; // Declare the chart dimensions and margins. const width = 640; const height = 400; const marginTop = 20; const marginRight = 20; const marginBottom = 30; const marginLeft = 40; // Declare the x (horizontal position) scale. const x = d3.scaleUtc() .domain([new Date("2023-01-01"), new Date("2024-01-01")]) .range([marginLeft, width - marginRight]); // Declare the y (vertical position) scale. const y = d3.scaleLinear() .domain([0, 100]) .range([height - marginBottom, marginTop]); // Create the SVG container. const svg = d3.create("svg") .attr("width", width) .attr("height", height); // Add the x-axis. svg.append("g") .attr("transform", `translate(0,${height - marginBottom})`) .call(d3.axisBottom(x)); // Add the y-axis. svg.append("g") .attr("transform", `translate(${marginLeft},0)`) .call(d3.axisLeft(y)); // Append the SVG element. container.append(svg.node()); </script>方式二:UMD + CDN
UMD 包以普通<script>加载时会挂出全局d3对象,适合无法使用 ES 模块的旧环境:
<!DOCTYPE html> <div id="container"></div> <script src="https://cdn.jsdelivr.net/npm/d3@7"></script> <script type="module"> // Declare the chart dimensions and margins. const width = 640; const height = 400; const marginTop = 20; const marginRight = 20; const marginBottom = 30; const marginLeft = 40; // Declare the x (horizontal position) scale. const x = d3.scaleUtc() .domain([new Date("2023-01-01"), new Date("2024-01-01")]) .range([marginLeft, width - marginRight]); // Declare the y (vertical position) scale. const y = d3.scaleLinear() .domain([0, 100]) .range([height - marginBottom, marginTop]); // Create the SVG container. const svg = d3.create("svg") .attr("width", width) .attr("height", height); // Add the x-axis. svg.append("g") .attr("transform", `translate(0,${height - marginBottom})`) .call(d3.axisBottom(x)); // Add the y-axis. svg.append("g") .attr("transform", `translate(${marginLeft},0)`) .call(d3.axisLeft(y)); // Append the SVG element. container.append(svg.node()); </script>方式三:UMD + 本地文件(离线场景)
<!DOCTYPE html> <div id="container"></div> <script src="d3.js"></script> <script type="module"> // Declare the chart dimensions and margins. const width = 640; const height = 400; const marginTop = 20; const marginRight = 20; const marginBottom = 30; const marginLeft = 40; // Declare the x (horizontal position) scale. const x = d3.scaleUtc() .domain([new Date("2023-01-01"), new Date("2024-01-01")]) .range([marginLeft, width - marginRight]); // Declare the y (vertical position) scale. const y = d3.scaleLinear() .domain([0, 100]) .range([height - marginBottom, marginTop]); // Create the SVG container. const svg = d3.create("svg") .attr("width", width) .attr("height", height); // Add the x-axis. svg.append("g") .attr("transform", `translate(0,${height - marginBottom})`) .call(d3.axisBottom(x)); // Add the y-axis. svg.append("g") .attr("transform", `translate(${marginLeft},0)`) .call(d3.axisLeft(y)); // Append the SVG element. container.append(svg.node()); </script>说明:官方文档页面上的
d3.v7.js/d3.v7.min.js下载链接指向由构建流程生成的 UMD 包——prebuild.sh 会在文档构建时把dist/d3.js和dist/d3.min.js复制为docs/public/d3.v7.js/docs/public/d3.v7.min.js,因此这两个文件并不直接提交在源码树中。调试时使用非压缩版,生产环境使用压缩版以获得更快的加载性能。
UMD 全局d3从何而来?从 rollup.config.js 可以看到,构建产物dist/d3.js采用format: "umd"、全局变量名为d3,并附加了版本与版权 banner;同一配置还额外产出dist/d3.mjs(ESM 格式)和dist/d3.min.js(经 terser 压缩)。package.json 中的files字段确认发布包携带dist/d3.js与dist/d3.min.js,jsdelivr/unpkg字段与exports["umd"]都指向dist/d3.min.js——这就是上述 CDN 与 UMD 加载方式背后的产物。
2.3 只加载需要的子模块
如果只需要力导向图,不必引入整个 d3 聚合包,可以直接从 CDN 按需导入单个子模块的具名导出:
<script type="module"> import {forceSimulation, forceCollide, forceX} from "https://cdn.jsdelivr.net/npm/d3-force@3/+esm"; const nodes = [{}, {}]; const simulation = forceSimulation(nodes) .force("x", forceX()) .force("collide", forceCollide(5)) .on("tick", () => console.log(nodes[0].x)); </script>注意这里导入的是独立的d3-force@3包而不是d3@7,子模块在 npm 上按各自的 v3 线发布(与主包 v7 并存),各子模块的最低版本约束以 package.json 中的dependencies为准。
三、从 npm 安装
如果你的应用基于 Node 构建(Vite、webpack 等打包环境),用任意包管理器安装整包:
# yarn yarn add d3# npm npm install d3# pnpm pnpm add d3安装后有三种导入粒度:
// 1. 整体导入:获得 30 个子模块的全部符号(最常见) import * as d3 from "d3"; // 2. 具名导入:只取需要的符号,便于打包器摇树 import {select, selectAll} from "d3"; // 3. 子模块直连:直接依赖 d3-array 等独立包 import {mean, median} from "d3-array";从源码结构看,package.json 将main/module都指向src/index.js,所以包管理器解析到的入口正是那个 30 行再导出文件,三种导入方式最终拿到的是同一套符号表。TypeScript 类型声明由社区维护的 DefinitelyTyped 库提供(@types/d3),文档未将其内置于包中,按需安装即可。
四、D3 在 React 中
D3 的模块大致分两类:
- 不触碰 DOM 的纯计算模块(d3-scale、d3-array、d3-interpolate、d3-format 等)——在 React 中与普通模块无差别,可以直接在 JSX 里做纯声明式渲染;
- 操作 selection 的模块(d3-selection、d3-transition、d3-axis)——直接改写真实 DOM,会与 React 的虚拟 DOM 冲突,需要借助 ref +
useEffect把 D3 的写入限制在 React 不管理的节点内。
模式一:纯声明式(无 DOM 操作)。下面这个折线图组件只用了比例尺与 d3-shape 的line,SVG 元素全部由 React 渲染:
import * as d3 from "d3"; export default function LinePlot({ data, width = 640, height = 400, marginTop = 20, marginRight = 20, marginBottom = 20, marginLeft = 20 }) { const x = d3.scaleLinear([0, data.length - 1], [marginLeft, width - marginRight]); const y = d3.scaleLinear(d3.extent(data), [height - marginBottom, marginTop]); const line = d3.line((d, i) => x(i), y); return ( <svg width={width} height={height}> <path fill="none" stroke="currentColor" strokeWidth="1.5" d={line(data)} /> <g fill="white" stroke="currentColor" strokeWidth="1.5"> {data.map((d, i) => (<circle key={i} cx={x(i)} cy={y(d)} r="2.5" />))} </g> </svg> ); }模式二:ref + useEffect(需要 DOM 操作的坐标轴)。给两个<g>挂 ref,在 effect 里把 D3 选集交给坐标轴:
import * as d3 from "d3"; import {useRef, useEffect} from "react"; export default function LinePlot({ data, width = 640, height = 400, marginTop = 20, marginRight = 20, marginBottom = 30, marginLeft = 40 }) { const gx = useRef(); const gy = useRef(); const x = d3.scaleLinear([0, data.length - 1], [marginLeft, width - marginRight]); const y = d3.scaleLinear(d3.extent(data), [height - marginBottom, marginTop]); const line = d3.line((d, i) => x(i), y); useEffect(() => void d3.select(gx.current).call(d3.axisBottom(x)), [gx, x]); useEffect(() => void d3.select(gy.current).call(d3.axisLeft(y)), [gy, y]); return ( <svg width={width} height={height}> <g ref={gx} transform={`translate(0,${height - marginBottom})`} /> <g ref={gy} transform={`translate(${marginLeft},0)`} /> <path fill="none" stroke="currentColor" strokeWidth="1.5" d={line(data)} /> <g fill="white" stroke="currentColor" strokeWidth="1.5"> {data.map((d, i) => (<circle key={i} cx={x(i)} cy={y(d)} r="2.5" />))} </g> </svg> ); }关键点:useEffect的依赖数组传入[gx, x]/[gy, y],数据变化导致比例尺重建时坐标轴随之重绘;而<path>与<circle>仍由 React 声明,D3 只负责d3.axisBottom/d3.axisLeft内部生成的刻度与标签——两者各管各的 DOM 子树,互不覆盖。
五、D3 在 Svelte 中
Svelte 的策略与 React 相同:优先只用不操作 DOM 的模块做纯渲染,需要 DOM 操作时再借bind:this把节点交给 D3。
模式一:纯声明式折线图(使用 d3-shape 与 d3-scale):
<script> import * as d3 from 'd3'; export let data; export let width = 640; export let height = 400; export let marginTop = 20; export let marginRight = 20; export let marginBottom = 20; export let marginLeft = 20; $: x = d3.scaleLinear([0, data.length - 1], [marginLeft, width - marginRight]); $: y = d3.scaleLinear(d3.extent(data), [height - marginBottom, marginTop]); $: line = d3.line((d, i) => x(i), y); </script> <svg width={width} height={height}> <path fill="none" stroke="currentColor" stroke-width="1.5" d={line(data)} /> <g fill="white" stroke="currentColor" stroke-width="1.5"> {#each data as d, i} <circle key={i} cx={x(i)} cy={y(d)} r="2.5" /> {/each} </g> </svg>模式二:响应式语句驱动动态坐标轴。Svelte 的$:响应式语句与 D3 的数据联结(data join)天然契合——数据一变,语句重算,坐标轴自动更新:
<script> import * as d3 from 'd3'; export let data; export let width = 640; export let height = 400; export let marginTop = 20; export let marginRight = 20; export let marginBottom = 30; export let marginLeft = 40; let gx; let gy; $: x = d3.scaleLinear([0, data.length - 1], [marginLeft, width - marginRight]); $: y = d3.scaleLinear(d3.extent(data), [height - marginBottom, marginTop]); $: line = d3.line((d, i) => x(i), y); $: d3.select(gy).call(d3.axisLeft(y)); $: d3.select(gx).call(d3.axisBottom(x)); </script> <svg width={width} height={height}> <g bind:this={gx} transform={`translate(0,${height - marginBottom})`} /> <g bind:this={gy} transform={`translate(${marginLeft},0)`} /> <path fill="none" stroke="currentColor" stroke-width="1.5" d={line(data)} /> <g fill="white" stroke="currentColor" stroke-width="1.5"> {#each data as d, i} <circle key={i} cx={x(i)} cy={y(d)} r="2.5" /> {/each} </g> </svg>与 React 版对比:Svelte 不需要useEffect来声明副作用边界,bind:this+$:语句本身就承担了这个职责,因此框架内使用 D3 的“DOM 操作型”模块时,Svelte 的样板代码更少。
六、选型小结与后续路径
| 场景 | 推荐方式 | 依据 |
|---|---|---|
| 快速试验、教学演示 | Observable 在线笔记本 | D3 默认内置于其标准库,单元返回 DOM 即渲染 |
| 静态页面、一次性嵌入 | ESM + CDN(d3@7/+esm) | 官方推荐;无构建步骤,<script type="module">直用 |
| 旧环境 / 无模块支持 | UMD + CDN(全局d3) | dist/d3.js为 UMD 格式,加载即挂全局 |
| 离线 / 内网环境 | 本地 UMD 文件 | 非压缩版调试、压缩版生产 |
| Node 构建应用 | npm install d3+ ES 导入 | 入口即 src/index.js 再导出层 |
| React / Svelte | 纯计算模块声明式渲染;selection 类模块走 ref /bind:this | 避免 D3 与虚拟 DOM 争抢节点 |
入门之后,可按主题沿仓库文档深入:选择与数据联结见 d3-selection/selecting.md 与 d3-selection/joining.md,比例尺体系见 d3-scale.md 及其下的 band/linear/ordinal 等专题文档,图形生成器见 d3-shape/line.md 等;完整 API 总览可参考 API.md。由于 D3 v7 是纯 ESM 发布(type: "module"),若你的运行环境不支持原生 ES 模块或import语法,需要借助 UMD 包或自行使用打包工具做转译——这是选择加载方式时最核心的兼容性前提。
【免费下载链接】d3Bring data to life with SVG, Canvas and HTML. :bar_chart::chart_with_upwards_trend::tada:项目地址: https://gitcode.com/GitHub_Trending/d3/d3
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考