StackGenVis: Alignment of Data, Algorithms, and Models for Stacking Ensemble Learning Using Performance Metrics
https://doi.org/10.1109/TVCG.2020.3030352
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
507 lines
15 KiB
507 lines
15 KiB
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vega-dataflow'), require('vega-util'), require('d3-hierarchy')) :
|
|
typeof define === 'function' && define.amd ? define(['exports', 'vega-dataflow', 'vega-util', 'd3-hierarchy'], factory) :
|
|
(global = global || self, factory((global.vega = global.vega || {}, global.vega.transforms = {}), global.vega, global.vega, global.d3));
|
|
}(this, function (exports, vegaDataflow, vegaUtil, d3Hierarchy) { 'use strict';
|
|
|
|
// Build lookup table mapping tuple keys to tree node instances
|
|
function lookup(tree, key, filter) {
|
|
var map = {};
|
|
tree.each(function(node) {
|
|
var t = node.data;
|
|
if (filter(t)) map[key(t)] = node;
|
|
});
|
|
tree.lookup = map;
|
|
return tree;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
function Nest(params) {
|
|
vegaDataflow.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" }
|
|
]
|
|
};
|
|
|
|
var prototype = vegaUtil.inherits(Nest, vegaDataflow.Transform);
|
|
|
|
function children(n) {
|
|
return n.values;
|
|
}
|
|
|
|
prototype.transform = function(_, pulse) {
|
|
if (!pulse.source) {
|
|
vegaUtil.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 && vegaDataflow.isTuple(node.data)) {
|
|
out.rem.push(node.data);
|
|
}
|
|
});
|
|
}
|
|
|
|
// generate new tree structure
|
|
this.value = tree = d3Hierarchy.hierarchy({
|
|
values: vegaUtil.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 = vegaDataflow.ingest(node.data);
|
|
out.add.push(node);
|
|
out.source.push(node);
|
|
}
|
|
});
|
|
}
|
|
|
|
// build lookup table
|
|
lookup(tree, vegaDataflow.tupleid, vegaDataflow.tupleid);
|
|
}
|
|
|
|
out.source.root = tree;
|
|
return out;
|
|
};
|
|
|
|
function nest() {
|
|
var keys = [],
|
|
nest;
|
|
|
|
function apply(array, depth) {
|
|
if (depth >= keys.length) {
|
|
return array;
|
|
}
|
|
|
|
var i = -1,
|
|
n = array.length,
|
|
key = keys[depth++],
|
|
keyValue,
|
|
value,
|
|
valuesByKey = {},
|
|
values,
|
|
result = {};
|
|
|
|
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;
|
|
var array = [], k;
|
|
for (k in map) {
|
|
array.push({key: k, values: entries(map[k], depth)});
|
|
}
|
|
return array;
|
|
}
|
|
|
|
return nest = {
|
|
entries: array => entries(apply(array, 0), 0),
|
|
key: d => { keys.push(d); return nest; }
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Abstract class for tree layout.
|
|
* @constructor
|
|
* @param {object} params - The parameters for this operator.
|
|
*/
|
|
function HierarchyLayout(params) {
|
|
vegaDataflow.Transform.call(this, null, params);
|
|
}
|
|
|
|
var prototype$1 = vegaUtil.inherits(HierarchyLayout, vegaDataflow.Transform);
|
|
|
|
prototype$1.transform = function(_, pulse) {
|
|
if (!pulse.source || !pulse.source.root) {
|
|
vegaUtil.error(this.constructor.name
|
|
+ ' transform requires a backing tree data source.');
|
|
}
|
|
|
|
var layout = this.layout(_.method),
|
|
fields = this.fields,
|
|
root = pulse.source.root,
|
|
as = _.as || fields;
|
|
|
|
if (_.field) root.sum(_.field); else root.count();
|
|
if (_.sort) root.sort(vegaDataflow.stableCompare(_.sort, d => d.data));
|
|
|
|
setParams(layout, this.params, _);
|
|
if (layout.separation) {
|
|
layout.separation(_.separation !== false ? defaultSeparation : vegaUtil.one);
|
|
}
|
|
|
|
try {
|
|
this.value = layout(root);
|
|
} catch (err) {
|
|
vegaUtil.error(err);
|
|
}
|
|
root.each(function(node) { setFields(node, fields, as); });
|
|
|
|
return pulse.reflow(_.modified()).modifies(as).modifies('leaf');
|
|
};
|
|
|
|
function setParams(layout, params, _) {
|
|
for (var p, i=0, n=params.length; i<n; ++i) {
|
|
p = params[i];
|
|
if (p in _) layout[p](_[p]);
|
|
}
|
|
}
|
|
|
|
function setFields(node, fields, as) {
|
|
var t = node.data;
|
|
for (var i=0, n=fields.length-1; i<n; ++i) {
|
|
t[as[i]] = node[fields[i]];
|
|
}
|
|
t[as[n]] = node.children ? node.children.length : 0;
|
|
}
|
|
|
|
function defaultSeparation(a, b) {
|
|
return a.parent === b.parent ? 1 : 2;
|
|
}
|
|
|
|
var Output = ['x', 'y', 'r', 'depth', 'children'];
|
|
|
|
/**
|
|
* Packed circle tree layout.
|
|
* @constructor
|
|
* @param {object} params - The parameters for this operator.
|
|
* @param {function(object): *} params.field - The value field to size nodes.
|
|
*/
|
|
function Pack(params) {
|
|
HierarchyLayout.call(this, params);
|
|
}
|
|
|
|
Pack.Definition = {
|
|
"type": "Pack",
|
|
"metadata": {"tree": true, "modifies": true},
|
|
"params": [
|
|
{ "name": "field", "type": "field" },
|
|
{ "name": "sort", "type": "compare" },
|
|
{ "name": "padding", "type": "number", "default": 0 },
|
|
{ "name": "radius", "type": "field", "default": null },
|
|
{ "name": "size", "type": "number", "array": true, "length": 2 },
|
|
{ "name": "as", "type": "string", "array": true, "length": Output.length, "default": Output }
|
|
]
|
|
};
|
|
|
|
var prototype$2 = vegaUtil.inherits(Pack, HierarchyLayout);
|
|
|
|
prototype$2.layout = d3Hierarchy.pack;
|
|
|
|
prototype$2.params = ['radius', 'size', 'padding'];
|
|
|
|
prototype$2.fields = Output;
|
|
|
|
var Output$1 = ['x0', 'y0', 'x1', 'y1', 'depth', 'children'];
|
|
|
|
/**
|
|
* Partition tree layout.
|
|
* @constructor
|
|
* @param {object} params - The parameters for this operator.
|
|
* @param {function(object): *} params.field - The value field to size nodes.
|
|
*/
|
|
function Partition(params) {
|
|
HierarchyLayout.call(this, params);
|
|
}
|
|
|
|
Partition.Definition = {
|
|
"type": "Partition",
|
|
"metadata": {"tree": true, "modifies": true},
|
|
"params": [
|
|
{ "name": "field", "type": "field" },
|
|
{ "name": "sort", "type": "compare" },
|
|
{ "name": "padding", "type": "number", "default": 0 },
|
|
{ "name": "round", "type": "boolean", "default": false },
|
|
{ "name": "size", "type": "number", "array": true, "length": 2 },
|
|
{ "name": "as", "type": "string", "array": true, "length": Output$1.length, "default": Output$1 }
|
|
]
|
|
};
|
|
|
|
var prototype$3 = vegaUtil.inherits(Partition, HierarchyLayout);
|
|
|
|
prototype$3.layout = d3Hierarchy.partition;
|
|
|
|
prototype$3.params = ['size', 'round', 'padding'];
|
|
|
|
prototype$3.fields = Output$1;
|
|
|
|
/**
|
|
* Stratify a collection of tuples into a tree structure based on
|
|
* id and parent id fields.
|
|
* @constructor
|
|
* @param {object} params - The parameters for this operator.
|
|
* @param {function(object): *} params.key - Unique key field for each tuple.
|
|
* @param {function(object): *} params.parentKey - Field with key for parent tuple.
|
|
*/
|
|
function Stratify(params) {
|
|
vegaDataflow.Transform.call(this, null, params);
|
|
}
|
|
|
|
Stratify.Definition = {
|
|
"type": "Stratify",
|
|
"metadata": {"treesource": true},
|
|
"params": [
|
|
{ "name": "key", "type": "field", "required": true },
|
|
{ "name": "parentKey", "type": "field", "required": true }
|
|
]
|
|
};
|
|
|
|
var prototype$4 = vegaUtil.inherits(Stratify, vegaDataflow.Transform);
|
|
|
|
prototype$4.transform = function(_, pulse) {
|
|
if (!pulse.source) {
|
|
vegaUtil.error('Stratify transform requires an upstream data source.');
|
|
}
|
|
|
|
var tree = this.value,
|
|
mod = _.modified(),
|
|
out = pulse.fork(pulse.ALL).materialize(pulse.SOURCE),
|
|
run = !this.value
|
|
|| mod
|
|
|| pulse.changed(pulse.ADD_REM)
|
|
|| pulse.modified(_.key.fields)
|
|
|| pulse.modified(_.parentKey.fields);
|
|
|
|
// prevent upstream source pollution
|
|
out.source = out.source.slice();
|
|
|
|
if (run) {
|
|
if (out.source.length) {
|
|
tree = lookup(
|
|
d3Hierarchy.stratify().id(_.key).parentId(_.parentKey)(out.source)
|
|
, _.key, vegaUtil.truthy);
|
|
} else {
|
|
tree = lookup(d3Hierarchy.stratify()([{}]), _.key, _.key);
|
|
}
|
|
}
|
|
|
|
out.source.root = this.value = tree;
|
|
return out;
|
|
};
|
|
|
|
var Layouts = {
|
|
tidy: d3Hierarchy.tree,
|
|
cluster: d3Hierarchy.cluster
|
|
};
|
|
|
|
var Output$2 = ['x', 'y', 'depth', 'children'];
|
|
|
|
/**
|
|
* Tree layout. Depending on the method parameter, performs either
|
|
* Reingold-Tilford 'tidy' layout or dendrogram 'cluster' layout.
|
|
* @constructor
|
|
* @param {object} params - The parameters for this operator.
|
|
*/
|
|
function Tree(params) {
|
|
HierarchyLayout.call(this, params);
|
|
}
|
|
|
|
Tree.Definition = {
|
|
"type": "Tree",
|
|
"metadata": {"tree": true, "modifies": true},
|
|
"params": [
|
|
{ "name": "field", "type": "field" },
|
|
{ "name": "sort", "type": "compare" },
|
|
{ "name": "method", "type": "enum", "default": "tidy", "values": ["tidy", "cluster"] },
|
|
{ "name": "size", "type": "number", "array": true, "length": 2 },
|
|
{ "name": "nodeSize", "type": "number", "array": true, "length": 2 },
|
|
{ "name": "separation", "type": "boolean", "default": true },
|
|
{ "name": "as", "type": "string", "array": true, "length": Output$2.length, "default": Output$2 }
|
|
]
|
|
};
|
|
|
|
var prototype$5 = vegaUtil.inherits(Tree, HierarchyLayout);
|
|
|
|
/**
|
|
* Tree layout generator. Supports both 'tidy' and 'cluster' layouts.
|
|
*/
|
|
prototype$5.layout = function(method) {
|
|
var m = method || 'tidy';
|
|
if (vegaUtil.hasOwnProperty(Layouts, m)) return Layouts[m]();
|
|
else vegaUtil.error('Unrecognized Tree layout method: ' + m);
|
|
};
|
|
|
|
prototype$5.params = ['size', 'nodeSize'];
|
|
|
|
prototype$5.fields = Output$2;
|
|
|
|
/**
|
|
* Generate tuples representing links between tree nodes.
|
|
* The resulting tuples will contain 'source' and 'target' fields,
|
|
* which point to parent and child node tuples, respectively.
|
|
* @constructor
|
|
* @param {object} params - The parameters for this operator.
|
|
*/
|
|
function TreeLinks(params) {
|
|
vegaDataflow.Transform.call(this, [], params);
|
|
}
|
|
|
|
TreeLinks.Definition = {
|
|
"type": "TreeLinks",
|
|
"metadata": {"tree": true, "generates": true, "changes": true},
|
|
"params": []
|
|
};
|
|
|
|
var prototype$6 = vegaUtil.inherits(TreeLinks, vegaDataflow.Transform);
|
|
|
|
prototype$6.transform = function(_, pulse) {
|
|
var links = this.value,
|
|
tree = pulse.source && pulse.source.root,
|
|
out = pulse.fork(pulse.NO_SOURCE),
|
|
lut = {};
|
|
|
|
if (!tree) vegaUtil.error('TreeLinks transform requires a tree data source.');
|
|
|
|
if (pulse.changed(pulse.ADD_REM)) {
|
|
// remove previous links
|
|
out.rem = links;
|
|
|
|
// build lookup table of valid tuples
|
|
pulse.visit(pulse.SOURCE, function(t) { lut[vegaDataflow.tupleid(t)] = 1; });
|
|
|
|
// generate links for all edges incident on valid tuples
|
|
tree.each(function(node) {
|
|
var t = node.data,
|
|
p = node.parent && node.parent.data;
|
|
if (p && lut[vegaDataflow.tupleid(t)] && lut[vegaDataflow.tupleid(p)]) {
|
|
out.add.push(vegaDataflow.ingest({source: p, target: t}));
|
|
}
|
|
});
|
|
this.value = out.add;
|
|
}
|
|
|
|
else if (pulse.changed(pulse.MOD)) {
|
|
// build lookup table of modified tuples
|
|
pulse.visit(pulse.MOD, function(t) { lut[vegaDataflow.tupleid(t)] = 1; });
|
|
|
|
// gather links incident on modified tuples
|
|
links.forEach(function(link) {
|
|
if (lut[vegaDataflow.tupleid(link.source)] || lut[vegaDataflow.tupleid(link.target)]) {
|
|
out.mod.push(link);
|
|
}
|
|
});
|
|
}
|
|
|
|
return out;
|
|
};
|
|
|
|
var Tiles = {
|
|
binary: d3Hierarchy.treemapBinary,
|
|
dice: d3Hierarchy.treemapDice,
|
|
slice: d3Hierarchy.treemapSlice,
|
|
slicedice: d3Hierarchy.treemapSliceDice,
|
|
squarify: d3Hierarchy.treemapSquarify,
|
|
resquarify: d3Hierarchy.treemapResquarify
|
|
};
|
|
|
|
var Output$3 = ['x0', 'y0', 'x1', 'y1', 'depth', 'children'];
|
|
|
|
/**
|
|
* Treemap layout.
|
|
* @constructor
|
|
* @param {object} params - The parameters for this operator.
|
|
* @param {function(object): *} params.field - The value field to size nodes.
|
|
*/
|
|
function Treemap(params) {
|
|
HierarchyLayout.call(this, params);
|
|
}
|
|
|
|
Treemap.Definition = {
|
|
"type": "Treemap",
|
|
"metadata": {"tree": true, "modifies": true},
|
|
"params": [
|
|
{ "name": "field", "type": "field" },
|
|
{ "name": "sort", "type": "compare" },
|
|
{ "name": "method", "type": "enum", "default": "squarify",
|
|
"values": ["squarify", "resquarify", "binary", "dice", "slice", "slicedice"] },
|
|
{ "name": "padding", "type": "number", "default": 0 },
|
|
{ "name": "paddingInner", "type": "number", "default": 0 },
|
|
{ "name": "paddingOuter", "type": "number", "default": 0 },
|
|
{ "name": "paddingTop", "type": "number", "default": 0 },
|
|
{ "name": "paddingRight", "type": "number", "default": 0 },
|
|
{ "name": "paddingBottom", "type": "number", "default": 0 },
|
|
{ "name": "paddingLeft", "type": "number", "default": 0 },
|
|
{ "name": "ratio", "type": "number", "default": 1.618033988749895 },
|
|
{ "name": "round", "type": "boolean", "default": false },
|
|
{ "name": "size", "type": "number", "array": true, "length": 2 },
|
|
{ "name": "as", "type": "string", "array": true, "length": Output$3.length, "default": Output$3 }
|
|
]
|
|
};
|
|
|
|
var prototype$7 = vegaUtil.inherits(Treemap, HierarchyLayout);
|
|
|
|
/**
|
|
* Treemap layout generator. Adds 'method' and 'ratio' parameters
|
|
* to configure the underlying tile method.
|
|
*/
|
|
prototype$7.layout = function() {
|
|
var x = d3Hierarchy.treemap();
|
|
x.ratio = function(_) {
|
|
var t = x.tile();
|
|
if (t.ratio) x.tile(t.ratio(_));
|
|
};
|
|
x.method = function(_) {
|
|
if (vegaUtil.hasOwnProperty(Tiles, _)) x.tile(Tiles[_]);
|
|
else vegaUtil.error('Unrecognized Treemap layout method: ' + _);
|
|
};
|
|
return x;
|
|
};
|
|
|
|
prototype$7.params = [
|
|
'method', 'ratio', 'size', 'round',
|
|
'padding', 'paddingInner', 'paddingOuter',
|
|
'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft'
|
|
];
|
|
|
|
prototype$7.fields = Output$3;
|
|
|
|
exports.nest = Nest;
|
|
exports.pack = Pack;
|
|
exports.partition = Partition;
|
|
exports.stratify = Stratify;
|
|
exports.tree = Tree;
|
|
exports.treelinks = TreeLinks;
|
|
exports.treemap = Treemap;
|
|
|
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
|
}));
|
|
|