你有没有见过一座城市地下纵横交错的水管?自来水厂把水送出去,主管道粗、支管道细,水沿着管道分岔流进千家万户;你打开水表,能看到这个月家里用了多少吨;水务局的大屏上,能看到整座城市的供水从源头到龙头的完整脉络。而如果统计每个小区之间的水量互补、跨区调水,又需要一张“谁和谁在打交道”的联络图。再进一步,想判断一座小区的水质、水压、满意度、漏损率到底哪项强哪项弱,又得把多个指标放在同一张蛛网上对比。 这三件事,恰恰对应了 D3.js 里三种“高级图表”的本领:桑基图(Sankey) 画“流”的分配与去向,和弦图(Chord) 画“关系”的你来我往,雷达图(Radar) 画“多维指标”的此消彼长。加上 d3-hierarchy 布局和自定义形状生成器,你就拥有了在浏览器里亲手搭建这类“数据大屏”的完整工具箱。本文就从这张“城市供水网”出发,带你零基础看懂这三张图背后共同的 D3 设计哲学:数据 → 布局 → 图形。
对 AI 从业者而言,掌握 D3 高级图表意味着你不仅能读懂 GPU 算力消耗、模型性能雷达图、数据流水线依赖关系,还能独立把它们画成交互式大屏,在汇报与产品中直接提升说服力。
可视化演示
动态演示D3.js高级图表的核心机制,通过入门/进阶/实战/优化四种模式展示关键概念与交互流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
D3.js高级图表:Djs高 D3.js高级图表体系概述 桑基图与能量流可视化 核心机制可视化 D3.js高级图表整体架构与关键组件
桑基图初识
让我们回到城市供水系统。假设你是水务局的调度员,最关心的问题是:“水厂产出的 100 万吨水,到底流向了哪里?”一份 Excel 表格可以告诉你东区 30 万吨、西区 25 万吨、工业区 45 万吨,可是“流向”这件事带着过程感——水从水厂出发,途经加压站,再分到各区,最后一部分变成污水回归处理厂。表格无法让你一眼看到“谁给了谁多少”,而桑基图就是为这种“流动分配”而生的。
桑基图由两类视觉元素构成:节点(node) 和 连接(link)。节点是“实体”,比如水厂、加压站、东区、西区;连接是“流动关系”,比如“水厂 → 东区”这根管道。关键的视觉编码是——连接条的宽度与流量数值成正比。就像现实中粗水管流量大、细水管流量小,桑基图里的边宽就是流量的“物理化身”。这种由数据字段直接决定几何属性的做法,正是 D3 可视化的灵魂。
在 D3 中,节点和连接都是普通 JavaScript 对象。节点只要有一个 name 字段;连接需要三个字段:source(起点)、target(终点)、value(流量数值)。桑基布局拿到这些数据后,会自动计算出每个节点应该放在哪一列、每根连接条应该有多宽、弯曲路径怎么走。你不需要手动画一条贝塞尔曲线,D3 的 sankey 布局会替你完成全部几何运算——这正是“数据驱动图形”的体现。
实际应用中,一张桑基图通常有几十个节点、上百条连接,D3 的 sankey 布局会自动完成分层排列,让所有连接条尽量少交叉、流向清晰。这就像水务工程师设计地下管网时,要让管线交错但水流有序。你只需要维护数据,剩下的几何难题交给布局库。
- 边的宽度正比于 value这条规则把“流量”这个抽象数字翻译成肉眼可见的条宽,好比水管越粗表示水量越大。
- 节点位置交给布局函数sankey 布局接收节点数组和连接数组,输出每个节点的 x/y 坐标,类似自动排版师。
构建能量流
现在我们开始动手铺设第一段“数据管道”。以供水系统为例,数据准备工作分两步:第一步,定义节点数组,把参与流动的所有实体列出来——“水厂”“加压站”“东区”“西区”“工业区”;第二步,定义连接数组,把每条流动关系写成 {source: "水厂", target: "东区", value: 30} 这样的对象。这里的 value 就是流量数值,它直接决定连接条的宽度。
有了这两份数据,就轮到 sankey 布局登场。调用 d3.sankey() 创建一个布局实例,然后传入节点和连接数组,它会返回一个带坐标的增强数据。举个例子,布局完成后,节点对象上会多出 x0、x1、y0、y1 四个属性,确定了节点矩形的精确位置;连接对象上会多出 width 属性和 path 属性,前者是条宽,后者是可直接交给 SVG 的路径字符串。
这里有一个重要的理念:布局只算“位置”,不负责“绘制”。布局完成后,你用 rect 元素渲染节点,用 path 元素的 d 属性渲染连接曲线。D3 的选择集和 enter/exit 机制在这里大显身手:svg.selectAll('rect').data(nodes).enter().append('rect') 就能给每个节点创建一个矩形。这种“数据绑定-进入-更新-退出”的模式,是 D3 生态最核心的心智模型。
布局计算完成后,最终的视觉效果由渲染层完成。节点矩形用 fill-opacity 控制透明度,连接条用渐变色或半透明色体现层级感。桑基图最有魅力的地方是:当鼠标悬停在某个节点上时,你可以高亮所有与之相连的路径,弱化其余部分——这种交互体验,让观看者能快速追踪“水”从哪来到哪去。
- 连接像素宽度计算把单条流量除以总流量得到占比,再乘以图表总宽度,得到这条边该画多宽。
和弦关系图
桑基图擅长回答“流到哪里”,但有时候你更关心“谁和谁之间有往来”。想象供水网络里五个片区之间需要互相调水:东区缺水时向西区借水,工业区向电厂输送冷却水,处理厂向绿化带回供水。这种“多对多、双向的”关系,用桑基图会显得杂乱无章,而和弦图(Chord Diagram)才是天生的主角。
和弦图的核心数据结构是一张关系矩阵:一个 n×n 的二维数组,matrix[i][j] 表示第 i 个实体流向第 j 个实体的量。矩阵第 i 行的总和就是节点 i 的出度,第 j 列的总和是节点 j 的入度。在视觉上,n 个节点沿着圆周均匀分布,每个节点对应一段圆弧,圆弧长度与该节点的总量成正比;节点与节点之间的“弦”——一条连接两段圆弧的带状区域——宽度代表两者之间的流量。由此,你可以一眼看清“谁和谁互动最频繁”。
D3 提供 d3.chord() 布局,输入矩阵,输出一组 chord 对象,每个对象包含 source 和 target 两个子对象,各自带有 startAngle、endAngle、value 等字段。这些角度值直接描述圆弧和带状区域的范围。绘制时,D3 的 d3.arc() 生成圆弧路径,d3.ribbon() 生成弦的带状路径。
和弦图的优雅之处在于,矩阵的行列之和与圆弧长度严格对应,形成“数据-几何-视觉”的闭环。交互方面,鼠标移到某段圆弧上时,可以高亮该节点所有连入连出的弦,其他弦全部降低透明度。这种“点亮关系网”的效果,在展示社交网络、基因互作、城市间人口流动时极其震撼。
- 矩阵元素映射为弦矩阵的每个元素
matrix[i][j]经 chord 布局计算后,变成一段角度区间,确定弦从哪里开始、在哪里结束。 - 角度区间确定弦的边界
startAngle与endAngle构成该弦在圆周上的跨度,角度差越大,弦越粗。
手写和弦图
d3.chord() 虽然方便,但为了真正理解“矩阵如何变成弦”,我们不妨手写一个简化版的和弦图布局。假设有三个节点:水厂 A、东区 B、西区 C。它们之间的调水关系如下:A 给 B 送 10 吨,A 给 C 送 5 吨,B 给 C 送 3 吨。第一步是构造 relationMap——用二维数组记录任意两节点之间的流量,relationMap[0][1] = 10 表示 A→B 有 10 吨。
第二步,因为和弦图在计算角度时依赖每个节点自身的总量,而我们的数据只有“出量”,需要手动补全“入量”。将 relationMap 与其转置相加,得到一个对称矩阵:matrix[i][j] = relationMap[i][j] + relationMap[j][i]。这样,A→B 的 10 吨和 B→A 的 0 吨会合并成一项,出现在矩阵的对称位置上。这种对称化处理,让每个节点在同一段圆弧上同时表达“给出”和“接收”。
第三步,计算每个节点的总量 total[i] = sum(matrix[i]),按照总量占比分配圆周角度。节点的起始角度累加,就能得到每一段圆弧的 startAngle 和 endAngle。最后用 d3.arc() 画圆弧、用 d3.ribbon() 画弦。整个过程下来,你会深刻体会到:和弦图的本质是“总量决定角度,分项决定弦宽”。
手写一遍和弦图,最大的收获是理解了 D3 布局库背后“角度-位置”的换算逻辑。当你日后遇到 D3 没有现成布局的图(比如蜂群图、冲积图),你也能从容地用基本几何知识自己实现。这就像会做饭的人看菜谱,不仅会跟着步骤走,还知道每一步为什么要这么做。
- 对称化矩阵将原始关系矩阵与其转置相加,保证
i→j与j→i的流量合并到对称位置,图表才能表达双向关系。 - 总量折算圆心角每个节点的总流量占所有节点总流量的比例,乘以 2π,就是该节点圆弧的角度跨度。
雷达图对比
供水系统除了“流向”和“关系”,还有“质量”问题。调度员需要给各个片区的水质、水压、漏损率、用户满意度、抢修及时率打分,然后回答:哪个片区综合表现最好?如果用柱状图并列五个指标,你需要画五个小图;如果拍成五维表格,又很难直观对比。雷达图(Radar/Spider Chart)恰好是这种“多维对比”场景的利器。
雷达图的基本结构是:从圆心向外辐射出若干条轴线,每条轴代表一个指标维度,轴的末端标注指标名称。数据值映射为该方向上距离圆心的长度,把各个轴上的点连起来,就形成一个不规则多边形。多边形的“面积”越大,代表该实体综合表现越好;多条多边形叠加在一起,可以直观地比较多个实体在多个维度上的优劣。
用 D3 绘制雷达图需要两个核心映射:第一,角度映射:第 i 个指标的角度是 i / n * 2π(从 12 点方向开始顺时针分布);第二,半径映射:指标值经过线性比例尺 d3.scaleLinear() 映射为半径上的像素距离。得到每个轴的坐标之后,用 d3.line() 生成多边形路径。雷达图的“蛛网骨架”则由若干同心圆环和径向轴线组成,这些辅助图形让数值的刻度感更清晰。
雷达图在实际使用中要注意“维度不要太多”。超过 8 个轴,蛛网会变得密集,反而难以阅读;指标之间如果相关性很强,多边形的凹陷与凸起也容易误导判断。合理做法是:选取 4~8 个互相独立的核心指标,一次对比 2~4 个实体,用不同颜色的半透明填充加以区分。
- 归一化指标值把原始分数 v 减去最小值再除以极差,得到 0~1 之间的归一化值,这样不同量纲的指标才能放在同一张图上。
- 角度分配第 i 个指标的角度为 i/n 圈,n 个指标均匀分布在圆周上。
- 投影为平面坐标以圆心为原点,半径乘以 cos/sin 得到该点的 x/y 坐标,D3 据此连线成多边形。
布局与实战
三种图各自精彩,但真实场景往往需要它们同台登场。假设你要给水务局建设一张“城市供水数据大屏”:中间是桑基图展示水厂到片区的流量,右侧是和弦图展示片区间的调水关系,下方是雷达图对比各片区综合指标。三张图共享同一个数据中心,如何组织代码?
答案藏在 d3-hierarchy 布局中。d3.hierarchy() 可以把一棵普通的嵌套 JSON 数据转换成带 depth、height、parent、children 等属性的层级对象。当你的数据模型是“公司→部门→小组→个人”或“省→市→区→街道”这种树状结构时,hierarchy 布局是整个可视化方案的骨架。桑基图的层级列、和弦图的节点分组、雷达图的指标分组,都可以用同一棵 hierarchy 树来驱动。
另一个被低估的工具是自定义形状生成器。桑基图的连接条不是直线矩形,而是渐变的曲线带状;和弦图的弦也不是简单的直线,而是有厚度的弯曲带状。D3 的 d3.ribbon()、d3.curveBasis、d3.linkHorizontal() 等路径生成器,让你能精确控制曲线的切线方向与弯曲程度。这些生成器接受角度或坐标,输出 SVG 路径字符串——它们是“形状的语法”。
工程实战中,三图联动的大屏通常遵循一条约定:一份数据仓库,多个布局实例,统一渲染入口。数据更新时,三个布局各自重新计算,然后统一触发重绘。性能方面,如果数据量较大,可以开启 canvas 渲染层(桑基图的连接条、弦图的带状区域在 canvas 上性能更佳),SVG 则保留给少量交互元素。可维护性方面,把“数据加载”“布局计算”“图形渲染”“交互绑定”拆成四个模块,是 D3 项目的最佳实践。
- 路径由生成器产出给定数据点和张力系数,生成器返回 SVG 的 d 属性路径字符串。
AI 应用场景
可视化 AI 模型训练流水线
阶段:模型训练与调优阶段(数据流分析)
解决的问题:训练一个大型推荐模型时,数据从清洗、特征工程、采样、训练到评估,每个环节的数据量都在变化。用表格难以快速定位“哪个环节丢数据最严重”“某条数据支路是否被过度采样”。桑基图把每个处理环节当作节点,把数据量当作连接宽度,一眼就能看出数据在哪个环节“瘦身”最多、哪条支路贡献最大。
具体案例:某推荐算法团队将训练流水线中 120 个处理步骤聚合为 12 个核心节点,用桑基图展示从原始日志到最终训练样本的流量变化,发现某个特征拼接步骤丢弃了 37% 的样本,优化后模型离线 AUC 提升 1.8%。
对比多个模型的多维性能
阶段:模型评估与选型阶段
解决的问题:同时训练了 4 个候选模型,需要从准确率、召回率、推理延迟、显存占用、鲁棒性 5 个维度综合评判。常规表格只能逐行对比,雷达图将 4 个模型的多边形叠加在一个蛛网上,直接看出模型 A 在“鲁棒性”维度明显凹陷,模型 C 全面占优。
具体案例:某自动驾驶团队用雷达图对比 4 个感知模型在晴天、雨天、夜间、逆光、遮挡 5 个场景下的 mAP,发现一个在平均分上最高的模型在夜间场景严重退化,最终放弃该模型。
分析模型间知识迁移关系
阶段:模型部署与知识蒸馏阶段
解决的问题:知识蒸馏中,教师模型向多个学生模型传递知识,学生模型之间也可能互相学习。这种“谁向谁学了什么”的多对多关系,适合用和弦图表达:每个模型是一段圆弧,弦的宽度代表蒸馏样本量或迁移影响强度。
具体案例:某 NLP 团队用和弦图可视化 BERT 教师模型向 5 个轻量学生模型蒸馏时的知识流动量,发现某个学生模型反而向另一个学生模型学到了更多,间接揭示了蒸馏顺序的优化空间。
Python 演示
# -*- coding: utf-8 -*-"""Three examples of advanced D3-style charts implemented with Python/Matplotlib.Focus: Sankey-like flows, Chord-like relations, Radar-like multi-dim comparison.""" import numpy as npimport matplotlib.pyplot as pltfrom matplotlib.patches import PathPatchfrom matplotlib.path import Path def sankey_demo(): # nodes: 0=plant, 1=station, 2=east, 3=west, 4=industry links = [{"source": 0, "target": 1, "value": 30}, {"source": 0, "target": 2, "value": 20}, {"source": 1, "target": 3, "value": 25}, {"source": 1, "target": 4, "value": 35}] total = float(sum(l["value"] for l in links)) y_pos = [0.8, 0.6, 0.4, 0.2, 0.0] color_map = plt.cm.Blues fig, ax = plt.subplots(figsize=(6, 4)) for i, l in enumerate(links): width = l["value"] / total * 0.6 y1 = y_pos[l["source"]] y2 = y_pos[l["target"]] x1, x2 = 0.1, 0.9 verts = [(x1, y1), (x1, y1 - width / 2), (x2, y2 - width / 2), (x2, y2)] codes = [Path.MOVETO, Path.LINETO, Path.LINETO, Path.LINETO] path = Path(verts, codes) patch = PathPatch(path, lw=0, color=color_map(0.4 + 0.1 * i), alpha=0.6) ax.add_patch(patch) ax.set_xlim(0, 1) ax.set_ylim(-0.2, 1.0) ax.set_axis_off() ax.set_title("Sankey-like flow") plt.show() def chord_demo(): matrix = np.array([[0, 10, 5], [10, 0, 3], [5, 3, 0]], dtype=float) n = matrix.shape[0] totals = matrix.sum(axis=1) angles = np.cumsum(np.insert(totals / totals.sum() * 2 * np.pi, 0, 0)) fig, ax = plt.subplots(figsize=(5, 5)) colors = ["#818CF8", "#10B981", "#F43F5E"] for i in range(n): theta = np.linspace(angles[i], angles[i + 1], 50) ax.plot(np.cos(theta), np.sin(theta), color=colors[i], lw=4) for i in range(n): for j in range(i + 1, n): w = (matrix[i, j] + matrix[j, i]) / matrix.sum() * 2 start = angles[i] + 0.05 end = angles[j] + 0.05 theta = np.linspace(start, end, 100) rad = 1.0 + w * 0.4 ax.fill(np.cos(theta) * rad, np.sin(theta) * rad, color=colors[i], alpha=0.3) theta2 = np.linspace(end, start + 2 * np.pi, 100) ax.fill(np.cos(theta2) * rad, np.sin(theta2) * rad, color=colors[j], alpha=0.3) ax.set_aspect("equal") ax.set_axis_off() ax.set_title("Chord-like relations") plt.show() def radar_demo(): dims = ["accuracy", "recall", "latency", "memory", "robust"] n = len(dims) angles = np.linspace(0, 2 * np.pi, n, endpoint=False).tolist() angles += angles[:1] model_a = [0.85, 0.72, 0.90, 0.66, 0.78] model_b = [0.78, 0.88, 0.60, 0.82, 0.90] values_a = model_a + model_a[:1] values_b = model_b + model_b[:1] fig, ax = plt.subplots(figsize=(5, 5), subplot_kw=dict(polar=True)) ax.plot(angles, values_a, label="Model A", color="#818CF8") ax.fill(angles, values_a, color="#818CF8", alpha=0.25) ax.plot(angles, values_b, label="Model B", color="#10B981") ax.fill(angles, values_b, color="#10B981", alpha=0.25) ax.set_xticks(angles[:-1]) ax.set_xticklabels(dims) ax.set_ylim(0, 1) ax.set_title("Radar multi-dim") ax.legend(loc="upper right", bbox_to_anchor=(1.3, 1.0)) plt.show() if __name__ == "__main__": sankey_demo() chord_demo() radar_demo()
links = [{"source": 0, "target": 1, "value": 30}, ...]用字典列表存储连接关系,和 D3 的 links 数组结构一致,source/target 用节点索引表示。width = l["value"] / total * 0.6流量归一化后乘以绘图区宽度,得到连接条宽度,实现“边宽正比于值”。PathPatch(path, lw=0, color=color_map(...), alpha=0.6)用 Matplotlib 的低层 Path 绘制自定义多边形连接条,类似于 D3 中path元素的d属性。totals = matrix.sum(axis=1)按行求和得到每个节点的总流量,这是和弦图圆弧角度分配的数据基础。angles = np.cumsum(...)对归一化后的总量做累积求和,把“占比”转换为圆弧的起始角度。这等价于 D3 chord 布局内部的角度累积逻辑。model_a + model_a[:1]在雷达图数据末尾追加第一个点,让折线闭合围成多边形,对应 D3line().closed(true)的效果。ax.plot(angles, values_a, color="#818CF8")绘制模型 A 的雷达轮廓,颜色与 D3 示例保持同一风格。
常见误区
误区“桑基图两个节点之间连线越宽,就代表它们在屏幕上离得越远或越近吧?毕竟画图的时候宽度是用坐标差算出来的。”
正解桑基图中边的宽度只由数据字段 value 决定,与节点之间的视觉距离无关。布局算法确实会计算坐标,但那是为了决定节点列位置和避免重叠,边的粗细严格由流量值映射而来。就像水管粗细由实际流量决定,而不是由两个水表之间的距离决定。
为何常见:初学者看到桑基图的连接条形状像梯形,误以为宽度来自两端坐标差。实际上宽度是数据映射的结果,坐标差只影响“哪一列”和“弯多少”。
误区“和弦图要求 matrix[i][j] 和 matrix[j][i] 必须相等,不相等就画不出来吧?”
正解d3.chord() 本身不要求对称矩阵,它可以直接处理有向关系,把 i→j 和 j→i 分别画成两段不同半边的弦。只是在很多场景中,我们为了同时显示“双向总量”才手动做对称化处理。矩阵对称与否,取决于你想表达“有向流”还是“无向关系”。
为何常见:网上多数教程为了方便演示,都从对称矩阵或“单向合并”开始讲,导致读者误以为对称是前提条件。
误区“雷达图围出来的面积越大,说明这个实体越优秀,直接用面积排序不就行了?”
正解雷达图面积大只代表该实体在“已选指标上整体数值高”。如果有一个关键指标(比如安全性)缺失或权重很低,面积大可能是虚胖。而且当指标量纲不同时,面积大小还受归一化方式影响。雷达图的正确用法是观察“形状”——哪个维度凹陷、哪个维度凸起,而不是只比面积。
为何常见:人类视觉天然对“面积”敏感,看到大块填充色容易形成“大=好”的直觉,忽略了多边形的形状细节。
误区“d3-hierarchy 不就是用来画树状图的吗?跟桑基图、和弦图有什么关系?”
正解d3.hierarchy() 的核心是给嵌套数据添加层级元信息(parent/children/depth/height),它是许多布局的公共前置阶段。桑基图的列层级、和弦图的节点分组、甚至坐标轴的树状分组,都可以由 hierarchy 统一构建。它和 d3.tree() 是“数据预处理”与“具体视觉布局”的关系——先有 hierarchy,再有各种布局。
为何常见:D3 官方示例中 hierarchy 最常配 tree 展示,导致初学者把它等同于“树状图专用工具”,忽略了它在数据组织层面的通用性。
误区“D3 就是玩 SVG 的,所有图表用 SVG 准没错,大不了卡一点。”
正解桑基图的数百根连接条、和弦图的数千条带状区域,在 SVG 中会生成大量 DOM 节点,交互时极易卡顿。D3 支持 canvas 渲染,d3.sankey 布局的输出可以直接交给 canvas 2D 的 ctx.beginPath() 和 ctx.stroke() 来绘制。工程实践中,大规模静态图形用 canvas,少量高交互元素用 SVG 叠加,是最稳妥的策略。
为何常见:D3 教程大多从 SVG 入手,因为 DOM 结构便于调试和样式控制。但学到高级图表时数据量成倍增长,沿用旧习惯就容易踩性能坑。
面试题
Q1. 桑基图中,连接条(link)的宽度直接由哪个字段决定?
- A.source 节点的坐标
- B.target 节点的坐标
- C.value 字段的数值← 正确答案
- D.节点的总数
答案:C。桑基图的视觉编码核心是“边宽正比于流量数值”,即 value 字段。source/target 坐标只决定节点的位置布局,节点总数只影响分列数量,都不直接决定连接条的粗细。
Q2. 和弦图的输入数据本质上是什么结构?
- A.一棵树
- B.一个 n×n 的矩阵← 正确答案
- C.一个由坐标点组成的数组
- D.一个 CSR 稀疏格式对象
答案:B。和弦图的核心数据结构是 n×n 的关系矩阵 matrix[i][j],表示第 i 行实体与第 j 列实体之间的流量或关系强度。树结构是 hierarchy 的输入,坐标数组一般是折线图的输入。
Q3. 在手写和弦图布局时,将关系矩阵对称化(m_ij = r_ij + r_ji)的目的是什么?
- A.让矩阵变成方阵
- B.将单向流量合并为双向总量← 正确答案
- C.让每个节点在一段圆弧中同时表达给出与接收← 正确答案
- D.减少矩阵的维度数量
答案:BC。对称化把 i→j 和 j→i 的流量相加,得到无向的总关系强度,让每个节点在同一段圆弧上同时体现“给出”和“接收”。原始矩阵本身就是方阵,因此 A 不成立;对称化不会减少维度,D 错误。
Q4. 在雷达图中,若某个指标需要归一化到 0~1 区间,正确的映射方式是?
- A.(v - min) / (max - min)← 正确答案
- B.v / max * 100
- C.(max - v) / (max - min)
- D.v / sum(v)
答案:A。(v - min) / (max - min) 能将任意数值线性映射到 0~1,是雷达图半径映射的标准做法。B 只适合最小值为 0 的场景;C 是反向归一化,会大小颠倒;D 得到的是占比而非 0~1 区间。
Q5. D3 中 d3.hierarchy() 的主要作用是什么?
- A.直接在页面上绘制树状图
- B.给嵌套数据添加 depth、height、parent、children 等层级元信息← 正确答案
- C.将矩阵数据转换为和弦图
- D.计算桑基图中的连接条宽度
答案:B。d3.hierarchy() 是布局的“前置处理器”,它把嵌套 JSON 数据转换成带层级元信息的对象,供 tree、treemap、partition 等布局使用。绘制树状图还需要下一步调用 d3.tree(),和弦图与桑基图也不由 hierarchy 直接计算。
Q6. 下列关于桑基图与和弦图的区别,正确的是?
- A.桑基图适合多对多双向关系,和弦图适合单向层级流
- B.桑基图适合流动分配,和弦图适合成对关系矩阵← 正确答案
- C.两者都要求输入对称矩阵
- D.两者都必须用 canvas 渲染
答案:B。桑基图擅长表达“从哪来、到哪去”的流量分配,边宽编码流量;和弦图擅长表达 n 个实体之间的成对关系,矩阵驱动弦宽。桑基图不需要对称矩阵,和弦图也不强制;二者用 SVG 或 canvas 皆可。
Q7. D3 中选择集 .enter() 的语义是什么?
- A.更新已有 DOM 元素
- B.为新增数据创建对应的 DOM 元素← 正确答案
- C.删除多余的 DOM 元素
- D.绑定鼠标事件
答案:B。D3 的数据绑定三件套 enter/update/exit 中,.enter() 专门处理“数据比 DOM 多”的情况,为每条新增数据补建元素。.exit() 处理多余元素,.append() 是 enter 选择集上常用的创建操作。
Q8. 在构建大型桑基图时,下列哪些做法有助于提升渲染性能?
- A.使用 canvas 渲染大量连接条← 正确答案
- B.将节点数压缩到合理粒度← 正确答案
- C.为每条边创建独立的 SVG group 节点
- D.合并大量无交互需求的静态路径← 正确答案
答案:ABD。canvas 适合大规模图形,合理聚合节点能降低连接数量,合并静态路径能减少 DOM 节点数量——这些都是有效的优化手段。为每条边单独建 group 反而增加 DOM 数量,降低性能。
Q9. 和弦图中,一段圆弧的角度跨度与什么成正比?
- A.该节点的出度之和
- B.该节点的入度之和
- C.该节点所有关系量(出度+入度)的总和← 正确答案
- D.连接到该节点的弦的数量
答案:C。和弦图里每个节点的圆弧跨度与该节点参与的所有关系总量成正比(出度与入度合并后的总量)。只看出度或入度都会漏掉另一半,弦的数量无法反映量的差异。
Q10. 雷达图中,当某个轴的数值需要“越小越好”(如延迟、错误率)时,建议如何处理?
- A.直接映射原始值,越小越靠近圆心
- B.先做反向归一化,让“更好”对应更大的半径← 正确答案
- C.去掉这个轴
- D.改用柱状图
答案:B。对于“越小越好”的指标,直接映射会让优秀表现凹陷在圆心附近,无法直观体现优势。更适合的做法是反向归一化(如用 1 - v' 或 (max-v)/(max-min)),让“更好”对应更大的半径,保持所有轴“越往外越好”的一致性,方便对比。