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.
365 lines
9.7 KiB
365 lines
9.7 KiB
4 years ago
|
'use strict';
|
||
|
|
||
|
var utils = require('./utils');
|
||
|
|
||
|
/**
|
||
|
* Create a new instance of the Compose for the supplied provider and receiver instances,
|
||
|
* with the list of given generators.
|
||
|
*
|
||
|
* ```js
|
||
|
* var compose = new Compose(provider, receiver, ['a', 'b', 'c']);
|
||
|
* ```
|
||
|
* @param {Object} `provider` The generator instance with generators to extend onto the receiver instance.
|
||
|
* @param {Object} `receiver` The current generator instance to extend.
|
||
|
* @param {Array} `generators` One or more generators from the `provider` generator to iterate over.
|
||
|
*/
|
||
|
|
||
|
function Compose(provider, receiver, generators) {
|
||
|
this.generators = utils.arrayify(generators);
|
||
|
this.provider = provider;
|
||
|
this.receiver = receiver;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Merge the options from each generator into the `app` options.
|
||
|
* This method requires using the [base-option][base-option] plugin.
|
||
|
*
|
||
|
* ```js
|
||
|
* a.option({foo: 'a'});
|
||
|
* b.option({foo: 'b'});
|
||
|
* c.option({foo: 'c'});
|
||
|
*
|
||
|
* app.compose(base, ['a', 'b', 'c'])
|
||
|
* .options();
|
||
|
*
|
||
|
* console.log(app.options);
|
||
|
* //=> {foo: 'c'}
|
||
|
* ```
|
||
|
* @name .compose.options
|
||
|
* @param {String} `key` Optionally pass the name of a property to merge from the `options` object. Dot-notation may be used for nested properties.
|
||
|
* @return {Object} Returns the `Compose` instance for chaining
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
Compose.prototype.options = function(key) {
|
||
|
this.iterator(function(generator, app) {
|
||
|
if (key && typeof key === 'string') {
|
||
|
app.option(key, generator.option(key));
|
||
|
} else {
|
||
|
app.option(generator.options);
|
||
|
}
|
||
|
});
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Merge the `cache.data` object from each generator onto the `app.cache.data` object.
|
||
|
* This method requires the `.data()` method from [templates][].
|
||
|
*
|
||
|
* ```js
|
||
|
* a.data({foo: 'a'});
|
||
|
* b.data({foo: 'b'});
|
||
|
* c.data({foo: 'c'});
|
||
|
*
|
||
|
* app.compose(base, ['a', 'b', 'c'])
|
||
|
* .data();
|
||
|
*
|
||
|
* console.log(app.cache.data);
|
||
|
* //=> {foo: 'c'}
|
||
|
* ```
|
||
|
* @name .compose.data
|
||
|
* @param {String} `key` Optionally pass a key to merge from the `data` object.
|
||
|
* @return {Object} Returns the `Compose` instance for chaining
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
Compose.prototype.data = function(prop) {
|
||
|
if (typeof this.receiver.data !== 'function') {
|
||
|
throw new Error('expected the base-data plugin to be registered');
|
||
|
}
|
||
|
|
||
|
this.iterator(function(generator, app) {
|
||
|
if (prop && typeof prop === 'string') {
|
||
|
app.data(prop, generator.data(prop));
|
||
|
} else {
|
||
|
app.data(generator.cache.data);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Merge the engines from each generator into the `app` engines.
|
||
|
* This method requires the `.engine()` methods from [templates][].
|
||
|
*
|
||
|
* ```js
|
||
|
* app.compose(base, ['a', 'b', 'c'])
|
||
|
* .engines();
|
||
|
* ```
|
||
|
* @name .compose.engines
|
||
|
* @return {Object} Returns the `Compose` instance for chaining
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
Compose.prototype.engines = function() {
|
||
|
if (typeof this.receiver.engine !== 'function') {
|
||
|
throw new Error('.engines requires an instance of templates');
|
||
|
}
|
||
|
|
||
|
this.iterator(function(generator, app) {
|
||
|
utils.merge(app.engines, generator.engines);
|
||
|
});
|
||
|
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Merge the helpers from each generator into `app.helpers`. Requires the
|
||
|
* `.helper` method from [templates][].
|
||
|
*
|
||
|
* ```js
|
||
|
* app.compose(base, ['a', 'b', 'c'])
|
||
|
* .helpers();
|
||
|
* ```
|
||
|
* @name .compose.helpers
|
||
|
* @return {Object} Returns the `Compose` instance for chaining
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
Compose.prototype.helpers = function() {
|
||
|
if (typeof this.receiver.helper !== 'function') {
|
||
|
throw new Error('.helpers requires an instance of templates');
|
||
|
}
|
||
|
|
||
|
this.iterator(function(generator, app) {
|
||
|
app.asyncHelpers(generator._.helpers.async);
|
||
|
app.helpers(generator._.helpers.sync);
|
||
|
});
|
||
|
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Merge `generator.questions.cache` from specified generators onto `app.questions.cache`.
|
||
|
* Requires the [base-questions][] plugin to be registered.
|
||
|
*
|
||
|
* ```js
|
||
|
* app.compose(base, ['a', 'b', 'c'])
|
||
|
* .questions();
|
||
|
* ```
|
||
|
* @name .compose.questions
|
||
|
* @return {Object} Returns the `Compose` instance for chaining
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
Compose.prototype.questions = function() {
|
||
|
if (typeof this.receiver.questions === 'undefined') {
|
||
|
throw new Error('expected the base-questions plugin to be registered');
|
||
|
}
|
||
|
|
||
|
this.iterator(function(generator, app) {
|
||
|
for (var key in generator.questions.cache) {
|
||
|
if (generator.questions.cache.hasOwnProperty(key)) {
|
||
|
app.question(key, generator.questions.cache[key]);
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Merge the pipeline plugins from each generator onto `app.plugins`.
|
||
|
* Requires the [base-pipeline][] plugin to be registered.
|
||
|
*
|
||
|
* ```js
|
||
|
* app.compose(base, ['a', 'b', 'c'])
|
||
|
* .pipeline();
|
||
|
* ```
|
||
|
* @name .compose.pipeline
|
||
|
* @return {Object} Returns the `Compose` instance for chaining
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
Compose.prototype.pipeline = function() {
|
||
|
if (typeof this.receiver.pipeline !== 'function') {
|
||
|
throw new Error('expected the base-pipeline plugin to be registered');
|
||
|
}
|
||
|
|
||
|
this.iterator(function(generator, app) {
|
||
|
for (var key in generator.plugins) {
|
||
|
app.plugin(key, generator.plugins[key]);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Copy the specified tasks and task-dependencies from each generator
|
||
|
* onto `app.tasks`. Requires using the [base-task][] plugin to be registered.
|
||
|
*
|
||
|
* ```js
|
||
|
* app.compose(base, ['a', 'b', 'c'])
|
||
|
* .tasks(['foo', 'bar', 'default']);
|
||
|
*
|
||
|
* // or to copy all tasks
|
||
|
* app.compose(base, ['a', 'b', 'c'])
|
||
|
* .tasks();
|
||
|
* ```
|
||
|
* @name .compose.tasks
|
||
|
* @param {String|Array} `tasks` One or more task names (optional)
|
||
|
* @return {Object} Returns the `Compose` instance for chaining
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
Compose.prototype.tasks = function(tasks) {
|
||
|
if (typeof this.receiver.task !== 'function') {
|
||
|
throw new Error('expected the base-task plugin to be registered');
|
||
|
}
|
||
|
|
||
|
this.iterator(function(generator, app) {
|
||
|
utils.arrayify(tasks || Object.keys(generator.tasks)).forEach(function(task) {
|
||
|
utils.copyTask(generator, app, task);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Copy view collections and views from each generator onto `app`.
|
||
|
* Expects `app` to be an instance of [templates][].
|
||
|
*
|
||
|
* ```js
|
||
|
* app.compose(base, ['a', 'b', 'c'])
|
||
|
* .views();
|
||
|
* ```
|
||
|
* @name .compose.views
|
||
|
* @param {Array|String} `names` (optional) Names of one or more collections to copy. If undefined all collections will be copied.
|
||
|
* @param {Function} `filter` Optionally pass a filter function to filter views copied from each collection. The filter function exposes `key`, `view` and `collection` as arguments. If used, the function must return `true` to copy a view.
|
||
|
* @return {Object} Returns the `Compose` instance for chaining
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
Compose.prototype.views = function(names, filter) {
|
||
|
if (typeof this.receiver.create !== 'function') {
|
||
|
throw new Error('.views requires an instance of templates');
|
||
|
}
|
||
|
|
||
|
if (typeof names === 'function') {
|
||
|
filter = names;
|
||
|
names = null;
|
||
|
}
|
||
|
|
||
|
names = utils.arrayify(names);
|
||
|
|
||
|
this.iterator(function(generator, app) {
|
||
|
var len = names.length;
|
||
|
var idx = -1;
|
||
|
|
||
|
if (len === 0) {
|
||
|
names = Object.keys(generator.views);
|
||
|
len = names.length;
|
||
|
}
|
||
|
|
||
|
while (++idx < len) {
|
||
|
var name = names[idx];
|
||
|
var collection = generator[name];
|
||
|
|
||
|
if (typeof app[name] !== 'function' || typeof app[name].views === 'undefined') {
|
||
|
app.create(collection.options.inflection, collection.options);
|
||
|
}
|
||
|
|
||
|
var views = collection.views;
|
||
|
|
||
|
if (typeof filter === 'function') {
|
||
|
for (var key in views) {
|
||
|
if (views.hasOwnProperty(key)) {
|
||
|
var view = views[key];
|
||
|
|
||
|
if (filter(key, view, views)) {
|
||
|
app[name].addView(key, view);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
app[name].addViews(views);
|
||
|
}
|
||
|
};
|
||
|
});
|
||
|
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Returns an iterator function for iterating over an array of generators.
|
||
|
* The iterator takes a `fn` that exposes the current generator being iterated
|
||
|
* over (`generator`) and the app passed into the original function as arguments.
|
||
|
* No binding is done within the iterator so the function passed in can be
|
||
|
* safely bound.
|
||
|
*
|
||
|
* ```js
|
||
|
* app.compose(base, ['a', 'b', 'c'])
|
||
|
* .iterator(function(generator, app) {
|
||
|
* // do work
|
||
|
* app.data(generator.cache.data);
|
||
|
* });
|
||
|
*
|
||
|
* // optionally pass an array of additional generator names as the
|
||
|
* // first argument. If generator names are defined on `iterator`,
|
||
|
* // any names passed to `.compose()` will be ignored.
|
||
|
* app.compose(base, ['a', 'b', 'c'])
|
||
|
* .iterator(['d', 'e', 'f'], function(generator, app) {
|
||
|
* // do stuff to `generator` and `app`
|
||
|
* });
|
||
|
* ```
|
||
|
* @name .compose.iterator
|
||
|
* @param {Array} `names` Names of generators to iterate over (optional).
|
||
|
* @param {Function} `iteratorFn` Function to invoke for each generator in `generators`. Exposes `app` and `generator` as arguments.
|
||
|
* @return {Object} Returns the `Compose` instance for chaining
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
Compose.prototype.iterator = function(names, iteratorFn) {
|
||
|
if (typeof names === 'function') {
|
||
|
iteratorFn = names;
|
||
|
names = null;
|
||
|
}
|
||
|
|
||
|
names = utils.arrayify(names || this.generators);
|
||
|
var len = names.length;
|
||
|
var idx = -1;
|
||
|
|
||
|
while (++idx < len) {
|
||
|
var name = names[idx];
|
||
|
var generator = name;
|
||
|
try {
|
||
|
if (typeof name === 'string') {
|
||
|
generator = this.provider.getGenerator(name);
|
||
|
if (typeof generator === 'undefined') {
|
||
|
if (name === 'default') {
|
||
|
continue;
|
||
|
}
|
||
|
throw new Error('generator "' + name + '" is not registered');
|
||
|
}
|
||
|
}
|
||
|
iteratorFn(generator, this.receiver);
|
||
|
} catch (err) {
|
||
|
if (this.receiver.hasListeners('error')) {
|
||
|
this.receiver.emit('error', err);
|
||
|
} else {
|
||
|
throw err;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return this;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Expose `Compose`
|
||
|
*/
|
||
|
|
||
|
module.exports = Compose;
|