跳转至

加载与解析数据 — d3-fetch 与 d3-dsv

这一章讲什么: 网页上的图,数据几乎总在外面某个文件里。这一章讲 D3 把文件搬进程序的两套工具: d3-fetch(取:各种格式一步加载)和 d3-dsv(解:表格字符串和数组之间的双向转换)。 读完你会拿到数据管线的完整入口——后面每一张图,第一步都是这里。

1. 顶层全景:数据进门的四道手续

  文件(CSV / JSON / SVG / 图片 / 二进制)
      │  ① 定位:相对或绝对 URL
      ▼
  网络请求(fetch,异步 → Promise)
      │  ② 取回:d3-fetch 按格式分派
      ▼
  原始内容(字符串 / Blob / 文档对象)
      │  ③ 解析:能原生解析的直接返回对象,表格走 d3-dsv
      ▼
  JavaScript 数组或对象 ←—— ④ 加工:行函数逐行过滤、转换、补字段
      │
      ▼
  绑定到元素,画图(下一章)

  图说:①②由 d3-fetch 管,③④里表格相关的部分由 d3-dsv 管。

本章的主线例子:2024 巴黎奥运会奖牌榜 CSV。文件前四行长这样(原书照录)1:

  Rank,Country,Gold,Silver,Bronze
  1,United States,40,44,42
  2,China,40,27,24
  3,Japan,20,12,13

全章就围绕这一个文件反复练手:取文本 → 解析成对象数组 → 逐行加工 → 转成别的形状 → 再格式化回字符串。 每步旁边都有具体的解析结果。

2. 核心原理

2.1 先看清原生 fetch:两步两 Promise

最底层的加载是浏览器自带的 fetch()。注意它有两层等待, 这也是所有 D3 加载函数返回 Promise 的原因2:

  fetch(dataFile)                        // ① 发 GET 请求,马上返回 Promise(响应头先到)
    .then(response => response.text())   // ② 响应到了,取正文(正文传输又是个 Promise)
    .then(text => console.log(text))     // ③ 正文到了,才能用

  // async/await 写法等价:
  const response = await fetch(dataFile);
  console.log(await response.text());

第一层等「服务器答不答应、响应头是什么」,第二层等「正文传完」。 两层都 await 了,才算真的拿到数据。 D3 做的事,就是把这两层加上「按格式解析」打包成一步。

2.2 d3-fetch:一步取回 + 解析的加载函数

d3-fetch 的函数家族,每个对应一类资源3:

函数 返回什么 典型用途
d3.text(url) 字符串 想自己控制解析时
d3.csv(url) / d3.tsv(url) 对象数组(首行表头变属性名) 表格数据,最常用
d3.json(url) JS 对象/数组 JSON、GeoJSON
d3.svg(url) / d3.xml(url) / d3.html(url) DOM 文档对象 加载现成矢量图、网页片段
d3.image(url) HTMLImageElement(图片对象) 预加载图片
d3.blob(url) / d3.buffer(url) Blob / ArrayBuffer 原始二进制

所有函数都接受可选的第二个参数:HTTP 配置对象(请求头、方法等),表格类函数还支持更多参数(下面讲)4。

主线例子的第一步: 把奖牌 CSV 一步解析成对象数组——

  const data = await d3.csv(dataFile);

  // data 现在是:
  [
    {Rank: "1", Country: "USA", Gold: "40", Silver: "44", Bronze: "42"},
    {Rank: "2", Country: "China", Gold: "40", Silver: "27", Bronze: "24"},
    /* … */
  ]

  图说:每个对象对应一行,每个表头对应一个属性。d3.csv 自动做了「取文本 + csvParse 解析」。

表头名单独放在结果的 columns 属性上:data.columns 是 ['Rank', 'Country', 'Gold', 'Silver', 'Bronze']——画表格、动态取列时要用它5。

JSON 同理:await d3.json(url) 拿到的就是和文件结构一致的 JS 对象; d3.svg() 加载一个 SVG 文件后返回 XML 文档对象,可以调 DOM 方法(如 getElementById)取出里面的元素, 再挂到自己的页面上;d3.image() 预加载图片,成功后拿到图片对象,能读出 URL 和尺寸, 再 <image> 标签挂进 SVG 显示6。

2.3 行函数:解析途中逐行加工

d3.csv/d3.tsv/d3.dsv 的最后一个参数可以是一个函数,它在每一行解析出来时被调用一次, 收到 (行对象, 行号),返回值替换成这一行的最终结果7。

主线例子的第二步:给每行算奖牌总数——

  const data = await d3.csv(dataFile, row => {
    row.Total = +row.Gold + +row.Silver + +row.Bronze;
    return row;
  });

  // 每行多了 Total 字段:
  // {Rank: "1", Country: "USA", Gold: "40", Silver: "44", Bronze: "42", Total: 126}
  // {Rank: "2", Country: "China", …, Total: 91}

  图说:+row.Gold 的加号是把字符串转成数字——不加的话 "40"+"44" 会变成 "4044" 拼接。

这里有个贯穿全书的坑,原书特意点破:表格解析器把值全当字符串。 +row.Gold 的那个 + 是「转数字」的一元运算符; 忘了转,加法就变字符串拼接——"40" + "44" 得到 "4044",不是 848。

不想逐字段加 +,可以把 D3 自带的 d3.autoType 直接当行函数用: 它自动把「长得像数字、日期、布尔值的字符串」转成对应类型——

  const data = await d3.csv(dataFile, d3.autoType)

  // 这次数字真的是数字了:
  // {Rank: 1, Country: "USA", Gold: 40, Silver: 44, Bronze: 42}

行函数永远是最后一个参数。如果请求需要带认证头(比如 OAuth(一种令牌认证机制)的 bearer token(访问令牌)), HTTP 配置对象要插在 URL 和行函数中间9。

2.4 d3-dsv:解析器的完整家族

d3.csv() 是「取 + 解」的合体。如果文件已经以字符串形式在手(或者你想保留二维结构), 可以用 d3-dsv 模块的解析器单独「解」。原书给了一张函数表,核心是这几个10:

函数 输入 → 输出
d3.dsvFormat(分隔符) 造一个自定义分隔符的解析器实例(如分号 ";")
parser.parse(字符串) DSV 字符串 → 对象数组(首行当表头)
parser.parseRows(字符串) DSV 字符串 → 数组的数组(所有行平权,表头也是数据)
d3.csvParse / d3.tsvParse 上面两者的现成逗号/制表符版本,不用造实例

parse 和 parseRows 的差别,就是本章主线例子接下来要走的分岔:

  同一个 CSV:

  data1 = d3.csvParse(csv)          data2 = d3.csvParseRows(csv)
  ┌─ 数组 of 对象 ─────────────┐    ┌─ 数组 of 数组 ───────────────┐
  │ {Rank:"1", Country:"USA",  │    │ ["Rank","Country","Gold",…]  │ ← 表头成了第 0 行
  │  Gold:"40", …}             │    │ ["1","USA","40","44","42"]   │
  │ {Rank:"2", …}              │    │ ["2","China","40","27","24"] │
  └────────────────────────────┘    └──────────────────────────────┘

  取「日本的金牌数」:
  data1[2].Gold                   data2[3][2]
  (按行号 + 列名)                 (按行号 + 列号,表头占一行所以行号 +1)

  图说:对象数组适合「按名字取列」;数组的数组适合「不看名字通吃」——比如把任何 CSV 直接渲染成 HTML 表格。

选择的原则原书说得很具体:要按列名取特定值,用对象数组; 要把任意 CSV 渲染成表格或电子表格样式(列名不重要),用数组的数组11。

后面跟的 HTML 表格例子展示了第二种结构的威力:data2[0] 是表头行、data2.slice(1) 是数据行; 表头绑给 <th>,数据行绑给 <tr>,每行内部的数组再绑给 <td>—— 嵌套绑定(二维数组的两个维度各自 join 一次)第一次在这里出现,第 05 章会展开12。

2.5 tidy 数据:行函数的主战场

行函数最常干的一件大事:把数据从宽表(wide)重组成整齐表(tidy/long)。 这两个词是数据可视化行业的行话,原书在这里做了正式交代13:

宽表(wide) 整齐表(tidy/long)
长相 像电子表格,一列一个类别 一行一个观测值
奖牌例 Gold, Silver, Bronze 各占一列 medal 列(值是 Gold/Silver/Bronze)+ count 列
行数 一个国家一行 一个国家三行(每类奖牌一行)
什么时候好 人眼看、电子表格 大多数 D3 工具

原书给的转换代码,用行函数一行返回三个对象(三类奖牌各一个)—— 注意返回数组后外面还要 .flat() 把行数组摊平14:

  const array = d3.csv(dataFile, (row, i, cols) => {
    const medals = cols.slice(2,5);            // Gold、Silver、Bronze 三列
    return medals.map(m => ({country: row.Country, medal: m, count: +row[m]}));
  });
  console.log(array.flat());

原书的经验法则:大多数 D3 工具偏好 tidy 格式;少数(如堆叠图的原始数据)反而要宽表15。 这个判断在第 12 章(堆叠图)会兑现。

2.6 反向:把数组格式化回字符串

解析器的另一半能力:格式化(数组 → DSV 字符串),调试或回传服务器时用。 还是同一个解析器实例,方法名对称:parse 对 format,parseRows 对 formatRows16。

  const scParser = d3.dsvFormat(";");     // 造个分号分隔的解析器

  scParser.formatRows([["abc","def","ghi"],["jkl","mno","pqr"]])
  // → "abc;def;ghi\njkl;mno;pqr"

  scParser.format([{name:"Jupiter",distance:5.2},{name:"Saturn",distance:9.54}])
  // → "name;distance\nJupiter;5.2\nSaturn;9.54"   ← 对象的键名自动变成表头

  d3.csvFormat(objects)                    // 逗号版快捷函数,不用造实例
  // → "name,distance\nJupiter,5.2\nSaturn,9.54"

format() 吃对象数组、把键名当表头;formatRows() 吃字符串数组的数组、不管表头17。

2.7 多源加载:Promise.all

现实场景:一张地图要两个文件——GeoJSON 轮廓 + 城市坐标 CSV,两个都到了才能画。 每个加载函数返回的都是 Promise,Promise.all() 把多个 Promise 合成一个: 全部兑现才继续,结果按传入顺序放进一个数组18:

  const shapesPromise = d3.json('../data/world-lowres.geojson');
  const pointsPromise = d3.dsv(';', '../data/cities15000.csv',
    row => ({name: row.asciiname,
             coords: [+row.longitude, +row.latitude],   // 经度在前、纬度在后
             pop: +row.population}));

  const [shapes, points] = await Promise.all([shapesPromise, pointsPromise]);

  图说:解构赋值一次取出两个结果——shapes 是地图轮廓,points 是城市点,
  顺序和 Promise.all 数组里的顺序一致。

另一个典型场景:预加载图片。交互图按需显示图片时,现点现下会卡; 提前把一批图片全部 d3.image() 出来再 Promise.all 等齐,浏览器缓存里就有了19:

  const promises = entries.map(entry =>
    d3.image(`../data/images/${entry.name}.png`)
      .then(img => entry.image = img.src)   // 成功:给条目挂上图片 URL
      .catch(() => {})                     // 失败:安静跳过(不拖累整批)
  );
  await Promise.all(promises);

  d3.select("svg").selectAll("image")
    .data(entries.filter(d => d.image))    // 只取有图的条目
    .join("image")
      .attr("href", d => d.image)
      .attr("x", (d,i) => 10 + i*90).attr("y", 10)
      .attr("height", 80).attr("width", 80);

.catch(() => {}) 是个小但重要的手法:单个文件 404 不该让整批加载失败—— 返回空,后面用 filter 把没图的剔掉20。

2.8 在线教程:卫星尺寸对比图(第一部分)

章末的在线教程把本章工具用在一个完整可视化上:比较一颗行星的各卫星大小。 流程:规划和布局 → 定义边距和比例尺 → 预加载卫星图片 → 过滤数据 → 把形状和文字绑上去。 第二部分(接在第 05 章拆解章之后)再升级成交互应用:动态换行星、控制面板、悬浮提示21。 教程共九步、每步有带注释的代码,在仓库 Part2/Chapter05/Tutorial/; 成品也有一个公开的 Observable 笔记本可以在线跑着看22。

3. 作者的判断与证据

判断 性质 依据
「大多数 D3 工具偏好 tidy 格式」 作者经验法则 给了反例方向(少数工具要好宽表),第 12 章兑现15
「按列名取值用对象数组、通吃渲染用数组的数组」 使用建议 配了取「日本金牌数」的对照写法11
「行函数是最后参数、HTTP 配置插中间」 接口事实 给了带 bearer token 的完整例子9

4. 边界与局限

  • JSON/SVG/HTML/XML 没有专用解析器——原生 JavaScript 就能解析,这是原书明确的设计说明23。
  • 错误处理讲得轻:多源加载给了 .catch(() => {}) 的静默跳过模式, 但加载失败时的用户反馈(重试、错误界面)全书都没有展开。
  • CSV 的「字符串陷阱」只靠 + 和 autoType 两处提及;更复杂的类型推断(货币、千分位)不在书里。
  • 在线教程不在纸质书里,只有入口和成品图22。

判断(我们的,不是书里的): d3-dsv 的设计值得注意的对称性—— parse/format 处理对象数组(带表头语义),parseRows/formatRows 处理纯二维数组, 同一个解析器实例双向通用。这个「解析器和格式化器共用一个对象」的收窄设计, 比很多语言里解析与序列化分两套 API 的做法更容易记错的可能性更小。 如果错,会错在: 共用一个实例意味着配置(分隔符、转义)在解析和格式化间强耦合—— 需要「按 A 分隔解析、按 B 分隔输出」的场景就得造两个实例,书里没演示这个边角。

5. 可带走的

本章主线例子,一行走完: 奖牌 CSV(表头 Rank/Country/Gold/Silver/Bronze)→ d3.csv() 成对象数组 (值是字符串!)→ 行函数 +row.Gold+… 算 Total → d3.autoType 自动转型 → csvParse/parseRows 两种结构对照 → 行函数把宽表拍成 tidy 三行/国 → csvFormat 再 stringify → Promise.all 双文件合流画图。

  1. d3.csv/tsv/json/svg/image/blob/buffer:URL 进、解析好的原生结构出,一律返回 Promise;
  2. 表格解析的值全是字符串,运算前 + 转数字,或直接用 d3.autoType 当行函数;
  3. 行函数 = 加载函数的最后一个参数,逐行加工、可改可删可换;
  4. parse 出对象数组(按列名取值),parseRows 出数组的数组(通吃渲染成表格);
  5. tidy 格式:一行一个观测;大多数 D3 工具好这口,堆叠图是主要例外;
  6. format/formatRows 把数组写回 DSV 字符串,键名自动变表头;
  7. 多源加载 Promise.all([...]),结果按顺序解构;单文件失败用 .catch(() => {}) 隔离;
  8. data.columns 拿到表头数组,画动态表格时必备。

6. 原文地图

主题 原书章 原文位置
章节目标与 d3-fetch 总览 Loading and Parsing Data text/12-ch05-chapter-5-loading-and-parsing-data.txt:3(搜「asynchronously load and parse」) · text/12-ch05-chapter-5-loading-and-parsing-data.txt:60(搜「native data structures」)
fetch 两层 Promise Loading and Parsing Data text/12-ch05-chapter-5-loading-and-parsing-data.txt:41(搜「GET request」) · text/12-ch05-chapter-5-loading-and-parsing-data.txt:44(搜「blocks execution」)
d3.text / d3.csv / columns Loading and Parsing Data text/12-ch05-chapter-5-loading-and-parsing-data.txt:74(搜「d3.text(dataFile)」) · text/12-ch05-chapter-5-loading-and-parsing-data.txt:84(搜「await d3.csv」) · text/12-ch05-chapter-5-loading-and-parsing-data.txt:96(搜「data.columns」)
d3.svg / d3.xml / d3.image / blob / buffer Loading and Parsing Data text/12-ch05-chapter-5-loading-and-parsing-data.txt:105(搜「d3.svg()」) · text/12-ch05-chapter-5-loading-and-parsing-data.txt:121(搜「binary data」) · text/12-ch05-chapter-5-loading-and-parsing-data.txt:133(搜「append("image")」)
行函数与 Total 例子 Loading and Parsing Data text/12-ch05-chapter-5-loading-and-parsing-data.txt:146(搜「last argument」) · text/12-ch05-chapter-5-loading-and-parsing-data.txt:156(搜「row.Total」) · text/12-ch05-chapter-5-loading-and-parsing-data.txt:169(搜「values as strings by default」)
d3.autoType Loading and Parsing Data text/12-ch05-chapter-5-loading-and-parsing-data.txt:172(搜「autoType」) · text/12-ch05-chapter-5-loading-and-parsing-data.txt:297(搜「tsvParse(tsv, d3.autoType)」)
HTTP 配置插中间 Loading and Parsing Data text/12-ch05-chapter-5-loading-and-parsing-data.txt:183(搜「last argument」) · text/12-ch05-chapter-5-loading-and-parsing-data.txt:188(搜「bearer」)
dsvFormat/parse/parseRows 函数表 Loading and Parsing Data text/12-ch05-chapter-5-loading-and-parsing-data.txt:208(搜「dsvFormat(delimiter)」) · text/12-ch05-chapter-5-loading-and-parsing-data.txt:229(搜「equivalent to using d3.text()」)
对象数组 vs 数组的数组 Loading and Parsing Data text/12-ch05-chapter-5-loading-and-parsing-data.txt:242(搜「data1[2].Gold」) · text/12-ch05-chapter-5-loading-and-parsing-data.txt:238(搜「render any CSV as an HTML table」)
嵌套绑定渲染表格 Loading and Parsing Data text/12-ch05-chapter-5-loading-and-parsing-data.txt:267(搜「headers = data2[0]」) · text/12-ch05-chapter-5-loading-and-parsing-data.txt:284(搜「nested binding」)
tidy/宽表与转换 Loading and Parsing Data text/12-ch05-chapter-5-loading-and-parsing-data.txt:334(搜「tidy (or long) format」) · text/12-ch05-chapter-5-loading-and-parsing-data.txt:349(搜「array.flat()」) · text/12-ch05-chapter-5-loading-and-parsing-data.txt:351(搜「easier to use if your data is in a tidy format」)
format/formatRows 与快捷函数 Loading and Parsing Data text/12-ch05-chapter-5-loading-and-parsing-data.txt:366(搜「parser.format(rows, columns)」) · text/12-ch05-chapter-5-loading-and-parsing-data.txt:398(搜「dsvFormat(";")」) · text/12-ch05-chapter-5-loading-and-parsing-data.txt:434(搜「d3.csvFormat」)
Promise.all 多源加载 Loading and Parsing Data text/12-ch05-chapter-5-loading-and-parsing-data.txt:443(搜「Promise.all()」) · text/12-ch05-chapter-5-loading-and-parsing-data.txt:459(搜「const [shapes, points]」)
图片预加载 Loading and Parsing Data text/12-ch05-chapter-5-loading-and-parsing-data.txt:483(搜「promises = entries.map」) · text/12-ch05-chapter-5-loading-and-parsing-data.txt:492(搜「blocks until all promises」) · text/12-ch05-chapter-5-loading-and-parsing-data.txt:499(搜「entries.filter」)
在线教程(第一部分) Loading and Parsing Data text/12-ch05-chapter-5-loading-and-parsing-data.txt:532(搜「part 1」) · text/12-ch05-chapter-5-loading-and-parsing-data.txt:542(搜「StepByStep/1-images」)
章小结 Loading and Parsing Data text/12-ch05-chapter-5-loading-and-parsing-data.txt:552(搜「Summary」)

  1. 出处:「Loading and Parsing Data」第 49–55 段(text/12-ch05-chapter-5-loading-and-parsing-data.txt:51,搜「Rank,Country,Gold,Silver,Bronze」)。CSV 前四行照抄原书。 ↩

  2. 出处:「Loading and Parsing Data」第 41–47 段(text/12-ch05-chapter-5-loading-and-parsing-data.txt:41,搜「GET request」;:44,搜「blocks execution」)。原文:「A default fetch operation sends a GET request to the server and receives an HTTP response wrapped in a promise.」 ↩

  3. 出处:「Loading and Parsing Data」第 59–62 段(text/12-ch05-chapter-5-loading-and-parsing-data.txt:60,搜「native data structures」)。函数家族一览见原书 Figure 5.1 的说明文字。 ↩

  4. 出处:「Loading and Parsing Data」第 65–67 段(text/12-ch05-chapter-5-loading-and-parsing-data.txt:66,搜「standard HTTP configuration」)。 ↩

  5. 出处:「Loading and Parsing Data」第 86–96 段(text/12-ch05-chapter-5-loading-and-parsing-data.txt:89,搜「Rank: "1", Country: "USA"」;:96,搜「data.columns」)。 ↩

  6. 出处:「Loading and Parsing Data」第 98–136 段(text/12-ch05-chapter-5-loading-and-parsing-data.txt:100,搜「loading a GeoJSON shapefile」;:108,搜「knight.svg」;:122,搜「d3.blob()」;:133,搜「append("image")」)。 ↩

  7. 出处:「Loading and Parsing Data」第 146–149 段(text/12-ch05-chapter-5-loading-and-parsing-data.txt:146,搜「last argument」)。原文:「A function can be provided as the last argument in d3.csv(), d3.tsv(), or d3.dsv() to modify the row object during parsing.」 ↩

  8. 出处:「Loading and Parsing Data」第 168–170 段(text/12-ch05-chapter-5-loading-and-parsing-data.txt:169,搜「values as strings by default」)。原文:「This is important, since tabular parsers treat values as strings by default. If you attempt to add them, they won't be added but will instead be concatenated.」 ↩

  9. 出处:「Loading and Parsing Data」第 183–191 段(text/12-ch05-chapter-5-loading-and-parsing-data.txt:183,搜「last argument」;:191,搜「Bearer ${token}」)。 ↩↩

  10. 出处:「Loading and Parsing Data」第 202–227 段(text/12-ch05-chapter-5-loading-and-parsing-data.txt:208,搜「dsvFormat(delimiter)」;:220,搜「csvParse(string,row)」)。函数表照原书 Table 5.1 转写。 ↩

  11. 出处:「Loading and Parsing Data」第 237–243 段(text/12-ch05-chapter-5-loading-and-parsing-data.txt:242,搜「medals1」)。原文:「You would typically use the data1 structure to retrieve data from selected columns, by name, while data2 may be best to render any CSV as an HTML table or spreadsheet.」 ↩↩

  12. 出处:「Loading and Parsing Data」第 262–286 段(text/12-ch05-chapter-5-loading-and-parsing-data.txt:284,搜「nested binding」)。原文:「The preceding code is a great example of nested binding in D3. … Nested structures like these will be explored in detail in Chapters 6 and 7.」 ↩

  13. 出处:「Loading and Parsing Data」第 334–340 段(text/12-ch05-chapter-5-loading-and-parsing-data.txt:334,搜「tidy (or long) format」;:336,搜「wide data format」)。原文:「a standard way of structuring data so that each variable has a column, and all values are rows」。 ↩

  14. 出处:「Loading and Parsing Data」第 341–349 段(text/12-ch05-chapter-5-loading-and-parsing-data.txt:343,搜「array = d3.csv(dataFile」;:349,搜「array.flat()」)。 ↩

  15. 出处:「Loading and Parsing Data」第 351 段(text/12-ch05-chapter-5-loading-and-parsing-data.txt:351,搜「easier to use if your data is in a tidy format」)。 ↩↩

  16. 出处:「Loading and Parsing Data」第 356–383 段(text/12-ch05-chapter-5-loading-and-parsing-data.txt:366,搜「parser.format(rows, columns)」)。方法表照原书 Table 5.2 转写。 ↩

  17. 出处:「Loading and Parsing Data」第 390–434 段(text/12-ch05-chapter-5-loading-and-parsing-data.txt:398,搜「dsvFormat(";")」;:408,搜「abc;def;ghi」;:427,搜「name;distance」;:434,搜「d3.csvFormat」)。 ↩

  18. 出处:「Loading and Parsing Data」第 438–464 段(text/12-ch05-chapter-5-loading-and-parsing-data.txt:443,搜「Promise.all()」;:459,搜「const [shapes, points]」)。「经纬度顺序」:原书 coords: [+row.longitude, +row.latitude] 经度在前,这是 GeoJSON 的坐标顺序惯例。 ↩

  19. 出处:「Loading and Parsing Data」第 468–492 段(text/12-ch05-chapter-5-loading-and-parsing-data.txt:483,搜「promises = entries.map」;:492,搜「blocks until all promises」)。 ↩

  20. 出处:「Loading and Parsing Data」第 483–503 段(text/12-ch05-chapter-5-loading-and-parsing-data.txt:486,搜「do nothing if file not found」;:499,搜「entries.filter」)。 ↩

  21. 出处:「Loading and Parsing Data」第 532–544 段(text/12-ch05-chapter-5-loading-and-parsing-data.txt:533,搜「Instead of another」;:536,搜「Part 1」)。 ↩

  22. 出处:「Loading and Parsing Data」第 545–550 段(text/12-ch05-chapter-5-loading-and-parsing-data.txt:545,搜「Tutorial/README.md」;:549,搜「Observable notebook」)。 ↩↩

  23. 出处:「Loading and Parsing Data」第 330–331 段(text/12-ch05-chapter-5-loading-and-parsing-data.txt:330,搜「no special parsers」)。原文:「D3 only provides string parsers for tabular data formats. There are no special parsers for JSON, SVG, HTML, and XML because they can be parsed with native JavaScript tools.」 ↩