d3.rollup(iterable, reduce, …keys)

将可迭代的值转化为Map,转化结果从key到value,并支持嵌套。

function dogroup(values, keyof) {
const map = new Map();
let index = -1;
for (const value of values) {
const key = keyof(value, ++index, values);
const group = map.get(key);
if (group) group.push(value);
else map.set(key, [value]);
}
return map;
}

export default function rollup(values, reduce, ...keys) {
return (function regroup(values, i) {
if (i >= keys.length) return reduce(values);
const map = dogroup(values, keys[i]);
return new Map(Array.from(map, ([k, v]) => [k, regroup(v, i + 1)]));
})(values, 0);
}

这里rollup返回一个立即执行函数,该函数会首先执行一次dogroup函数,根据keys的对应关系生成一个map,然后根据keys的长度去递归的调用regroup生成嵌套map,最后调用reduce。形成一条k=>k=>value递归链。用法如下:

d3.rollup(data, v => v.length, d => d.name, d => d.date)

结果为

Map(3) {
"jim" => Map(1) {
"11/12/2015" => 1
}
"carl" => Map(1) {
"11/12/2015" => 1
}
"stacy" => Map(1) {
"01/04/2016" => 2
}
}

d3.group(iterable, …keys)

import identity from "./identity.js";
import rollup from "./rollup.js";

export default function group(values, ...keys) {
return rollup(values, identity, ...keys);
}

identity.js

export default function(x) {
return x;
}

该函数是简化的rollup,rollup的结果是key对应到value上,group则是key对应到数组上,减少了计算值的一个函数(使用默认的identity)。用法如下:

data = [
{name: "jim", amount: "34.0", date: "11/12/2015"},
{name: "carl", amount: "120.11", date: "11/12/2015"},
{name: "stacy", amount: "12.01", date: "01/04/2016"},
{name: "stacy", amount: "34.05", date: "01/04/2016"}
]
d3.group(data, d => d.name)
Map(3) {
"jim" => Array(1)
"carl" => Array(1)
"stacy" => Array(2)
}

d3.cross(…iterables[, reducer])

function length(array) {
return array.length | 0;
}

function empty(length) {
return !(length > 0);
}

function arrayify(values) {
return typeof values !== "object" || "length" in values ? values : Array.from(values);
}

function reducer(reduce) {
return values => reduce(...values);
}

export default function cross(...values) {
const reduce = typeof values[values.length - 1] === "function" && reducer(values.pop());//如果最后参数传入函数,对每一个valuesz执行该函数
values = values.map(arrayify);//调用array,from将values转化为数组
const lengths = values.map(length);//length保存数组长度的数组
const j = values.length - 1;
const index = new Array(j + 1).fill(0);//见过这道题。。
const product = [];
if (j < 0 || lengths.some(empty)) return product;//如果数组长度小于0或者有数组为空,就返回一个空数组
while (true) {//这个函数修改了原数组的顺序
product.push(index.map((j, i) => values[i][j]));
let i = j;
while (++index[i] === lengths[i]) {
if (i === 0) return reduce ? product.map(reduce) : product;
index[i--] = 0;
}
}
}

返回两个数组的笛卡儿积。

d3.merge(iterables)

和concat类似,讲数组进行合并,用法如下:

d3.merge([[1], [2, 3]]); // returns [1, 2, 3]
function* flatten(arrays) {
for (const array of arrays) {
yield* array;
}
}

export default function merge(arrays) {
return Array.from(flatten(arrays));
}

实现中用到了生成器函数返回生成器的迭代器对象,再通过array.from转化成数组,array.from转化条件有2条,1,是要求维数组对象,拥有一个length属性和若干索引属性的任意对象2,可迭代对象,可以获取对象中的元素,如map,set

d3.pairs(iterable[, reducer])

d3.pairs([1, 2, 3, 4]); // returns [[1, 2], [2, 3], [3, 4]]
d3.pairs([1, 2, 3, 4], (a, b) => b - a); // returns [1, 1, 1];

返回一个数组对,如果给定了reducer函数,返回计算后的数组。

export default function pairs(values, pairof = pair) {
const pairs = [];
let previous;
let first = false;
for (const value of values) {
if (first) pairs.push(pairof(previous, value));
previous = value;
first = true;
}
return pairs;
}

export function pair(a, b) {
return [a, b];
}

这个很简单,就是一个for循环,用一个previous记录上一个数组的值,第一次有特殊处理。

d3.permute(array, indexes)

permute(["a", "b", "c"], [1, 2, 0]); // returns ["b", "c", "a"]
export default function(source, keys) {
return Array.from(keys, key => source[key]);
}

根据输入的数组以及一个key值数组,修改原数组顺序。

d3.shuffle(array[, start[, stop]])

export default function shuffle(array, i0 = 0, i1 = array.length) {
var m = i1 - (i0 = +i0),
t,
i;

while (m) {
i = Math.random() * m-- | 0;
t = array[m + i0];
array[m + i0] = array[i + i0];
array[i + i0] = t;
}

return array;
}

随机打乱数组顺序,说是用Fisher–Yates Shuffle算法,代码就是一个随机数互换位置。

d3.ticks(start, stop, count)

d3.range([start, ]stop[, step])

根据输入值返回一个数组。

export default function(start, stop, step) {
start = +start, stop = +stop, step = (n = arguments.length) < 2 ? (stop = start, start = 0, 1) : n < 3 ? 1 : +step;

var i = -1,
n = Math.max(0, Math.ceil((stop - start) / step)) | 0,
range = new Array(n);

while (++i < n) {
range[i] = start + i * step;
}

return range;
}

d3.zip(arrays…)

根据输入数组,返回的多维数组第i个元素包含每个输入数组的第一个元素。

import min from "./min.js";

export default function(matrix) {
if (!(n = matrix.length)) return [];
for (var i = -1, m = min(matrix, length), transpose = new Array(m); ++i < m;) {
for (var j = -1, n, row = transpose[i] = new Array(n); ++j < n;) {
row[j] = matrix[j][i];
}
}
return transpose;
}

function length(d) {
return d.length;
}

d3.transpose(matrix)

和d3.zip一样对矩阵进行操作

d3.ticks(start, stop, count)

根据输入,返回一个分段数组,这个有点复杂,后面在仔细分析。