Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to replace a d3js nest function with group and rollup

Tags:

I'm struggling to translate some older d3js code to the newest version (V6). It seems the old function nest() has been deprecated. From now on the docs recommend using group() and rollup(), however, they don't give any real good examples and I struggle to achieve the same data structure with these new functions.

Data Input:

[
  {
    name: "Anton",
    year: "1990"
  },
  {
    name: "Anton",
    year: "1990"
  },
  {
    name: "Anton",
    year: "1971"
  },
  {
    name: "Markus",
    year: "1981"
  },
]

Old D3 Code:

let nested = d3.nest()
  .key(function (d) { return d.name })     
  .key(function (d) { return d.year })
  .rollup(function (leaves) { return leaves.length })
  .entries(data)

Output (correct):

[
  {
    key: "Anton"
    values: [
      {
        key: "1990",
        value: 2
      }
      {
        key: "1971",
        value: 1
      }
    ]  
  },
  {
    key: "Markus"
    values: [
      key: "1981"
      value: 1
    ]
  }
]

New Code

I tried to combine group and rollup in different ways, but I always end up with another data structure. The closes I've come is with this:

let rollup = d3.rollups(data, v => v.length, d => d.name, d => d.year);
let arrayFromRollup = Array.from(rollup, ([key, value]) => ({key, value}));

Output (wrong)

[
  {
    key: "Anton"
    values: [
      ["1990", 2],
      ["1971", 1]
    ]  
  },
  {
    key: "Markus"
    values: [
      ["1981", 1]
    ]
  }
]

I'm helpful for any advice, apparently, most similar questions/posts I've found are quite old and work with nest.

like image 286
Max Avatar asked Oct 06 '20 14:10

Max


1 Answers

D3 provides excellent convenience functions, but with the rise of ES6, array and object manipulation has become a lot easier. You can achieve this using pure ES6 methods:

const input = [{
    name: "Anton",
    year: "1990"
  },
  {
    name: "Anton",
    year: "1990"
  },
  {
    name: "Anton",
    year: "1971"
  },
  {
    name: "Markus",
    year: "1981"
  },
]

const expected = [
  {
    key: "Anton",
    values: [
      {
        key: "1990",
        value: 2
      },
      {
        key: "1971",
        value: 1
      }
    ],
  },
  {
    key: "Markus",
    values: [
      {
        key: "1981",
        value: 1,
      }
    ],
  },
];

// For ease of use, start with an object, we map key to values
// and unpack it later. The structure we go for now:
// { name: { year: count }}
const nameYearsCount = input.reduce((obj, {
  name,
  year
}) => {
  if (!(name in obj)) {
    obj[name] = {
      [year]: 1
    };
  } else {

    // I.e. if the year doesn't exist, default to zero
    obj[name][year] = (obj[name][year] || 0) + 1
  }
  return obj;
}, {});

// Now transform it into the desired format
const result = Object.entries(nameYearsCount)
  .map(([name, yearsCount]) => {
    const values = Object.entries(yearsCount)
      .map(([year, count]) => ({
        key: year,
        value: count
      }));
    return {
      key: name,
      values
    };
  });

console.log(result);
like image 143
Ruben Helsloot Avatar answered Sep 30 '22 17:09

Ruben Helsloot