d3 selection 元素修改(Modifying Elements)完全指南:attr、classed、style、append 等方法的用法与实现细节
本篇基于 docs/d3-selection/modifying.md 系统讲解 d3 中「修改元素」这一核心环节:完成选择后,如何使用 selection 的 attr、classed、style、property、text、html、append、insert、remove、sort、order、raise、lower、create、creator 等方法精确操作 DOM,理解函数式取值约定(datum/index/nodes)、空值语义、命名空间规则以及选择集不可变(immutable)的设计原则。掌握这些方法后,你就能写出与数据绑定(data join)无缝衔接、可链式复用的 d3 图形代码。
1. 选择之后:用链式调用修改元素
选中元素后,selection 本身就是操作 DOM 的入口。官方文档给出的最简示例是给当前文档中所有段落设置 class 与颜色样式:
d3.selectAll("p")
.attr("class", "graf")
.style("color", "red");
selection 方法通常返回当前选择或一个新的选择,因此可以通过**方法链(method chaining)**在一个选择上简洁地施加多个操作。上例等价于:
const p = d3.selectAll("p");
p.attr("class", "graf");
p.style("color", "red");
一个关键的 API 设计约束(见 modifying.md 原文)是:选择集是不可变的。所有会影响「哪些元素被选中(或其顺序)」的方法都返回新的 selection,而不是修改当前 selection。但要注意:元素本身必然是可变的——因为 selection 驱动的就是对文档的变换!换句话说,sort() 不会改变原 selection,而 style() 不改变 selection 的组成,却直接改变 DOM。
在 docs/d3-selection/selecting.md 中还约定了一种排版惯例:返回当前选择的方法(如 attr)使用四格空格缩进,返回新选择的方法(如 append)使用两格空格缩进,让链中的上下文切换在视觉上「跳出来」:
d3.select("body")
.append("svg")
.attr("width", 960)
.attr("height", 500)
.append("g")
.attr("transform", "translate(20,20)")
.append("rect")
.attr("width", 920)
.attr("height", 460);
这些方法在本仓库中的来源:d3 主包是一个聚合包,package.json 声明了 "d3-selection": "^3.0.0" 依赖,并由 src/index.js 中 export * from "d3-selection" 一行将其全部 API 重新导出,因此 d3.attr 相关的 selection 方法直接可用。各方法的实现源码位于 d3-selection 子项目的 src/selection/*.js 文件(如 attr.js、classed.js、style.js、append.js 等),原文档每个方法小节开头的 Source 链接即指向这些文件。
2. 修改属性:selection.attr(name, value)
attr 方法 支持三种调用形态:
// 设置:常量值
selection.attr("color", "red")
// 读取:返回选择中第一个(非 null)元素该属性的当前值
selection.attr("color") // "red"
规则要点:
- 指定了
value时,将所选元素上指定名称的属性设置为该值,并返回当前 selection(可继续链式调用); - 若
value是常量,所有元素获得相同的属性值;若value是函数,则按顺序对每个被选元素求值,函数参数依次为当前数据(d)、当前索引(i)、当前组(nodes),this指向当前 DOM 元素(即nodes[i]),函数返回值用于设置每个元素的属性。这是 d3 中最重要的通用约定,classed、style、text、append等方法全部复用同一套签名; - 传入
null会移除该属性; - 不传
value时,返回第一个非 null 元素的属性当前值——这一「读取」形态通常只在你知道选择恰好只含一个元素时才实用; - 指定的
name可以带命名空间前缀,例如xlink:href表示 XLink 命名空间中的href属性。已注册前缀见第 7 节的 namespaces 说明。
3. 管理 CSS 类:selection.classed(names, value)
classed 方法 通过设置 class 属性或修改 classList 属性来分配/取消分配 CSS 类:
selection.classed("foo", true)
规则要点:
names是空格分隔的类名串。要给元素同时分配foo和bar两个类:
selection.classed("foo bar", true)
value为真值(truthy)时所有元素获得指定类,否则取消分配;value可以是函数,同样按 (d, i, nodes)、this为当前元素的约定逐个求值,返回值决定每个元素是否拥有该类。例如随机半数以做交替着色:
selection.classed("foo", () => Math.random() > 0.5)
- 不传
value时,返回第一个非 null 被选元素是否带有指定类(true/false):
selection.classed("foo") // true, perhaps
4. 修改样式:selection.style(name, value, priority)
style 方法 的设置/读取语义与 attr 完全一致:常量或函数赋值、null 移除样式、省略 value 时读取第一个非 null 元素的当前值。当前值的定义是:若元素存在内联样式则返回内联值,否则返回其计算值(computed value)。
style 多出一个可选参数 priority,取值为 null 或字符串 "important"(不带感叹号),用于生成 !important 声明。
文档中有一段值得注意的警告(modifying.md 原文):
与许多 SVG 属性不同,CSS 样式通常带有单位。例如
3px是合法的 stroke-width 值,而3不合法。部分浏览器会隐式为数值补上px单位,但并非所有浏览器都这样做:IE 甚至会抛出 “invalid arguments” 错误。
这条警告的实战含义是:对 SVG 图形属性(cx、r、stroke-width 的 attribute 形式)可以放心用裸数值;而一旦改用 style() 设置,就应显式带上单位,避免跨浏览器不一致。
5. 修改属性之外的 DOM 属性:selection.property(name, value)
property 方法 用于处理属性(attribute)和样式都无法触达的 HTML 特殊属性,最典型的就是表单文本域的 value 与复选框的 checked 布尔量:
selection.property("checked", true)
selection.property("checked") // true, perhaps
规则与前几个方法一致:常量或函数(d, i, nodes 签名)赋值、null 删除该属性、省略 value 时读取第一个非 null 元素的属性值。attr/style/property 的选型经验法则:
| 目标 | 方法 | 典型场景 |
|---|---|---|
| XML 属性、SVG 图形属性 | attr() |
width、xlink:href、d |
| 内联/计算样式 | style() |
fill、color(注意单位) |
| DOM 属性对象成员 | property() |
checked、value、selected |
6. 替换内容:text 与 html
selection.text(value)
text 方法 设置元素的文本内容(text content),会替换所有现有子元素:
selection.text("Hello, world!")
selection.text() // "Hello, world!"
常量或函数(同一 d/i/nodes 约定)赋值;null 清空内容;省略 value 时返回第一个非 null 元素的文本内容。
selection.html(value)
html 方法 设置元素的内层 HTML(inner HTML),同样替换现有子元素:
selection.html("Hello, <i>world</i>!")
selection.html() // "Hello, <i>world</i>!"
文档对此方法给出了明确的使用边界:
- 数据驱动的内容应改用
append/insert;html的定位是「需要一小段富格式 HTML」的场景; selection.html仅在 HTML 元素上受支持。SVG 元素及其他非 HTML 元素没有 innerHTML 属性,因此与html不兼容;需要把 DOM 子树转为文本时可考虑 XMLSerializer,或借助 innersvg 这类 polyfill 为 SVG 元素补上 innerHTML 支持。
7. 插入与创建元素:append、insert、create、creator
selection.append(type)
append 方法 把新元素作为每个被选元素的最后一个子节点追加;若当前是 enter selection(进入选择,见 docs/d3-selection/joining.md),则插入到该进入组内下一个兄弟节点之前——这一行为使插入顺序与新绑定数据保持一致。但要注意:若更新(update)元素之间发生了顺序变化(新数据顺序与旧数据不一致),仍可能需要调用 order() 修正。
type 为字符串时即标签名,等价于每步展开:
d3.selectAll("div").append("p");
// 等价于
d3.selectAll("div").append(() => document.createElement("p"));
// 进一步等价于
d3.selectAll("div").select(function() {
return this.appendChild(document.createElement("p"));
});
方法返回一个包含被追加元素的新选择;每个新元素按 select 的方式继承父元素的已有数据。type 也可以是函数,按 (d, i, nodes) 约定求值并返回要追加的元素(通常是新建的,也可以返回已有元素)。
命名空间规则:名称可带前缀,如 svg:text 表示 SVG 命名空间的 text;不带前缀时命名空间从父元素继承,或者名称本身是已知前缀时按对应命名空间解析(例如 svg 隐含 svg:svg)。
selection.insert(type, before)
insert 方法 在每个被选元素中,插入到匹配 before 选择器的第一个元素之前:
d3.selectAll("div").insert("p");
// 等价于
d3.selectAll("div").insert(() => document.createElement("p"));
// 进一步等价于
d3.selectAll("div").select(function() {
return this.insertBefore(document.createElement("p"), null);
});
要点:
before选择器如:first-child可实现「前置」效果;before缺省时默认为null(等价于 append 的位置);type与before都支持函数形式,type函数返回要插入的元素,before函数返回插入目标之前的子元素;- 与 append 相同,返回新选择、继承父元素数据、支持命名空间前缀;若要与绑定数据保持一致的顺序,优先用
append。
remove 与 clone
remove 方法 把所选元素从文档中移除,返回当前 selection(被移除的元素,此时已脱离 DOM)。目前没有专门 API 把移除的元素「加回」文档,但可以传函数给 append/insert 重新加入。
clone 方法 在所选元素之后插入其克隆体,返回新增克隆的选择;deep 为真值时连同后代节点一起克隆,否则只克隆元素本身。文档给出了其等价实现,可直接看出底层就是 cloneNode:
selection.select(function() {
return this.parentNode.insertBefore(this.cloneNode(deep), this.nextSibling);
});
create 与 creator
create 方法 给定元素名,返回包含一个尚未接入文档的元素的单元素选择:
d3.create("svg") // 等价于 svg:svg
d3.create("svg:svg") // 更显式
d3.create("svg:g") // SVG G 元素
d3.create("g") // HTML 中的 G(未知)元素
注意 create 默认假设 HTML 命名空间,因此创建 SVG 等非 HTML 元素必须显式指定命名空间前缀——d3.create("g") 得到的是 HTML 里一个未知的 G 元素,而不是 SVG 的 g。
creator 方法 给定元素名,返回一个「以 this 为父元素创建该元素」的函数。它被 append 和 insert 内部用于创建新元素,即:
selection.append("div");
// 等价于
selection.append(d3.creator("div"));
这也解释了命名空间的第二层继承逻辑:creator 生成元素时会参考父元素所在命名空间,从而让 svg 父级下 append("g") 自动创建 SVG 的 g 元素。
命名空间前缀的注册表见 docs/d3-selection/namespaces.md,其初始值为:
{
svg: "http://www.w3.org/2000/svg",
xhtml: "http://www.w3.org/1999/xhtml",
xlink: "http://www.w3.org/1999/xlink",
xml: "http://www.w3.org/XML/1998/namespace",
xmlns: "http://www.w3.org/2000/xmlns/"
}
可按需向该表追加前缀,从而在其他命名空间中创建元素或属性。典型的混用场景是把 HTML 塞进 SVG 的 foreignObject:
d3.create("svg")
.append("foreignObject")
.attr("width", 300)
.attr("height", 100)
.append("xhtml:div")
.text("Hello, HTML!");
8. 调整文档顺序:sort、order、raise、lower
这四个方法不改变选择的内容,只重排元素在父节点中的位置,在更新可视化(数据顺序变了但 DOM 要跟上)时必不可少。
selection.sort(compare)
sort 方法 返回一个新 selection,其中每组元素按 compare 函数排序;排序后按 order 的语义重新插入元素以匹配结果顺序。比较函数(默认使用 d3-array 的 ascending,见 docs/d3-array/sort.md)接收两个元素的数据 a、b,返回负值表示 a 在前、正值表示 a 在后、零表示相等(顺序任意)。
selection.order()
order 方法 把元素重新插入文档,使每组的文档顺序与选择顺序一致。若数据已经有序,它就等价于调用 sort,但速度快得多——因此常见写法是先改变节点数据的顺序,再轻量地 order()。
selection.raise() 与 selection.lower()
raise 方法 依次把每个被选元素重新插入为其父节点的最后一个子节点,等价于:
selection.each(function() {
this.parentNode.appendChild(this);
});
lower 方法 相反,把元素重插为第一个子节点,等价于:
selection.each(function() {
this.parentNode.insertBefore(this, this.parentNode.firstChild);
});
在 SVG 中「后绘制者覆盖先绘制者」,所以 raise/lower 是最直接的图层控制手段(例如把选中高亮的柱形置顶);而在 HTML 布局中它们影响的是兄弟节点排列顺序。
9. 小结:方法总览与适用边界
| 方法 | 返回 | 空值语义 | 关键注意点 |
|---|---|---|---|
attr(name, value) |
当前 selection | 设值时 null 移除属性 |
支持命名空间前缀(如 xlink:href) |
classed(names, value) |
当前 selection | — | 空格分隔多类名;true/false 或函数 |
style(name, value, priority) |
当前 selection | 设值时 null 移除样式 |
priority 取 null/"important";注意单位(3px vs 3) |
property(name, value) |
当前 selection | 设值时 null 删除属性 |
用于 checked、value 等属性/样式触达不了的成员 |
text(value) |
当前 selection | null 清空内容 |
读取返回首个非 null 元素值 |
html(value) |
当前 selection | null 清空内容 |
仅 HTML 元素支持;SVG 请用 XMLSerializer 等 |
append(type) |
新 selection | — | enter selection 中插在组内下一兄弟前;支持函数与命名空间 |
insert(type, before) |
新 selection | — | before 缺省为 null;数据有序场景优先 append |
remove() |
当前 selection(已脱离 DOM) | — | 重加回文档用函数形式的 append/insert |
clone(deep) |
新 selection(克隆体) | — | deep 决定是否连同后代克隆 |
sort(compare) |
新 selection | — | 默认 ascending,按数据比较 |
order() |
当前 selection | — | 已排序数据时比 sort 快得多 |
raise() / lower() |
当前 selection | — | SVG 中即图层置顶/置底 |
create(name) |
单元素选择(未接入文档) | — | 默认 HTML 命名空间,SVG 需写 svg: 前缀 |
creator(name) |
元素创建函数 | — | append/insert 内部创建元素的基础 |
两条贯穿全文的设计原则:其一,选择集不可变、元素可变——改变组成(append/insert/sort)返回新选择,改变外观/内容(attr/style/text)原地作用于 DOM;其二,所有函数式取值共享 (d, i, nodes) + this 约定,一旦掌握,全部修改方法的心智模型是统一的。配合 docs/d3-selection/selecting.md 的选择、docs/d3-selection/joining.md 的数据绑定与 docs/d3-selection/events.md 的事件监听,即构成 d3 数据驱动可视化的完整操作闭环。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00