diff --git a/README.md b/README.md index 8a224d6f..9ab02492 100644 --- a/README.md +++ b/README.md @@ -526,7 +526,7 @@ d3.range(49).map(function(d) { return d / 49; }); // GOOD: returns 49 elements. # d3.transpose(matrix) · [Source](https://github.com/d3/d3-array/blob/master/src/transpose.js), [Examples](https://observablehq.com/@d3/d3-transpose) -Uses the [zip](#zip) operator as a two-dimensional [matrix transpose](http://en.wikipedia.org/wiki/Transpose). +A two-dimensional [matrix transpose](http://en.wikipedia.org/wiki/Transpose). Accepts matrices as arrays of arrays [[]], arrays of objects [{}], objects of arrays {[]} and objects of objects {{}}. # d3.zip(arrays…) · [Source](https://github.com/d3/d3-array/blob/master/src/zip.js), [Examples](https://observablehq.com/@d3/d3-transpose) diff --git a/src/transpose.js b/src/transpose.js index 5ef3bfee..e6688ac9 100644 --- a/src/transpose.js +++ b/src/transpose.js @@ -1,15 +1,30 @@ -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]; + // matrix is a key-value store of lines, themselves key-value stores of data. + // dimension y of the incoming matrix + const y = Object.keys(matrix); + if (!y.length) return []; + + // dimension x of the incoming matrix + const line0 = matrix[y[0]], + x = new Set(Object.keys(line0)), + transpose = line0.length ? [] : {}; + + // prepare the transpose matrix with x as first dimension + for (const k of x) { + transpose[k] = matrix.length ? [] : {}; + } + for (const [i, line] of Object.entries(matrix)) { + for (const k of x) { + // checks that each key is present in the line, otherwise: + // - remove that key from the transpose (for lines already read) + // - remove it from x (ignores it in future lines) + if (!(k in line)) { + delete transpose[k]; + x.delete(k); + } else { + transpose[k][i] = line[k]; + } } } return transpose; } - -function length(d) { - return d.length; -} diff --git a/test/transpose-test.js b/test/transpose-test.js index 9321da4b..758bff23 100644 --- a/test/transpose-test.js +++ b/test/transpose-test.js @@ -35,3 +35,29 @@ tape("transpose(…) returns a copy", function(test) { test.deepEqual(tranpose, [[1, 3], [2, 4]]); test.end(); }); + +tape("transpose([objects]) transposes an array of objects", function(test) { + test.deepEqual(arrays.transpose([{a:1, b:2}, {a:3, b:4}, {a:5, b:6}]), {a: [1, 3, 5], b: [2, 4, 6]}); + test.end(); +}); + +tape("transpose([objects]) only uses properties present in all the objects", function(test) { + test.deepEqual(arrays.transpose([{a:1, b:2, c:-1}, {a:3, b:4}, {a:5, b:6, d:-1}]), {a: [1, 3, 5], b:[2, 4, 6]}); + test.end(); +}); + +tape("transpose(object) transposes an object of arrays", function(test) { + test.deepEqual(arrays.transpose({a: [1, 3, 5], b: [2, 4, 6]}), [{a:1, b:2}, {a:3, b:4}, {a:5, b:6}]); + test.end(); +}); + +tape("transpose(object) ignores extra elements", function(test) { + test.deepEqual(arrays.transpose({a: [1, 3, 5], b: [2, 4, 6, 8]}), [{a:1, b:2}, {a:3, b:4}, {a:5, b:6}]); + test.end(); +}); + +tape("transpose(object) transposes an object of objects", function(test) { + test.deepEqual(arrays.transpose({A: {a:1, b:2, c:-1}, B: {a:3, b:4}, C: {a:5, b:6, d:-1}}), { a: { A: 1, B: 3, C: 5 }, b: { A: 2, B: 4, C: 6 } }); + test.end(); +}); +