Tom Roth
Tom Roth

Reputation: 2074

Nesting d3.max with array of arrays

I have an array of arrays like so.

data = [
  [
    {x: 1, y: 40},
    {x: 2, y: 43},
    {x: 3, y: 12},
    {x: 4, y: 60},
    {x: 5, y: 63},
    {x: 6, y: 23}
 ], [
    {x: 1, y: 12},
    {x: 2, y: 5},
    {x: 3, y: 23},
    {x: 4, y: 18},
    {x: 5, y: 73},
    {x: 6, y: 27}
 ], [
    {x: 1, y: 60},
    {x: 2, y: 49},
    {x: 3, y: 16},
    {x: 4, y: 20},
    {x: 5, y: 92},
    {x: 6, y: 20}
  ] 
];

I can find the maximum y value of data with a nested d3.max() call:

d3.max(data, function(d) {
  return d3.max(d, function(d) {
    return d.y;
  });
});

I'm struggling to understand how this code actually works. I know the second argument of the d3.max() function specifies an accessor function - but I'm confused into how exactly calling d3.max() twice relates with the accessor function.

I guess what I'm asking for is a walkthrough of how javascript interprets this code. I've walked through it on the console but it didn't help unfortunately.

Upvotes: 1

Views: 1316

Answers (2)

Redu
Redu

Reputation: 26181

Wow..! intriguing question really. Just for some sporting purposes here is an ES6 resolution of this problem by invention of an array method called Array.prototype.maxByKey() So here you can see how in fact it's implemented by pure JS.

Array.prototype.maxByKey = function(k) {
  var m = this.reduce((m,o,i) => o[k] > m[1] ? [i,o[k]] : m ,[0,Number.MIN_VALUE]);
  return this[m[0]];
};
var data = [
[{x: 1, y: 40},{x: 2, y: 43},{x: 3, y: 12},{x: 4, y: 60},{x: 5, y: 63},{x: 6, y: 23}],
[{x: 1, y: 12},{x: 2, y: 5},{x: 3, y: 23},{x: 4, y: 18},{x: 5, y: 73},{x: 6, y: 27}],
[{x: 1, y: 60},{x: 2, y: 49},{x: 3, y: 16},{x: 4, y: 20},{x: 5, y: 92},{x: 6, y: 20}]
],
maxObj = data.map(a => a.maxByKey("y")).maxByKey("y");
console.log(maxObj);

Here is the story of what's going on in this piece of code. We will find the index of the object by reducing. Our reduce method uses an initial value, which is array [0,Number.MIN_VALUE], which at index 0 has 0 and at index 1 position has the smallest possible number in JS. Initial values are set to the first argument. So here m starts with the initial value. Reduce will walk over the array items (objects in our case) one by one and each time o will be assigned to the current object and the last argument i is of course the index of the position we are currently working on. k is provided to our function as the key that we will be using to test the max value upon.

So there is this simple ternary comparison o[k] > m[1] ? [i,o[k]] : m which means check current object property given by k (o[k]) if it is less than m[1] (where m is [0,Number.MIN_VALUE] in the first turn) return m as [i,o[k]] (check how ternaries return result) if it is not less than m[1] then return m as it is. And at the end of the walk we will be reduced down to [index of the element with max k property value, the value of that k property] in that array.

So as you see it is very simple.

Upvotes: 0

pintxo
pintxo

Reputation: 2155

Sometimes it's all about the naming of the variables:

// the outer function iterates over the outer array
// which we can think of as an array of rows

d3.max(data, function(row) {

  // while the inner function iterates over the inner
  // array, which we can think of as an array containing
  // the columns of a single row. Sometimes also called
  // a (table) cell.

  return d3.max(row, function(column) {
    return column.y;
  });

});

You can find the source code for the d3.max function here: https://github.com/d3/d3.github.com/blob/8f6ca19c42251ec27031376ba9168f23b9546de4/d3.v3.js#L69

Upvotes: 3

Related Questions