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();
+});
+