跳转至

数据处理 — d3-array 与格式化工具

这一章讲什么: 数据绑上图形之前,通常要先加工:挑出最大最小值来定比例尺的范围、 排序、求和取平均、把一张平表折叠成嵌套结构。这一份拆解决 d3-array 的数据处理全家桶 和 d3-format / d3-time-format 两套格式化工具(生成数据、分箱、分组在下一章)。 原书的讲法是「控制台一行行试」——每个函数都配了可复制的真实输出。

1. 主线例子:四个小数据集走全程

原书给了一组贯穿全章的演示数据,每个函数都在它们身上跑1:

  numArray = [9, 2, 12, 9, 71]                    —— 数字数组
  strArray = ['9', '2', '12', '9', '71', 'Y', null] —— 同样的数,但存成字符串,外加两个捣乱项
  tasks = [
    {task: 'Debugging', cost: 1.40, priority: 1, date: …},
    {task: 'Reviewing', cost: 0.60, priority: 2, date: …},
    {task: 'Editing',   cost: 0.25, priority: 5, date: …},
    {task: 'Testing',   cost: 0.10, priority: 1, date: …}
  ]                                                —— 对象数组(四个任务)
  taskmap = new Map(tasks.map(e => [e.task, e]))   —— 同样的数据装进 Map
  numset  = new Set(numArray)                       —— 去重后的集合 {9,2,12,71}
  str = "953zyx2"                                   —— 一个字符串

  图说:数字、字符串、对象、Map、Set、字符串六种形态,用来展示「任何可迭代对象都能吃」。

为什么需要这一整章?因为原生 JavaScript 的数组工具在这三类场景会翻车: Math.min(strArray) 遇字符串得 NaN、原生 sort() 默认按字典序排数字、 原生方法只认数组不认 Map/Set/字符串。d3-array 的对应函数修了这三个坑2。

2. 核心原理

2.1 最值:min / max / extent / minIndex

函数 返回
d3.min(集合, 访问器?) 最小值(自然序)
d3.max(集合, 访问器?) 最大值
d3.extent(集合, 访问器?) [min, max] 二元数组——配比例尺 domain 的标配
d3.minIndex / d3.maxIndex 最值所在的下标(没有则 -1)

为什么不用 Math.min? 对着主线例子看3:

  d3.min(numArray)    // 2        —— 数字按数值比,没问题
  d3.min(strArray)    // '12'     —— 字符串按字典序比:'1' 开头的 '12' 排在 '2' 前面!
  Math.min(strArray)  // NaN      —— 原生版本想把 'Y' 转成数字,失败,全盘皆 NaN

  图说:d3.min 遇到 null/NaN 直接跳过;字典序的坑在求「数值最小」时是静默错误——
  你的图不会报错,只是 domain 错得离谱。

对象数组要用访问器(accessor)告诉它比哪个字段,两种等价写法4:

  d3.min(tasks.map(d => d.cost))   // 0.1 —— 先 map 出数组再求值
  d3.min(tasks, d => d.cost)       // 0.1 —— 访问器直接传,更省一步
  d3.min(taskmap, d => d[1].cost)  // 0.1 —— Map 的元素是 [键, 值] 二元组,所以取 d[1]

警告框里的那个坑值得背下来:想顺手用访问器做类型转换时,别让它改变数据的「可比较性」5:

  d3.min(strArray, d => +d)                          // 0   ← 错了!
  // 原因:+null 会先被转成 0,null 就不再被忽略,0 成了最小值
  d3.min(strArray, d => (d == null) ? null : +d)     // 2   ← 显式挡住 null 才对

extent 一次拿两头,是配比例尺 domain 时最高频的调用:d3.extent(numArray) 返回 [2, 71]6。 它同样吃 Map、Set、字符串:d3.extent(str) 把字符串拆成字符按字典序比,得 [2, 'z']—— 单字符的 '2' 也能和 'z' 比,比较时按各自的自然序7。

2.2 排序与选择:比较器的世界

数字有自然序,对象没有。要「取出最贵的那个任务对象」而不是「最贵的价钱」, 就需要比较器(comparator):一个 (a, b) => 负数/0/正数 的函数,返回值的符号表示先后。

函数 干什么
d3.ascending(a, b) / d3.descending(a, b) 升/降序比较器(处理 null)
d3.least(集合, 比较器?) / d3.greatest 返回元素本身(不是字段值)
d3.leastIndex / d3.greatestIndex 最元素的下标
d3.rank(集合, 访问器?或比较器?) 返回每个元素的「名次」数组
d3.quickselect(数组, k) 原地重排,使 数组[k] 恰好是第 k 小(比全排序快)
  numArray.sort((a,b) => d3.descending(a,b))   // [71,12,9,9,2]
  numArray.sort(d3.descending)                 // 简写,等价
  tasks.sort((a,b) => d3.ascending(a.cost, b.cost))   // 对象按字段排

  d3.least(tasks, (a,b) => d3.ascending(a.priority, b.priority))
  // → {task: 'Debugging', …, priority: 1}   ← 返回整个对象!

  图说:least/greatest 加访问器时返回的是字段值(least(tasks, d=>d.priority) 得 1);
  加比较器时才能拿到「整条记录」——这个区别在「找记录」时是要命的。

d3.rank 返回名次数组:d3.rank(numArray) 得 [1,0,3,1,4]—— 9 排第 1(从 0 计)、2 排第 0、12 排第 3、9 又是第 1、71 排第 4; 对象数组不给比较依据就全体名次 0:d3.rank(tasks) 得 [0,0,0,0],补上访问器 d => d.date 才有意义8。

2.3 求和与统计

函数 返回
d3.sum(集合, 访问器?) 总和(跳过 null/NaN/undefined)
d3.cumsum(集合, 访问器?) 累积和数组:第 i 项是前 i 项之和
d3.fsum / d3.fcumsum 高精度版(慢,对付浮点误差)
new d3.Adder() 累加器对象:adder.add(x) 喂数,+adder 取值
d3.count 数值个数(不是总个数!)
d3.mode 众数
d3.mean / d3.median / d3.variance / d3.deviation 均值/中位数/方差/标准差

主线例子的实跑结果9:

  d3.sum(strArray)            // 103    —— 'Y' 和 null 被跳过,纯数字求和
  d3.sum(tasks, d => d.cost)  // 2.35   —— 四个任务的成本总和
  d3.cumsum(tasks, d => d.cost)  // [1.4, 2, 2.25, 2.35] —— 逐项累加

  d3.count(numArray)   // 5
  d3.count(strArray)   // 5   —— 'Y' 和 null 不算数
  d3.count(tasks)      // 0   —— 对象是「不是数」,除非给访问器
  d3.count(tasks, d => d.cost)  // 4

  d3.mean(numArray)     // 20.6
  d3.median(numArray)   // 9
  d3.variance(numArray) // 807.3
  d3.deviation(numArray) // 28.41302518212378

d3.fsum 的存在理由:JavaScript 的浮点数加法有舍入误差,0.1 + 0.2 !== 0.3; fsum 用补偿算法算出精确和,代价是慢10。d3.Adder() 是同一个思路的对象版: numArray.forEach(d => acum.add(d)) 之后 +acum 得 10311。

2.4 集合运算与 every/some

  d3.difference([3,6,9], [2,4,6,8,10])   // Set{3,9}    —— 在 a 不在 b
  d3.union([5,10], [3,6,9], [2,4,6,8,10]) // Set{2,3,4,5,6,8,9,10}  —— 并集(去重)
  d3.intersection([2,4,6,8,10], [3,6,9])  // Set{6}     —— 交集

  d3.disjoint([5,10], [3,6,9])            // true  —— 两集合无交集
  d3.superset([1,2,3,4,5,6], [2,4,6])     // true  —— a 包含 b
  d3.subset([3,6], [1,2,3,4,5,6])         // true  —— a 含于 b

  d3.difference("dinosaur", "pterosaur")  // Set{'d','i','n'}  —— 字符串也能当集合

返回值是 JavaScript 的 Set。d3.every/d3.some 与原生数组方法同名不同家: 不是数组方法、可作用于任何可迭代对象(d3.some('abc-def', ch => …) 直接吃字符串)12。

2.5 d3 版「原生方法」:三个关键差异

d3.filter / map / reduce / reverse / sort 与原生同名,但原书列出了三条差异13:

  1. 吃任何可迭代对象(字符串、Map、Set),不用先 Array.from 转换;
  2. 不改原集合——原生 sort()/reverse() 是原地修改,d3 版返回新数组;
  3. 默认自然序——d3.sort(numArray) 数字按数值排得 [2,9,9,12,71], 原生 numArray.sort() 按字典排得 [12,2,71,9,9]——经典翻车现场。
  array = [8,2,3,5]
  sorted = d3.sort(array)     // array 还是 [8,2,3,5],sorted 是 [2,3,5,8]
  sorted = array.sort()       // 原生:array 自己也变了

  d3.sort(集合, 比较器)         // 也可传多个访问器做多级排序

2.6 数组变换:merge / permute / cross / pairs / zip / transpose

六个变换函数,各解决一类「数组的数组」问题14:

函数 干什么 例子
d3.merge(二维可迭代) 拍平一层 merge([[3,4],[5,6]]) → [3,4,5,6]
d3.permute(数组, 下标数组) 按下标重排 permute([33,65,86,49],[3,2,0,1]) → [49,86,33,65]
d3.cross(a, b, 归并?) 笛卡尔积 cross([1,2],[9,8,7]) → [[1,9],[1,8],…] 6 组
d3.pairs(数组, 归并?) 相邻两两配对 pairs([3,4,5,6]) → [[3,4],[4,5],[5,6]]
d3.zip(多个数组) 按下标对齐合并(裁齐) zip([1,2,3],[4,5,6],[9,8,7,6]) → [[1,4,9],[2,5,8],[3,6,7]]
d3.transpose(矩阵) 转置 transpose([[1,2,3],[4,5,6],[9,8,7]]) → 行列互换

cross 的典型用途:替掉嵌套循环——画网格坐标、棋盘格(exercise 7.2 就是棋盘), cross([1,2],[9,8,7],(a,b)=>a*b) 直接给每个格子算好值15。 pairs 是画折线的原料:点列变线段列,pairs([3,4,5,6],(a,b)=>a+b) 得 [7,9,11]16。 merge 与原生 flat() 的取舍:merge 吃任意可迭代对象但遇非可迭代元素会报错, flat() 只认数组但更宽容更快——现代代码里原书也承认 flat/flatMap 通常是更好的选择17。 permute 允许重复和省略下标:d3.permute("aardvark",[2,0,3,0,2]).join('') 得 'radar'18。

2.7 二分查找与分位数

bisect 家族:在有序数组里找插入位置或最近元素,多数时候 D3 内部自用(坐标轴找刻度)。

  sorted1 = d3.sort(numArray)      // [2,9,9,12,71]

  d3.bisectLeft(sorted1, 8)    // 1   —— 8 该插在 1 号位(插在所有 8 之前)
  d3.bisectRight(sorted1, 8)   // 1   —— 不存在,左右一致
  d3.bisectLeft(sorted1, 9)    // 1   —— 9 已存在:左插,挤在已有 9 之前
  d3.bisectRight(sorted1, 9)   // 3   —— 右插,挤在已有 9 之后(下标 3)
  d3.bisectCenter(sorted1, 41) // 3   —— 离 41 最近的是 12(下标 3)
  d3.bisectCenter(sorted1, 42) // 4   —— 离 42 最近的是 71(下标 4)

  图说:bisectLeft/Right 只在「值已存在」时有别;配合 splice 就是有序的数组插入。

quantile(分位数):把一个排好序的数列切成等大的组,返回切点的值。 p 取 0–1:0.25/0.5/0.75 三个切点把数据四等分(四分位数),0.5 就是中位数19:

  sorted2 = [2,19,24,67,69,72,81,88,91,94,98,99]
  d3.quantile(sorted2, 0.25)  // 56.25
  d3.quantile(sorted2, 0.50)  // 76.5   ← 中位数
  d3.quantile(sorted2, 0.75)  // 91.75

分位数的实战价值原书点了两条:压缩大数据(一万个数的 100 个分位点就能画出保留趋势的简化图)、 给分箱当阈值(下一章的分箱就会用到)20。

3. 格式化:让数字和日期说读者的话

数字 123456789.9876 直接打印出来,读者要数好一会儿逗号。 d3.format(说明符) 造一个格式化函数,说明符是一个紧凑的模板字符串,语法借自 C/Python 的 printf 传统21:

  formatPercent = d3.format(".2%")     // 百分比,2 位小数
  formatUnit    = d3.format(".3s")     // SI 公制单位(k/M/G/µ)
  formatMoney   = d3.format("$,.2f")   // 货币 + 千分位 + 定点 2 位小数

  formatPercent(0.23478)        // "23.48%"
  formatUnit(0.0000045367)      // "4.537µ"
  formatMoney(123456789.9876)   // "$123,456,789.99"

  图说:.2% 的数会先乘 100;.3s 选合适数量级并保留 3 位有效数字;
  $ 是货币符号(会因地区而变),, 是千分位,.2f 是两位小数的定点数。

locale(地区设置)是这套工具的另一半:同样的 formatMoney,在巴西葡萄牙语环境里 小数点变逗号、千分位变点号:R$ 123.456.789,99。两种用法:自己拼定义对象 ({decimal, thousands, grouping, currency} 四个属性)传给 d3.formatDefaultLocale(), 或从 CDN 加载现成地区文件(如 de-DE.json)22。

日期是平行的一套:d3.timeFormat("%Y-%m-%d") 造格式化器,d3.timeParse(同模板) 造反向的解析器; ISO8601 标准格式有直达车 d3.isoParse("2024-01-14")。模板里的指令以 % 开头: %A 星期全名、%d 补零的日、%B 月全名、%Y 四位年、%H 24 小时…… 配合地区文件,%c/%x/%X 三个指令输出各地区习惯的完整日期、短日期、时间写法23。

解析失败返回 null——这个「安静的 null」是日期处理里最常见的 bug 来源,用前记得验24。

4. 作者的判断与证据

判断 性质 依据
「d3 版函数优于原生」 有边界的经验法则 三条理由(可迭代、不改原、自然序)都给了反例边界,如 flat() 更快13
「分位数可压缩大数据」 使用建议 配了万级数据 → 百级阈值的具体场景20
「高精度求和更慢」 性能事实 fsum/fcumsum 与 Adder 的取舍照实写10

5. 边界与局限

  • 本章是工具箱巡览,不是教程:17 个随机分布、全部分位数变体都只给名字和图, 细节让读者查官方文档25。
  • d3.format 说明符的完整语法原书明确不展开(「beyond the scope of this book」)26。
  • 时区处理只有 UTC 系列函数一句带过;夏令时原书明说不处理(count() 不考虑 DST)27。
  • 格式化函数的输出依赖运行环境的默认地区,同一行代码在德文系统上结果不同——团队项目里建议显式设 locale。

判断(我们的,不是书里的): d3-array 的存在理由是 JavaScript 标准库的三个历史遗留缺陷 (可迭代协议普及晚、sort 默认字典序、类型强制转换多),随着语言演进,这个模块的很大一部分 正在退化成「兼容性封装」——d3.sort 对 d3 自身的意义(与 scale/axis 的自然序约定一致) 大于它对用户的意义。学它的正确姿势是记住差异点,而不是背 API 表。 如果错,会错在: 可迭代对象原生方法的支持度如果继续扩大、或 TC39 给 sort 加自然序选项, d3-array 的差异化会继续萎缩——但「跳过脏数据」这一条(原生 Math.min 做不到)依然独一份。

6. 可带走的

主线例子一行收束: [9,2,12,9,71] 六个函数轮一遍——min 2、max 71、extent [2,71]、 sum 103、mean 20.6、median 9;同名字符串数组 min 得 '12'(字典序),Math.min 直接 NaN。

  1. d3.min/max/extent(集合, 访问器) 吃任何可迭代对象,跳过 null/NaN,默认自然序;
  2. extent 一次拿 [min,max],是配 scale domain 的标配;
  3. 访问器里做 +d 转换要小心 null 变 0 的坑;
  4. least/greatest + 比较器 返回整条记录;+ 访问器 只返回字段值;
  5. cumsum 累积和、fsum/fcumsum 高精度(慢)、Adder 对象累加;
  6. d3.count 数的是数值个数,不是元素个数;
  7. 集合运算返回 Set;difference/union/intersection/disjoint/superset/subset;
  8. d3.sort 等五件套:不改原集合、自然序、可迭代——与原生 sort 的三大差异;
  9. cross 替嵌套循环、pairs 产折线原料、zip/transpose 对位合并;
  10. bisectLeft/Right 在有序数组找插入位(值已存在时左右有别);quantile 切等分点;
  11. d3.format(".2%") 等说明符格式化数字,locale 决定小数点/千分位/货币样式;
  12. timeParse/timeFormat 模板互逆,解析失败返回 null。

7. 原文地图

主题 原书章 原文位置
章节目标与样本数据 Working with Data text/14-ch07-chapter-7-working-with-data.txt:2(搜「preparing your data」) · text/14-ch07-chapter-7-working-with-data.txt:35(搜「numArray = [9, 2, 12, 9, 71]」)
min/max 与字符串陷阱 Working with Data text/14-ch07-chapter-7-working-with-data.txt:77(搜「d3.min(iterable,accessor)」) · text/14-ch07-chapter-7-working-with-data.txt:101(搜「// 2」) · text/14-ch07-chapter-7-working-with-data.txt:108(搜「NaN」)
访问器与 null 警告 Working with Data text/14-ch07-chapter-7-working-with-data.txt:119(搜「d => d.cost」) · text/14-ch07-chapter-7-working-with-data.txt:130(搜「// 0」) · text/14-ch07-chapter-7-working-with-data.txt:136(搜「d == null」)
extent 与各集合类型 Working with Data text/14-ch07-chapter-7-working-with-data.txt:143(搜「[2,71]」) · text/14-ch07-chapter-7-working-with-data.txt:150(搜「[2, 'z']」)
比较器家族与 rank Working with Data text/14-ch07-chapter-7-working-with-data.txt:172(搜「d3.ascending(a,b)」) · text/14-ch07-chapter-7-working-with-data.txt:206(搜「[1,0,3,1,4]」) · text/14-ch07-chapter-7-working-with-data.txt:229(搜「highest-priority task」) · text/14-ch07-chapter-7-working-with-data.txt:234(搜「quickselect」)
求和与统计 Working with Data text/14-ch07-chapter-7-working-with-data.txt:242(搜「d3.sum(iterable,accessor)」) · text/14-ch07-chapter-7-working-with-data.txt:259(搜「// 103」) · text/14-ch07-chapter-7-working-with-data.txt:265(搜「[9,11,23,32,103,103,103]」) · text/14-ch07-chapter-7-working-with-data.txt:307(搜「// 5」) · text/14-ch07-chapter-7-working-with-data.txt:320(搜「// 20.6」)
fsum 与 Adder Working with Data text/14-ch07-chapter-7-working-with-data.txt:247(搜「full precision」) · text/14-ch07-chapter-7-working-with-data.txt:272(搜「new d3.Adder()」)
集合运算 Working with Data text/14-ch07-chapter-7-working-with-data.txt:332(搜「d3.difference(a」) · text/14-ch07-chapter-7-working-with-data.txt:352(搜「{3,9}」) · text/14-ch07-chapter-7-working-with-data.txt:369(搜「dinosaur」)
d3 版原生方法三差异 Working with Data text/14-ch07-chapter-7-working-with-data.txt:385(搜「d3.filter(iterable, test)」) · text/14-ch07-chapter-7-working-with-data.txt:416(搜「array = [8,2,3,5]」) · text/14-ch07-chapter-7-working-with-data.txt:428(搜「[12, 2, 71, 9, 9]」)
merge/permute/cross/pairs Working with Data text/14-ch07-chapter-7-working-with-data.txt:440(搜「d3.merge」) · text/14-ch07-chapter-7-working-with-data.txt:481(搜「[3,2,0,1]」) · text/14-ch07-chapter-7-working-with-data.txt:491(搜「[1,9], [1,8]」) · text/14-ch07-chapter-7-working-with-data.txt:502(搜「[3,4], [4,5]」)
zip/transpose Working with Data text/14-ch07-chapter-7-working-with-data.txt:516(搜「d3.zip(arrays)」) · text/14-ch07-chapter-7-working-with-data.txt:526(搜「last 6 discarded」)
bisect 家族 Working with Data text/14-ch07-chapter-7-working-with-data.txt:541(搜「d3.bisect(array,x」) · text/14-ch07-chapter-7-working-with-data.txt:569(搜「sorted1 = d3.sort」) · text/14-ch07-chapter-7-working-with-data.txt:583(搜「bisectCenter」)
分位数 Working with Data text/14-ch07-chapter-7-working-with-data.txt:556(搜「d3.quantile(iterable」) · text/14-ch07-chapter-7-working-with-data.txt:591(搜「sorted2 = [2,19,24」) · text/14-ch07-chapter-7-working-with-data.txt:601(搜「compress large datasets」)
d3.format 与说明符 Working with Data text/14-ch07-chapter-7-working-with-data.txt:639(搜「d3.format(specifier)」) · text/14-ch07-chapter-7-working-with-data.txt:651(搜「.2%」) · text/14-ch07-chapter-7-working-with-data.txt:705(搜「23.48%」)
locale Working with Data text/14-ch07-chapter-7-working-with-data.txt:661(搜「decimal points will be commas」) · text/14-ch07-chapter-7-working-with-data.txt:665(搜「pt_BR」) · text/14-ch07-chapter-7-working-with-data.txt:676(搜「de-DE.json」)
日期格式化与解析 Working with Data text/14-ch07-chapter-7-working-with-data.txt:734(搜「d3.timeFormat(specifier)」) · text/14-ch07-chapter-7-working-with-data.txt:766(搜「timeParse」) · text/14-ch07-chapter-7-working-with-data.txt:774(搜「return null」) · text/14-ch07-chapter-7-working-with-data.txt:781(搜「%A」)
章小结 Working with Data text/14-ch07-chapter-7-working-with-data.txt:1732(搜「Summary」)

  1. 出处:「Working with Data」第 35–48 段(text/14-ch07-chapter-7-working-with-data.txt:35,搜「numArray = [9, 2, 12, 9, 71]」;:45,搜「taskmap = new Map」)。六个数据集定义照原书转写;它们被复制到全局上下文以便控制台实验,见第 61–63 段。 ↩

  2. 出处:「Working with Data」第 92–96 段(text/14-ch07-chapter-7-working-with-data.txt:93,搜「ignore undefined, null, and NaN」;:425,搜「natural order as default」)。「Math.min 遇字符串得 NaN」的机理(想把 'Y' 转成数字失败)见第 108–111 段。 ↩

  3. 出处:「Working with Data」第 101–111 段(text/14-ch07-chapter-7-working-with-data.txt:101,搜「d3.min(numArray)」;:102,搜「'12'」;:108,搜「Math.min(strArray)」)。 ↩

  4. 出处:「Working with Data」第 115–123 段(text/14-ch07-chapter-7-working-with-data.txt:115,搜「0.1」;:123,搜「d[1].cost」)。 ↩

  5. 出处:「Working with Data」第 125–138 段(text/14-ch07-chapter-7-working-with-data.txt:130,搜「// 0」;:136,搜「d == null」)。原文:「This is because +null was coerced to 0 before the accessor was called.」 ↩

  6. 出处:「Working with Data」第 140–150 段(text/14-ch07-chapter-7-working-with-data.txt:143,搜「[2,71]」)。「for example, to set up a scale's domain」是原书给的典型场景。 ↩

  7. 出处:「Working with Data」第 150–155 段(text/14-ch07-chapter-7-working-with-data.txt:150,搜「[2, 'z']」;:153,搜「split into individual characters」)。 ↩

  8. 出处:「Working with Data」第 204–216 段(text/14-ch07-chapter-7-working-with-data.txt:206,搜「[1,0,3,1,4]」;:211,搜「[0,0,0,0]」)。 ↩

  9. 出处:「Working with Data」第 259–323 段(text/14-ch07-chapter-7-working-with-data.txt:259,搜「103」;:265,搜「[1.4,2,2.25,2.35]」;:307,搜「// 5」;:320,搜「20.6」)。逐值照原书注释。 ↩

  10. 出处:「Working with Data」第 247–249、268–269 段(text/14-ch07-chapter-7-working-with-data.txt:247,搜「full precision」;:269,搜「though they are slower」)。 ↩↩

  11. 出处:「Working with Data」第 270–280 段(text/14-ch07-chapter-7-working-with-data.txt:272,搜「new d3.Adder()」;:280,搜「result = +acum」)。 ↩

  12. 出处:「Working with Data」第 330–376 段(text/14-ch07-chapter-7-working-with-data.txt:352,搜「{3,9}」;:369,搜「dinosaur」;:375,搜「abc-def」)。 ↩

  13. 出处:「Working with Data」第 379–431 段(text/14-ch07-chapter-7-working-with-data.txt:385,搜「d3.filter(iterable, test)」;:416,搜「array = [8,2,3,5]」;:425,搜「natural order as default」;:428,搜「[12, 2, 71, 9, 9]」)。 ↩↩

  14. 出处:「Working with Data」第 434–530 段(text/14-ch07-chapter-7-working-with-data.txt:440,搜「d3.merge」;:516,搜「d3.zip」)。各函数输出值均照原书注释。 ↩

  15. 出处:「Working with Data」第 488–498 段(text/14-ch07-chapter-7-working-with-data.txt:491,搜「[1,9], [1,8]」;:497,搜「replace nested loops」)。 ↩

  16. 出处:「Working with Data」第 499–509 段(text/14-ch07-chapter-7-working-with-data.txt:502,搜「[3,4], [4,5]」;:508,搜「a+b」)。 ↩

  17. 出处:「Working with Data」第 469–477 段(text/14-ch07-chapter-7-working-with-data.txt:471,搜「flat() and flatMap()」;:469,搜「1 is not iterable」)。 ↩

  18. 出处:「Working with Data」第 478–486 段(text/14-ch07-chapter-7-working-with-data.txt:481,搜「[3,2,0,1]」;:486,搜「radar」)。 ↩

  19. 出处:「Working with Data」第 565–597 段(text/14-ch07-chapter-7-working-with-data.txt:569,搜「[2,9,9,12,71]」;:570,搜「index 1」;:574,搜「index 3」;:591,搜「sorted2 = [2,19,24」;:595,搜「56.25」)。 ↩

  20. 出处:「Working with Data」第 599–603 段(text/14-ch07-chapter-7-working-with-data.txt:601,搜「compress large datasets」)。 ↩↩

  21. 出处:「Working with Data」第 632–697 段(text/14-ch07-chapter-7-working-with-data.txt:639,搜「d3.format(specifier)」;:651,搜「.2%」;:689,搜「based on print formatting」)。 ↩

  22. 出处:「Working with Data」第 660–682 段(text/14-ch07-chapter-7-working-with-data.txt:665,搜「pt_BR」;:676,搜「de-DE.json」;:682,搜「123.456.789,99 €」)。 ↩

  23. 出处:「Working with Data」第 728–815 段(text/14-ch07-chapter-7-working-with-data.txt:734,搜「d3.timeFormat(specifier)」;:760,搜「%A %d %B %Y」;:780,搜「Day of week」;:804,搜「d3.timeFormat('%c')」)。 ↩

  24. 出处:「Working with Data」第 774 段(text/14-ch07-chapter-7-working-with-data.txt:774,搜「return null」)。 ↩

  25. 出处:「Working with Data」第 930–937 段(text/14-ch07-chapter-7-working-with-data.txt:931,搜「17 generators」;:936,搜「beyond the scope of this book」)。 ↩

  26. 出处:「Working with Data」第 723–724 段(text/14-ch07-chapter-7-working-with-data.txt:723,搜「beyond the scope」)。 ↩

  27. 出处:「Working with Data」第 1224 段(text/14-ch07-chapter-7-working-with-data.txt:1224,搜「daylight savings time」)。原文:「The interval considers the correct number of days in each month and leap years (but not daylight savings time).」 ↩