Сводка D3.js для оператора вложенности с несколькими ключами

Я работаю над визуализацией d3.js для приложения для представления времени. У меня есть данные строки в массиве, в котором содержатся отчеты о времени проекта (упрощенный):

[{  resource: "John Smith",
    reporting_period: "2012/04/1",
    project: "Java implementation",
    hours: 8} 
 ... }]

Я пытаюсь использовать оператор d3.nest для группировки фактических проектов по иерархии проекта, ресурса и периода. Все работает отлично, но я не могу найти способ получить промежуточные промежутки времени на промежуточных уровнях группировки, используя оператор nest.rollup().

У меня есть что-то вроде:

actuals_by_prj_rsrc_period = d3.nest()
        .key(function(d) { return d["project"]; })
        .key(function(d) { return d["resource"]; })
        .key(function(d) { return d["reporting_period"]; })
        .rollup(function(rows) {return {
            tot_hours:d3.sum(rows, function(d) {return d["hours"];}),
            actuals: rows
        };})
        .entries(actuals);

но он возвращает tot_hours только на уровне листа. Любые советы о том, как подойти к этому, используя только d3.nest?

Ответ 1

из документов:

nest.rollup(функция)

Определяет функцию сворачивания, которая должна применяться к каждой группе листаэлементы. Возвращаемое значение функции rollup заменит массив значений листа в ассоциативном массиве, возвращаемом оператора карты или атрибута значений для каждой записи, возвращаемой записей.

Как вы видите, rollup работает с листовыми элементами. Вы можете обойти это, если данные вложены на несколько уровней:

function nest(keys, data, rollupFunc) {
    var nst = d3.nest();
    keys.forEach(function(key) {
        nst.key(function(d) { return d[key]; });
    });
    if (rollupFunc) {
        nst.rollup(rollupFunc);
    }
    return nst.entries(data);
}

var rollupFunction = function(d) {
    return {
        "total_hours": d3.sum(d, function(dd) { return dd["hours"]})
    }
}

var rez1 = nest(["project", "resource"], actuals);
var rez2 = nest(["project"], actuals, rollupFunction);
var rez3 = nest(["project", "resource"], actuals, rollupFunction);

Но это очень неэффективно для больших наборов данных. В противном случае я бы предложил использовать функцию nest() для создания всех промежуточных уровней. Затем вы можете суммировать общие часы с помощью собственной рекурсивной функции. Псевдокод:

function aggregate(node) {
    if (node has property total_hours) {
        return total_hours
    }
    sum = 0
    foreach child in data.values {
        sum += aggregate(child)    
    }
    node.total_hours = sum
    return node.total_hours 
}