vega-hierarchy
Version:
Hierarchical layout transforms for Vega dataflows.
128 lines (108 loc) • 3.11 kB
JavaScript
import lookup from './lookup.js';
import {Transform, ingest, isTuple, tupleid} from 'vega-dataflow';
import {array, error, inherits} from 'vega-util';
import {hierarchy} from 'd3-hierarchy';
/**
* Nest tuples into a tree structure, grouped by key values.
* @constructor
* @param {object} params - The parameters for this operator.
* @param {Array<function(object): *>} params.keys - The key fields to nest by, in order.
* @param {boolean} [params.generate=false] - A boolean flag indicating if
* non-leaf nodes generated by this transform should be included in the
* output. The default (false) includes only the input data (leaf nodes)
* in the data stream.
*/
export default function Nest(params) {
Transform.call(this, null, params);
}
Nest.Definition = {
'type': 'Nest',
'metadata': {'treesource': true, 'changes': true},
'params': [
{ 'name': 'keys', 'type': 'field', 'array': true },
{ 'name': 'generate', 'type': 'boolean' }
]
};
const children = n => n.values;
inherits(Nest, Transform, {
transform(_, pulse) {
if (!pulse.source) {
error('Nest transform requires an upstream data source.');
}
var gen = _.generate,
mod = _.modified(),
out = pulse.clone(),
tree = this.value;
if (!tree || mod || pulse.changed()) {
// collect nodes to remove
if (tree) {
tree.each(node => {
if (node.children && isTuple(node.data)) {
out.rem.push(node.data);
}
});
}
// generate new tree structure
this.value = tree = hierarchy({
values: array(_.keys)
.reduce((n, k) => { n.key(k); return n; }, nest())
.entries(out.source)
}, children);
// collect nodes to add
if (gen) {
tree.each(node => {
if (node.children) {
node = ingest(node.data);
out.add.push(node);
out.source.push(node);
}
});
}
// build lookup table
lookup(tree, tupleid, tupleid);
}
out.source.root = tree;
return out;
}
});
function nest() {
const keys = [],
nest = {
entries: array => entries(apply(array, 0), 0),
key: d => (keys.push(d), nest)
};
function apply(array, depth) {
if (depth >= keys.length) {
return array;
}
const n = array.length,
key = keys[depth++],
valuesByKey = {},
result = {};
let i = -1, keyValue, value, values;
while (++i < n) {
keyValue = key(value = array[i]) + '';
if (values = valuesByKey[keyValue]) {
values.push(value);
} else {
valuesByKey[keyValue] = [value];
}
}
for (keyValue in valuesByKey) {
result[keyValue] = apply(valuesByKey[keyValue], depth);
}
return result;
}
function entries(map, depth) {
if (++depth > keys.length) return map;
const array = [];
for (const key in map) {
array.push({
key,
values: entries(map[key], depth)
});
}
return array;
}
return nest;
}