Функции d3.group и d3.rollup предназначены для преобразования плоских наборов данных в структурированные представления, удобные для агрегации, визуализации и построения многомерных иерархий. Оба метода работают с итерируемыми коллекциями и возвращают структуры на основе Map, но различаются уровнем агрегации и формой результата.
Функция d3.group() формирует вложенные структуры данных,
где каждый уровень группировки создаётся на основе переданной
функции-ключа. Результатом является вложенный Map, в
котором ключи соответствуют значениям группирующих функций, а значения —
либо вложенные Map, либо массив исходных элементов.
d3.group(data, ...keys)
data — исходный массив объектовkeys — функции, каждая из которых определяет уровень
группировкиКаждая функция принимает элемент массива и возвращает значение, используемое как ключ.
const data = [
{ country: "KZ", city: "Karaganda", value: 10 },
{ country: "KZ", city: "Almaty", value: 20 },
{ country: "RU", city: "Moscow", value: 30 }
];
const grouped = d3.group(data, d => d.country);
Результат:
Map {
"KZ" => [ {...}, {...} ],
"RU" => [ {...} ]
}
На верхнем уровне создаётся Map, где ключи — страны, а
значения — массивы объектов.
const grouped = d3.group(
data,
d => d.country,
d => d.city
);
Структура результата:
Map {
"KZ" => Map {
"Karaganda" => [ {...} ],
"Almaty" => [ {...} ]
},
"RU" => Map {
"Moscow" => [ {...} ]
}
}
Каждый дополнительный ключ добавляет уровень вложенности. Это позволяет строить иерархии произвольной глубины.
d3.group() возвращает не обычные объекты, а
Map, что даёт важные свойства:
.get(), .set(),
.has(), .forEach()Пример обхода:
for (const [country, cities] of grouped) {
for (const [city, items] of cities) {
console.log(country, city, items.length);
}
}
Для визуализаций часто требуется массив вместо Map:
const arrayForm = Array.from(grouped, ([key, value]) => ({
key,
value
}));
Или рекурсивно:
const toArray = map =>
Array.from(map, ([key, value]) => ({
key,
value: value instanceof Map ? toArray(value) : value
}));
Функция d3.rollup() расширяет идею группировки, добавляя
вычисление агрегированных значений на каждом уровне. Вместо массивов
элементов возвращаются результаты редукции.
d3.rollup(data, reducer, ...keys)
data — массив объектовreducer — функция агрегацииkeys — функции группировкиconst rolled = d3.rollup(
data,
v => d3.sum(v, d => d.value),
d => d.country
);
Результат:
Map {
"KZ" => 30,
"RU" => 30
}
Здесь:
v — массив элементов внутри группыd3.sum вычисляет сумму значенийconst rolled = d3.rollup(
data,
v => d3.mean(v, d => d.value),
d => d.country
);
Результат содержит средние значения по каждой стране.
const rolled = d3.rollup(
data,
v => d3.sum(v, d => d.value),
d => d.country,
d => d.city
);
Структура:
Map {
"KZ" => Map {
"Karaganda" => 10,
"Almaty" => 20
},
"RU" => Map {
"Moscow" => 30
}
}
Каждый уровень сохраняет агрегированное значение вместо массива объектов.
d3.group() → вложенные Map с массивами
данныхd3.rollup() → вложенные Map с
агрегированными значениямиПодходит для случаев, когда требуется:
Пример сценария:
Подходит для задач:
Примеры метрик:
Обе функции поддерживают произвольное число уровней группировки.
const rolled = d3.rollup(
data,
v => d3.sum(v, d => d.value),
d => d.country,
d => Math.floor(d.value / 10)
);
Это создаёт группы по стране и диапазонам значений.
Ключи в группировке не ограничиваются строками:
d3.group(data, d => new Date(d.date).getFullYear());
или даже объектами:
const keyFn = d => ({ type: d.type });
d3.group(data, keyFn);
Map позволяет использовать такие ключи без приведения к строке, в отличие от обычных объектов JavaScript.
В d3.rollup() часто применяются функции из
d3-array:
d3.sumd3.meand3.mind3.maxd3.mediand3.count (через v.length)Пример комбинирования:
const stats = d3.rollup(
data,
v => ({
total: d3.sum(v, d => d.value),
avg: d3.mean(v, d => d.value),
count: v.length
}),
d => d.category
);
Здесь агрегатор возвращает объект, а не число.
Для визуализаций часто требуется преобразовать Map в массив объектов:
const flattened = Array.from(rolled, ([key, value]) => ({
key,
value
}));
Для вложенных структур:
function flatten(map, depth = 0) {
return Array.from(map, ([key, value]) => ({
key,
value: value instanceof Map ? flatten(value, depth + 1) : value,
depth
}));
}
d3.rollup() часто используется для подготовки
данных:
const counts = d3.rollup(
data,
v => v.length,
d => d.category
);
Далее результат легко преобразуется в шкалу:
const dataset = Array.from(counts, ([category, count]) => ({
category,
count
}));
const stacked = d3.rollup(
data,
v => d3.sum(v, d => d.value),
d => d.group,
d => d.subgroup
);
Такой формат удобно преобразовать для d3.stack().
Если входной массив пуст, обе функции возвращают пустой
Map:
d3.group([], d => d.key); // Map {}
d3.rollup([], v => v.length, d => d.key); // Map {}
Это важно для безопасной обработки динамических данных.
Внутри rollup допустимо использовать произвольную
логику:
d3.rollup(
data,
v => {
const sorted = v.sort((a, b) => b.value - a.value);
return {
top: sorted[0],
bottom: sorted[sorted.length - 1]
};
},
d => d.group
);
Таким образом можно строить не только числовые метрики, но и структурные срезы данных.
grouped.forEach((value, key) => {
console.log(key, value);
});
rolled.forEach((value, key) => {
console.log(key, value);
});
Методы идентичны, так как обе структуры основаны на
Map.
Иногда используется двухэтапная обработка:
const grouped = d3.group(data, d => d.country);
const stats = new Map(
Array.from(grouped, ([country, values]) => [
country,
d3.rollup(values, v => d3.sum(v, d => d.value), d => d.city)
])
);
Так создаются гибридные структуры: группировка + агрегирование на разных уровнях.