几种常用的键值对类型的数据结构
NPM
安装:npm install d3-collection
, 还可以下载最新版,此外还可以直接从 d3js.org 以 单独的标准库 或作为 D3 4.0 的一部分引入,支持 AMD
, CommonJS
以及 vanilla
环境, 使用标签引入会暴露 d3
全局变量:
<script src="https://d3js.org/d3-collection.v1.min.js"></script>
<script>
var map = d3.map()
.set("foo", 1)
.set("bar", 2);
</script>
在JavaScript中常见的数据类型是 associative array(关联数组), 简言之就是具有一个属性集的 对象. 对象的的标准迭代方式是 for…in 循环, 但是迭代次序是未定义的. D3
提供了几种将对象转为数字索引的标准数组的方法.
请注意:当使用普通对象作为属性名是可以的,但是当使用特殊内置的属性名时会导致意想不到的事发生,比如使用 object["__proto__"] = 42
和 "hasOwnProperty" in object
. 如果不能保证映射的键是安全的情况下请使用 maps 和 sets(或标准的ES6数据结构)来代替对象.
返回一个包含了指定对象属性名的数组. 数组的顺序是未定义(不可靠)的.
返回一个包含了指定对象属性值的数组. 数组的顺序是未定义(不可靠)的.
将对象转为标准的包含 key
和 value
的对象数组.也就是将对象的 key-value
对重组为一个对象, 比如将 {foo: 42}
转为 {key: "foo", value: 42}
. 所传入的对象被重组为一个数组. 次序同样是不固定的:
d3.entries({foo: 42, bar: true}); // [{key: "foo", value: 42}, {key: "bar", value: true}]
与 ES6 Maps 类似, 但是有以下几点不同:
d3.maps
的Keys
会被强制转为字符串.- 使用 map.each 遍历, 而不是 map.forEach. (并且没有 thisArg 参数.)
- 使用 map.remove, 而不是 map.delete.
- map.entries 返回 {key, value} 对象数组而不是 [key, value] 迭代器.
- map.size 是一个方法而不是 property; map.empty 同样也是方法不是属性.
# d3.map([object[, key]]) <源码>
构建一个新的 map
. 如果指定了 object 则将其所有的可枚举对象复制到 map
中. object 可以是一个数组也可以是其他的 map
对象.可选的 key 方法用来指定使用哪个属性作为key
, 比如:
var map = d3.map([{name: "foo"}, {name: "bar"}], function(d) { return d.name; });
map.get("foo"); // {"name": "foo"}
map.get("bar"); // {"name": "bar"}
map.get("baz"); // undefined
参考 nests.
当且仅当 map
中包含指定的 key 的时候返回 true
, 要注意其对应的 value 可能为 null
或者 undefined
返回指定的 key 对应的值,如果 map
中不包含指定的 key 则返回 undefined
设置 map
中指定的 key 为 value, 如果已经有相同的 key 字符串则会被覆盖,此方法返回 map
对象因此可以链式调用. 例如:
var map = d3.map()
.set("foo", 1)
.set("bar", 2)
.set("baz", 3);
map.get("foo"); // 1
如果 map
中包含指定的 key 则将其删除并返回 true
, 否则什么都不做并返回 false
.
清空 map
中所有的项
以数组的形式返回 map
中所有的 keys, 顺序是不可靠的.
以数组的形式返回 map
中所有的 value, 顺序是不可靠的.
将 map
中所有的项重组为 key-value
数组. 顺序是随意的.每一项中 key 必须是字符串, 但是对应的 value 可以是任意的类型.
遍历 map
中的每一项, 并对每一项执行 function, 当前项的 value
和 key
作为参数, 随后是 map
本身, 返回 undefined
.
当且仅当 map
中没有任何项时返回 true
.
返回 map
中项的个数.
与 ES6 Sets类似,但是有以下不同:
- 值会被强制转换为字符串.
- set.each, 而非 set.forEach. (也没有 thisArg 参数.)
- set.remove, 而非 set.delete.
- set.size 是一个方法, 而不是 property; set.empty 也是一个方法而不是属性.
# d3.set([array[, accessor]]) <源码>
构建一个新的 set
对象. 如果指定了 array 则将 array 中的元素都作为 set
的元素并返回.array 也可以是其他的 set
对象.如果指定了 accessor , 则在将元素添加到 set
之前先对每个元素执行执行的访问器方法.
当 set
中包含指定的 value 字符串时返回 true
.
将指定的 value 字符串添加到 set
中并返回 set
, 因此支持链式语法:
var set = d3.set()
.add("foo")
.add("bar")
.add("baz");
set.has("foo"); // true
如果 set
中存在 value 字符串则将其移除并返回 true
, 否则什么都不做并返回 false
.
清空 set
.
以数组的形式返回 set
中所有的 values
, 顺序是任意不可信的,可以方便的用来去重:
d3.set(["foo", "bar", "foo", "baz"]).values(); // "foo", "bar", "baz"
为 set
中每个元素执行 function, 前两个参数都为 value
(为了和 map.each对称),其次为 set
自身. 最终返回 undefined
, 遍历次序任意不可信.
当 set
中没有任何项时返回 true
.
返回 set
中包含的项的个数.
Nests
(嵌套操作)可以将数组类型的元素重组为层次结构数据。想象一下 SQL
中的 GROUP BY
操作。除了可以对元素进行分组之外,嵌套操作的输出是一个树而不是扁平的表。树的具体结构层级由 key
函数决定。同时可以根据 value
对树的叶节点进行排序,而非叶节点可以根据 key
进行排序. 可选的 rollup
操作可以用来指定对叶节点组进行处理的函数。Nest
操作(由nest返回的对象)是可以多次使用的,并且不保留对嵌套数据的任何引用。
例如,考虑如下的扁平数据结构:
var yields = [
{yield: 27.00, variety: "Manchuria", year: 1931, site: "University Farm"},
{yield: 48.87, variety: "Manchuria", year: 1931, site: "Waseca"},
{yield: 27.43, variety: "Manchuria", year: 1931, site: "Morris"},
...
];
在可视化时可能需要先根据年份然后按照类别进行分组重构,可以进行如下操作:
var entries = d3.nest()
.key(function(d) { return d.year; })
.key(function(d) { return d.variety; })
.entries(yields);
返回的结果是一个嵌套的数组,最外层数据都由键值对组成:
[{key: "1931", values: [
{key: "Manchuria", values: [
{yield: 27.00, variety: "Manchuria", year: 1931, site: "University Farm"},
{yield: 48.87, variety: "Manchuria", year: 1931, site: "Waseca"},
{yield: 27.43, variety: "Manchuria", year: 1931, site: "Morris"}, ...]},
{key: "Glabron", values: [
{yield: 43.07, variety: "Glabron", year: 1931, site: "University Farm"},
{yield: 55.20, variety: "Glabron", year: 1931, site: "Waseca"}, ...]}, ...]},
{key: "1932", values: ...}]
嵌套的数据结构能轻松的在 SVG
或 HTML
文档中生成分层结构。
更多介绍参考:
- Phoebe Bright’s D3 Nest Tutorial and examples
- Shan Carter’s Mister Nester
构建一个新的嵌套操作。keys
初始为空
注册一个新的 key
函数,key
函数将会在输入数组的每个元素上进行调用,并且返回一个字符串标识用来对所有元素进行分组。大多数情况下,是一个简单的访问器,就行上述例子中的年份和种类访问器一样。(key
方法并不传递当前数组的索引),每次注册 key
后,其会被添加到 keys
内部数组的末尾,并且嵌套操作会根据指定的 key
再嵌套一层分组,某种意义上讲 key
的数量决定了最终嵌套结果的深度。
# nest.sortKeys(comparator) <源码>
为 current key(当前 key) 指定一个 comparator 函数用以对当前 key
下的元素排序。和 d3.ascending 或 d3.descending 类似。如果没有指定,则默认的 key
排序为 undefined
。例如,在第一层和第二次分别根据 year
和 varieties
进行排序:
var entries = d3.nest()
.key(function(d) { return d.year; }).sortKeys(d3.ascending)
.key(function(d) { return d.variety; }).sortKeys(d3.descending)
.entries(yields);
排序操作仅仅影响 nest.entries 的结果。因为 nest.map 和 nest.object 返回的 keys
的顺序总是是未知的。
# nest.sortValues(comparator) <源码>
为嵌套操作的叶节点指定一个 comparator,和 d3.ascending 或 d3.descending 类似。这与对数组进行嵌套重组之前进行排序是大致一致的。如果没有指定比较函数,则元素的顺序会依照输入数组的顺序排序。这个操作对 nest.map, nest.entries 和 nest.object 都有影响。
指定一个 rollup(归纳)
函数,应用在每个分组的叶节点上。归纳函数的返回值将代替由 nest.map 或 nest.object 返回的叶节点元素。对于nest.entries 操作,它会代替用 entry.value 代替 entry.values。如果指定了 leaf comparator 则叶节点会在调用归纳函数之前调用。函数参数会传递当前叶节点组。注意:上述操作都在定义嵌套的行为,要将嵌套应用到具体的数据上,需要调用 nest.map
, nest.object
或者 nest.entries
方法来获取嵌套后的具体形式
将嵌套操作应用到指定的数组,并返回嵌套后的 [map](#map)
。返回的 map
映射中的每一项都对应由第一个 key
函数的返回的独特的 key
值。每一项的值都取决于注册的 key
函数的数量:如果不止一个 key
函数则值是一个 map
,否则就是数组中经过过滤之后对应的 key
的元素数组。如果没有定义 key
函数,则返回输入数组。
将指定的嵌套操作应用到指定的 array, 返回一个嵌套对象。返回的关联数组中的每一项都与第一个 key
函数相关。每一项的值取决于 key
函数的个数:如果不止一个 key
函数则值为一个关联数组,否则是经过 key
函数过滤之后的数组。
注意的是如果输入数组中包括与 JavaScript
内置 key
(比如__proto__)冲突的话是不安全的。如果不能保证数组中所有的 key
都是安全的请使用 nest.map 代替。
将指定的嵌套操作应用到指定的 array,返回经过嵌套处理后的 key-values
对。与将 map.entries 应用到关联数组类似。但是具有层次结构,嵌套体现在 value
,如果指定了多个 key
则如果对应的 key
下有数据的话,其 value
也为一个 key-values
对象,以此类推。