@kitware/vtk.js
Version:
Visualization Toolkit for the Web
1,491 lines (1,409 loc) • 57 kB
JavaScript
import DeepEqual from 'fast-deep-equal';
import vtk, { vtkGlobal } from './vtk.js';
import ClassHierarchy from './Common/Core/ClassHierarchy.js';
/**
* macros.js is the old macro.js.
* The name change is so we do not get eaten by babel-plugin-macros.
*/
let globalMTime = 0;
const requiredParam = name => {
throw new Error(`Named parameter '${name}' is missing`);
};
const VOID = Symbol('void');
function getCurrentGlobalMTime() {
return globalMTime;
}
// ----------------------------------------------------------------------------
// Logging function calls
// ----------------------------------------------------------------------------
/* eslint-disable no-prototype-builtins */
const fakeConsole = {};
function noOp() {}
const consoleMethods = ['log', 'debug', 'info', 'warn', 'error', 'time', 'timeEnd', 'group', 'groupEnd'];
consoleMethods.forEach(methodName => {
fakeConsole[methodName] = noOp;
});
vtkGlobal.console = console.hasOwnProperty('log') ? console : fakeConsole;
const loggerFunctions = {
debug: noOp,
// Don't print debug by default
error: vtkGlobal.console.error || noOp,
info: vtkGlobal.console.info || noOp,
log: vtkGlobal.console.log || noOp,
warn: vtkGlobal.console.warn || noOp
};
function setLoggerFunction(name, fn) {
if (loggerFunctions[name]) {
loggerFunctions[name] = fn || noOp;
}
}
function vtkLogMacro() {
loggerFunctions.log(...arguments);
}
function vtkInfoMacro() {
loggerFunctions.info(...arguments);
}
function vtkDebugMacro() {
loggerFunctions.debug(...arguments);
}
function vtkErrorMacro() {
loggerFunctions.error(...arguments);
}
function vtkWarningMacro() {
loggerFunctions.warn(...arguments);
}
const ERROR_ONCE_MAP = {};
function vtkOnceErrorMacro(str) {
if (!ERROR_ONCE_MAP[str]) {
loggerFunctions.error(str);
ERROR_ONCE_MAP[str] = true;
}
}
// ----------------------------------------------------------------------------
// TypedArray
// ----------------------------------------------------------------------------
const TYPED_ARRAYS = Object.create(null);
TYPED_ARRAYS.Float32Array = Float32Array;
TYPED_ARRAYS.Float64Array = Float64Array;
TYPED_ARRAYS.Uint8Array = Uint8Array;
TYPED_ARRAYS.Int8Array = Int8Array;
TYPED_ARRAYS.Uint16Array = Uint16Array;
TYPED_ARRAYS.Int16Array = Int16Array;
TYPED_ARRAYS.Uint32Array = Uint32Array;
TYPED_ARRAYS.Int32Array = Int32Array;
TYPED_ARRAYS.Uint8ClampedArray = Uint8ClampedArray;
try {
TYPED_ARRAYS.BigInt64Array = BigInt64Array;
TYPED_ARRAYS.BigUint64Array = BigUint64Array;
} catch {
// ignore
}
function newTypedArray(type) {
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
return new (TYPED_ARRAYS[type] || Float64Array)(...args);
}
function newTypedArrayFrom(type) {
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
args[_key2 - 1] = arguments[_key2];
}
return (TYPED_ARRAYS[type] || Float64Array).from(...args);
}
// ----------------------------------------------------------------------------
// capitilize provided string
// ----------------------------------------------------------------------------
function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
function _capitalize(str) {
return capitalize(str[0] === '_' ? str.slice(1) : str);
}
function uncapitalize(str) {
return str.charAt(0).toLowerCase() + str.slice(1);
}
// ----------------------------------------------------------------------------
// Convert byte size into a well formatted string
// ----------------------------------------------------------------------------
function formatBytesToProperUnit(size) {
let precision = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 2;
let chunkSize = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1000;
const units = ['TB', 'GB', 'MB', 'KB'];
let value = Number(size);
let currentUnit = 'B';
while (value > chunkSize) {
value /= chunkSize;
currentUnit = units.pop();
}
return `${value.toFixed(precision)} ${currentUnit}`;
}
// ----------------------------------------------------------------------------
// Convert thousand number with proper separator
// ----------------------------------------------------------------------------
function formatNumbersWithThousandSeparator(n) {
let separator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ' ';
const sections = [];
let size = n;
while (size > 1000) {
sections.push(`000${size % 1000}`.slice(-3));
size = Math.floor(size / 1000);
}
if (size > 0) {
sections.push(size);
}
sections.reverse();
return sections.join(separator);
}
// ----------------------------------------------------------------------------
// Array helper
// ----------------------------------------------------------------------------
function safeArrays(model) {
Object.keys(model).forEach(key => {
if (Array.isArray(model[key])) {
model[key] = [].concat(model[key]);
}
});
}
function isTypedArray(value) {
return Object.values(TYPED_ARRAYS).some(ctor => value instanceof ctor);
}
// ----------------------------------------------------------------------------
// shallow equals
// ----------------------------------------------------------------------------
function shallowEquals(a, b) {
if (a === b) {
return true;
}
if (Array.isArray(a) && Array.isArray(b)) {
if (a.length !== b.length) {
return false;
}
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
return false;
}
// ----------------------------------------------------------------------------
function enumToString(e, value) {
return Object.keys(e).find(key => e[key] === value);
}
function getStateArrayMapFunc(item) {
if (item && item.isA) {
return item.getState();
}
return item;
}
// ----------------------------------------------------------------------------
// setImmediate
// ----------------------------------------------------------------------------
function setImmediateVTK(fn) {
setTimeout(fn, 0);
}
// ----------------------------------------------------------------------------
// measurePromiseExecution
//
// Measures the time it takes for a promise to finish from
// the time this function is invoked.
// The callback receives the time it took for the promise to resolve or reject.
// ----------------------------------------------------------------------------
function measurePromiseExecution(promise, callback) {
const start = performance.now();
promise.finally(() => {
const delta = performance.now() - start;
callback(delta);
});
}
// ----------------------------------------------------------------------------
// vtkObject: modified(), onModified(callback), delete()
// ----------------------------------------------------------------------------
function obj() {
let publicAPI = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let model = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
// Ensure each instance as a unique ref of array
safeArrays(model);
const callbacks = [];
if (!Number.isInteger(model.mtime)) {
model.mtime = ++globalMTime;
}
if (!('classHierarchy' in model)) {
model.classHierarchy = new ClassHierarchy('vtkObject');
} else if (!(model.classHierarchy instanceof ClassHierarchy)) {
const hierarchy = new ClassHierarchy();
for (let i = 0; i < model.classHierarchy.length; i++) {
hierarchy.push(model.classHierarchy[i]);
}
model.classHierarchy = hierarchy;
}
function off(index) {
callbacks[index] = null;
}
function on(index) {
function unsubscribe() {
off(index);
}
return Object.freeze({
unsubscribe
});
}
publicAPI.isDeleted = () => !!model.deleted;
publicAPI.modified = otherMTime => {
if (model.deleted) {
vtkErrorMacro('instance deleted - cannot call any method');
return;
}
if (otherMTime && otherMTime < publicAPI.getMTime()) {
return;
}
model.mtime = ++globalMTime;
callbacks.forEach(callback => callback && callback(publicAPI));
};
publicAPI.onModified = callback => {
if (model.deleted) {
vtkErrorMacro('instance deleted - cannot call any method');
return null;
}
const index = callbacks.length;
callbacks.push(callback);
return on(index);
};
publicAPI.getMTime = () => model.mtime;
publicAPI.isA = className => {
let count = model.classHierarchy.length;
// we go backwards as that is more likely for
// early termination
while (count--) {
if (model.classHierarchy[count] === className) {
return true;
}
}
return false;
};
publicAPI.getClassName = function () {
let depth = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
return model.classHierarchy[model.classHierarchy.length - 1 - depth];
};
publicAPI.set = function () {
let map = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let noWarning = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
let noFunction = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
let ret = false;
Object.keys(map).forEach(name => {
const fn = noFunction ? null : publicAPI[`set${capitalize(name)}`];
if (fn && Array.isArray(map[name]) && fn.length > 1) {
ret = fn(...map[name]) || ret;
} else if (fn) {
ret = fn(map[name]) || ret;
} else {
// Set data on model directly
if (['mtime'].indexOf(name) === -1 && !noWarning) {
vtkWarningMacro(`Warning: Set value to model directly ${name}, ${map[name]}`);
}
ret = model[name] !== map[name] || ret;
model[name] = map[name];
}
});
return ret;
};
publicAPI.get = function () {
for (var _len3 = arguments.length, list = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
list[_key3] = arguments[_key3];
}
if (!list.length) {
return model;
}
const subset = {};
list.forEach(name => {
subset[name] = model[name];
});
return subset;
};
publicAPI.getReferenceByName = val => model[val];
publicAPI.delete = () => {
Object.keys(model).forEach(field => delete model[field]);
callbacks.forEach((el, index) => off(index));
// Flag the instance being deleted
model.deleted = true;
};
// Add serialization support
publicAPI.getState = () => {
if (model.deleted) {
return null;
}
const jsonArchive = {
...model,
vtkClass: publicAPI.getClassName()
};
// Convert every vtkObject to its serializable form
Object.keys(jsonArchive).forEach(keyName => {
if (jsonArchive[keyName] === null || jsonArchive[keyName] === undefined || keyName[0] === '_' // protected members start with _
) {
delete jsonArchive[keyName];
} else if (jsonArchive[keyName].isA) {
jsonArchive[keyName] = jsonArchive[keyName].getState();
} else if (Array.isArray(jsonArchive[keyName])) {
jsonArchive[keyName] = jsonArchive[keyName].map(getStateArrayMapFunc);
} else if (isTypedArray(jsonArchive[keyName])) {
jsonArchive[keyName] = Array.from(jsonArchive[keyName]);
}
});
// Sort resulting object by key name
const sortedObj = {};
Object.keys(jsonArchive).sort().forEach(name => {
sortedObj[name] = jsonArchive[name];
});
// Remove mtime
if (sortedObj.mtime) {
delete sortedObj.mtime;
}
return sortedObj;
};
// Add shallowCopy(otherInstance) support
publicAPI.shallowCopy = function (other) {
let debug = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
if (other.getClassName() !== publicAPI.getClassName()) {
throw new Error(`Cannot ShallowCopy ${other.getClassName()} into ${publicAPI.getClassName()}`);
}
const otherModel = other.get();
const keyList = Object.keys(model).sort();
const otherKeyList = Object.keys(otherModel).sort();
otherKeyList.forEach(key => {
const keyIdx = keyList.indexOf(key);
if (keyIdx === -1) {
if (debug) {
vtkDebugMacro(`add ${key} in shallowCopy`);
}
} else {
keyList.splice(keyIdx, 1);
}
model[key] = otherModel[key];
});
if (keyList.length && debug) {
vtkDebugMacro(`Untouched keys: ${keyList.join(', ')}`);
}
publicAPI.modified();
};
// This function will get called when one invoke JSON.stringify(vtkObject)
// JSON.stringify will only stringify the return value of this function
publicAPI.toJSON = function vtkObjToJSON() {
return publicAPI.getState();
};
// Allow usage as decorator
return publicAPI;
}
// ----------------------------------------------------------------------------
// getXXX: add getters
// ----------------------------------------------------------------------------
const objectGetterMap = {
object(publicAPI, model, field) {
return function getter() {
return {
...model[field.name]
};
};
}
};
function get(publicAPI, model, fieldNames) {
fieldNames.forEach(field => {
if (typeof field === 'object') {
const getter = objectGetterMap[field.type];
if (getter) {
publicAPI[`get${_capitalize(field.name)}`] = getter(publicAPI, model, field);
} else {
publicAPI[`get${_capitalize(field.name)}`] = () => model[field.name];
}
} else {
publicAPI[`get${_capitalize(field)}`] = () => model[field];
}
});
}
// ----------------------------------------------------------------------------
// setXXX: add setters
// ----------------------------------------------------------------------------
const objectSetterMap = {
enum(publicAPI, model, field) {
const onChanged = `_on${_capitalize(field.name)}Changed`;
return value => {
if (typeof value === 'string') {
if (field.enum[value] !== undefined) {
if (model[field.name] !== field.enum[value]) {
model[field.name] = field.enum[value];
publicAPI.modified();
return true;
}
return false;
}
vtkErrorMacro(`Set Enum with invalid argument ${field}, ${value}`);
throw new RangeError('Set Enum with invalid string argument');
}
if (typeof value === 'number') {
if (model[field.name] !== value) {
if (Object.keys(field.enum).map(key => field.enum[key]).indexOf(value) !== -1) {
const previousValue = model[field.name];
model[field.name] = value;
model[onChanged]?.(publicAPI, model, value, previousValue);
publicAPI.modified();
return true;
}
vtkErrorMacro(`Set Enum outside numeric range ${field}, ${value}`);
throw new RangeError('Set Enum outside numeric range');
}
return false;
}
vtkErrorMacro(`Set Enum with invalid argument (String/Number) ${field}, ${value}`);
throw new TypeError('Set Enum with invalid argument (String/Number)');
};
},
object(publicAPI, model, field) {
if (field.params?.length === 1) {
vtkWarningMacro('Setter of type "object" with a single "param" field is not supported');
}
const onChanged = `_on${_capitalize(field.name)}Changed`;
return function () {
for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
args[_key4] = arguments[_key4];
}
let value;
if (args.length > 1 && field.params?.length) {
value = field.params.reduce((acc, prop, idx) => Object.assign(acc, {
[prop]: args[idx]
}), {});
} else {
value = args[0];
}
if (!DeepEqual(model[field.name], value)) {
const previousValue = model[field.name];
model[field.name] = value;
model[onChanged]?.(publicAPI, model, value, previousValue);
publicAPI.modified();
return true;
}
return false;
};
}
};
function findSetter(field) {
if (typeof field === 'object') {
const fn = objectSetterMap[field.type];
if (fn) {
return (publicAPI, model) => fn(publicAPI, model, field);
}
vtkErrorMacro(`No setter for field ${field}`);
throw new TypeError('No setter for field');
}
return function getSetter(publicAPI, model) {
const onChanged = `_on${_capitalize(field)}Changed`;
return function setter(value) {
if (model.deleted) {
vtkErrorMacro('instance deleted - cannot call any method');
return false;
}
if (model[field] !== value) {
const previousValue = model[field.name];
model[field] = value;
model[onChanged]?.(publicAPI, model, value, previousValue);
publicAPI.modified();
return true;
}
return false;
};
};
}
function set(publicAPI, model, fields) {
fields.forEach(field => {
if (typeof field === 'object') {
publicAPI[`set${_capitalize(field.name)}`] = findSetter(field)(publicAPI, model);
} else {
publicAPI[`set${_capitalize(field)}`] = findSetter(field)(publicAPI, model);
}
});
}
// ----------------------------------------------------------------------------
// set/get XXX: add both setters and getters
// ----------------------------------------------------------------------------
function setGet(publicAPI, model, fieldNames) {
get(publicAPI, model, fieldNames);
set(publicAPI, model, fieldNames);
}
// ----------------------------------------------------------------------------
// getXXX: add getters for object of type array with copy to be safe
// getXXXByReference: add getters for object of type array without copy
// ----------------------------------------------------------------------------
function getArray(publicAPI, model, fieldNames) {
fieldNames.forEach(field => {
publicAPI[`get${_capitalize(field)}`] = () => model[field] ? Array.from(model[field]) : model[field];
publicAPI[`get${_capitalize(field)}ByReference`] = () => model[field];
});
}
// ----------------------------------------------------------------------------
// setXXX: add setter for object of type array
// if 'defaultVal' is supplied, shorter arrays will be padded to 'size' with 'defaultVal'
// set...From: fast path to copy the content of an array to the current one without call to modified.
// ----------------------------------------------------------------------------
function setArray(publicAPI, model, fieldNames, size) {
let defaultVal = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : undefined;
fieldNames.forEach(field => {
if (model[field] && size && model[field].length !== size) {
throw new RangeError(`Invalid initial number of values for array (${field})`);
}
const onChanged = `_on${_capitalize(field)}Changed`;
publicAPI[`set${_capitalize(field)}`] = function () {
if (model.deleted) {
vtkErrorMacro('instance deleted - cannot call any method');
return false;
}
for (var _len5 = arguments.length, args = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
args[_key5] = arguments[_key5];
}
let array = args;
let changeDetected;
let needCopy = false;
// allow null or an array to be passed as a single arg.
if (array.length === 1 && (array[0] == null || array[0].length >= 0)) {
/* eslint-disable prefer-destructuring */
array = array[0];
/* eslint-enable prefer-destructuring */
needCopy = true;
}
if (array == null) {
changeDetected = model[field] !== array;
} else {
if (size && array.length !== size) {
if (array.length < size && defaultVal !== undefined) {
array = Array.from(array);
needCopy = false;
while (array.length < size) array.push(defaultVal);
} else {
throw new RangeError(`Invalid number of values for array setter (${field})`);
}
}
changeDetected = model[field] == null || model[field].length !== array.length;
for (let i = 0; !changeDetected && i < array.length; ++i) {
changeDetected = model[field][i] !== array[i];
}
if (changeDetected && needCopy) {
array = Array.from(array);
}
}
if (changeDetected) {
const previousValue = model[field.name];
model[field] = array;
model[onChanged]?.(publicAPI, model, array, previousValue);
publicAPI.modified();
}
return changeDetected;
};
publicAPI[`set${_capitalize(field)}From`] = otherArray => {
const target = model[field];
otherArray.forEach((v, i) => {
target[i] = v;
});
};
});
}
// ----------------------------------------------------------------------------
// set/get XXX: add setter and getter for object of type array
// ----------------------------------------------------------------------------
function setGetArray(publicAPI, model, fieldNames, size) {
let defaultVal = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : undefined;
getArray(publicAPI, model, fieldNames);
setArray(publicAPI, model, fieldNames, size, defaultVal);
}
function moveToProtected(publicAPI, model, fieldNames) {
for (let i = 0; i < fieldNames.length; i++) {
const fieldName = fieldNames[i];
if (model[fieldName] !== undefined) {
model[`_${fieldName}`] = model[fieldName];
delete model[fieldName];
}
}
}
// ----------------------------------------------------------------------------
// vtkAlgorithm: setInputData(), setInputConnection(), getOutputData(), getOutputPort()
// ----------------------------------------------------------------------------
function algo(publicAPI, model, numberOfInputs, numberOfOutputs) {
if (model.inputData) {
model.inputData = model.inputData.map(vtk);
} else {
model.inputData = [];
}
if (model.inputConnection) {
model.inputConnection = model.inputConnection.map(vtk);
} else {
model.inputConnection = [];
}
if (model.output) {
model.output = model.output.map(vtk);
} else {
model.output = [];
}
if (model.inputArrayToProcess) {
model.inputArrayToProcess = model.inputArrayToProcess.map(vtk);
} else {
model.inputArrayToProcess = [];
}
// Cache the argument for later manipulation
model.numberOfInputs = numberOfInputs;
// Methods
function setInputData(dataset) {
let port = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
if (model.deleted) {
vtkErrorMacro('instance deleted - cannot call any method');
return;
}
if (port >= model.numberOfInputs) {
vtkErrorMacro(`algorithm ${publicAPI.getClassName()} only has ${model.numberOfInputs} input ports. To add more input ports, use addInputData()`);
return;
}
if (model.inputData[port] !== dataset || model.inputConnection[port]) {
model.inputData[port] = dataset;
model.inputConnection[port] = null;
if (publicAPI.modified) {
publicAPI.modified();
}
}
}
function getInputData() {
let port = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
if (model.inputConnection[port]) {
model.inputData[port] = model.inputConnection[port]();
}
return model.inputData[port];
}
function setInputConnection(outputPort) {
let port = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
if (model.deleted) {
vtkErrorMacro('instance deleted - cannot call any method');
return;
}
if (port >= model.numberOfInputs) {
let msg = `algorithm ${publicAPI.getClassName()} only has `;
msg += `${model.numberOfInputs}`;
msg += ' input ports. To add more input ports, use addInputConnection()';
vtkErrorMacro(msg);
return;
}
model.inputData[port] = null;
model.inputConnection[port] = outputPort;
}
function getInputConnection() {
let port = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
return model.inputConnection[port];
}
function getPortToFill() {
let portToFill = model.numberOfInputs;
while (portToFill && !model.inputData[portToFill - 1] && !model.inputConnection[portToFill - 1]) {
portToFill--;
}
if (portToFill === model.numberOfInputs) {
model.numberOfInputs++;
}
return portToFill;
}
function addInputConnection(outputPort) {
if (model.deleted) {
vtkErrorMacro('instance deleted - cannot call any method');
return;
}
setInputConnection(outputPort, getPortToFill());
}
function addInputData(dataset) {
if (model.deleted) {
vtkErrorMacro('instance deleted - cannot call any method');
return;
}
setInputData(dataset, getPortToFill());
}
function getOutputData() {
let port = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
if (model.deleted) {
vtkErrorMacro('instance deleted - cannot call any method');
return null;
}
if (publicAPI.shouldUpdate()) {
publicAPI.update();
}
return model.output[port];
}
publicAPI.shouldUpdate = () => {
const localMTime = publicAPI.getMTime();
let minOutputMTime = Infinity;
let count = numberOfOutputs;
while (count--) {
if (!model.output[count] || model.output[count].isDeleted()) {
return true;
}
const mt = model.output[count].getMTime();
if (mt < localMTime) {
return true;
}
if (mt < minOutputMTime) {
minOutputMTime = mt;
}
}
count = model.numberOfInputs;
while (count--) {
if (model.inputConnection[count]?.filter.shouldUpdate() || publicAPI.getInputData(count)?.getMTime() > minOutputMTime) {
return true;
}
}
return false;
};
function getOutputPort() {
let port = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
const outputPortAccess = () => getOutputData(port);
// Add reference to filter
outputPortAccess.filter = publicAPI;
return outputPortAccess;
}
// Handle input if needed
if (model.numberOfInputs) {
// Reserve inputs
let count = model.numberOfInputs;
while (count--) {
model.inputData.push(null);
model.inputConnection.push(null);
}
// Expose public methods
publicAPI.setInputData = setInputData;
publicAPI.setInputConnection = setInputConnection;
publicAPI.addInputData = addInputData;
publicAPI.addInputConnection = addInputConnection;
publicAPI.getInputData = getInputData;
publicAPI.getInputConnection = getInputConnection;
}
if (numberOfOutputs) {
publicAPI.getOutputData = getOutputData;
publicAPI.getOutputPort = getOutputPort;
}
publicAPI.update = () => {
const ins = [];
if (model.numberOfInputs) {
let count = 0;
while (count < model.numberOfInputs) {
ins[count] = publicAPI.getInputData(count);
count++;
}
}
if (publicAPI.requestData && !publicAPI.isDeleted() && publicAPI.shouldUpdate()) {
publicAPI.requestData(ins, model.output);
}
};
publicAPI.getNumberOfInputPorts = () => model.numberOfInputs;
publicAPI.getNumberOfOutputPorts = () => numberOfOutputs || model.output.length;
publicAPI.getInputArrayToProcess = inputPort => {
const arrayDesc = model.inputArrayToProcess[inputPort];
const ds = model.inputData[inputPort];
if (arrayDesc && ds) {
return ds[`get${arrayDesc.fieldAssociation}`]().getArray(arrayDesc.arrayName);
}
return null;
};
publicAPI.setInputArrayToProcess = function (inputPort, arrayName, fieldAssociation) {
let attributeType = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 'Scalars';
while (model.inputArrayToProcess.length < inputPort) {
model.inputArrayToProcess.push(null);
}
model.inputArrayToProcess[inputPort] = {
arrayName,
fieldAssociation,
attributeType
};
};
}
// ----------------------------------------------------------------------------
// Event handling: onXXX(callback), invokeXXX(args...)
// ----------------------------------------------------------------------------
const EVENT_ABORT = Symbol('Event abort');
function event(publicAPI, model, eventName) {
const callbacks = [];
const previousDelete = publicAPI.delete;
let curCallbackID = 1;
function off(callbackID) {
for (let i = 0; i < callbacks.length; ++i) {
const [cbID] = callbacks[i];
if (cbID === callbackID) {
callbacks.splice(i, 1);
return;
}
}
}
function on(callbackID) {
function unsubscribe() {
off(callbackID);
}
return Object.freeze({
unsubscribe
});
}
function invoke() {
if (model.deleted) {
vtkErrorMacro('instance deleted - cannot call any method');
return;
}
/* eslint-disable prefer-rest-params */
// Go through a copy of the callbacks array in case new callbacks
// get prepended within previous callbacks
const currentCallbacks = callbacks.slice();
for (let index = 0; index < currentCallbacks.length; ++index) {
const [, cb, priority] = currentCallbacks[index];
if (!cb) {
continue; // eslint-disable-line
}
if (priority < 0) {
setTimeout(() => cb.apply(publicAPI, arguments), 1 - priority);
} else {
// Abort only if the callback explicitly returns false
const continueNext = cb.apply(publicAPI, arguments);
if (continueNext === EVENT_ABORT) {
break;
}
}
}
/* eslint-enable prefer-rest-params */
}
publicAPI[`invoke${_capitalize(eventName)}`] = invoke;
publicAPI[`on${_capitalize(eventName)}`] = function (callback) {
let priority = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0.0;
if (!callback.apply) {
console.error(`Invalid callback for event ${eventName}`);
return null;
}
if (model.deleted) {
vtkErrorMacro('instance deleted - cannot call any method');
return null;
}
const callbackID = curCallbackID++;
callbacks.push([callbackID, callback, priority]);
callbacks.sort((cb1, cb2) => cb2[2] - cb1[2]);
return on(callbackID);
};
publicAPI.delete = () => {
previousDelete();
callbacks.forEach(_ref => {
let [cbID] = _ref;
return off(cbID);
});
};
}
// ----------------------------------------------------------------------------
// newInstance
// ----------------------------------------------------------------------------
function newInstance(extend, className) {
const constructor = function () {
let initialValues = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
const model = {};
const publicAPI = {};
extend(publicAPI, model, initialValues);
return Object.freeze(publicAPI);
};
// Register constructor to factory
if (className) {
vtk.register(className, constructor);
}
return constructor;
}
// ----------------------------------------------------------------------------
// Chain function calls
// ----------------------------------------------------------------------------
function chain() {
for (var _len6 = arguments.length, fn = new Array(_len6), _key6 = 0; _key6 < _len6; _key6++) {
fn[_key6] = arguments[_key6];
}
return function () {
for (var _len7 = arguments.length, args = new Array(_len7), _key7 = 0; _key7 < _len7; _key7++) {
args[_key7] = arguments[_key7];
}
return fn.filter(i => !!i).map(i => i(...args));
};
}
// ----------------------------------------------------------------------------
// Some utility methods for vtk objects
// ----------------------------------------------------------------------------
function isVtkObject(instance) {
return instance && instance.isA && instance.isA('vtkObject');
}
function traverseInstanceTree(instance, extractFunction) {
let accumulator = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
let visitedInstances = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [];
if (isVtkObject(instance)) {
if (visitedInstances.indexOf(instance) >= 0) {
// avoid cycles
return accumulator;
}
visitedInstances.push(instance);
const result = extractFunction(instance);
if (result !== undefined) {
accumulator.push(result);
}
// Now go through this instance's model
const model = instance.get();
Object.keys(model).forEach(key => {
const modelObj = model[key];
if (Array.isArray(modelObj)) {
modelObj.forEach(subObj => {
traverseInstanceTree(subObj, extractFunction, accumulator, visitedInstances);
});
} else {
traverseInstanceTree(modelObj, extractFunction, accumulator, visitedInstances);
}
});
}
return accumulator;
}
// ----------------------------------------------------------------------------
// Returns a function, that, as long as it continues to be invoked, will not
// be triggered. The function will be called after it stops being called for
// N milliseconds. If `immediate` is passed, trigger the function on the
// leading edge, instead of the trailing.
function debounce(func, wait, immediate) {
var _this = this;
let timeout;
const debounced = function () {
for (var _len8 = arguments.length, args = new Array(_len8), _key8 = 0; _key8 < _len8; _key8++) {
args[_key8] = arguments[_key8];
}
const context = _this;
const later = () => {
timeout = null;
if (!immediate) {
func.apply(context, args);
}
};
const callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) {
func.apply(context, args);
}
};
debounced.cancel = () => clearTimeout(timeout);
return debounced;
}
// ----------------------------------------------------------------------------
// Creates a throttled function that only invokes `func` at most once per
// every `wait` milliseconds.
function throttle(callback, delay) {
let isThrottled = false;
let argsToUse = null;
function next() {
isThrottled = false;
if (argsToUse !== null) {
wrapper(...argsToUse); // eslint-disable-line
argsToUse = null;
}
}
function wrapper() {
for (var _len9 = arguments.length, args = new Array(_len9), _key9 = 0; _key9 < _len9; _key9++) {
args[_key9] = arguments[_key9];
}
if (isThrottled) {
argsToUse = args;
return;
}
isThrottled = true;
callback(...args);
setTimeout(next, delay);
}
return wrapper;
}
// ----------------------------------------------------------------------------
// keystore(publicAPI, model, initialKeystore)
//
// - initialKeystore: Initial keystore. This can be either a Map or an
// object.
//
// Generated API
// setKey(key, value) : mixed (returns value)
// getKey(key) : mixed
// getAllKeys() : [mixed]
// deleteKey(key) : Boolean
// ----------------------------------------------------------------------------
function keystore(publicAPI, model) {
let initialKeystore = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
model.keystore = Object.assign(model.keystore || {}, initialKeystore);
publicAPI.setKey = (key, value) => {
model.keystore[key] = value;
};
publicAPI.getKey = key => model.keystore[key];
publicAPI.getAllKeys = () => Object.keys(model.keystore);
publicAPI.deleteKey = key => delete model.keystore[key];
publicAPI.clearKeystore = () => publicAPI.getAllKeys().forEach(key => delete model.keystore[key]);
}
// ----------------------------------------------------------------------------
// proxy(publicAPI, model, sectionName, propertyUI)
//
// - sectionName: Name of the section for UI
// - propertyUI: List of props with their UI description
//
// Generated API
// getProxyId() : String
// listProxyProperties() : [string]
// updateProxyProperty(name, prop)
// getProxySection() => List of properties for UI generation
// ----------------------------------------------------------------------------
let nextProxyId = 1;
const ROOT_GROUP_NAME = '__root__';
function proxy(publicAPI, model) {
// Proxies are keystores
keystore(publicAPI, model);
const parentDelete = publicAPI.delete;
// getProxyId
model.proxyId = `${nextProxyId++}`;
// ui handling
model.ui = JSON.parse(JSON.stringify(model.ui || [])); // deep copy
get(publicAPI, model, ['proxyId', 'proxyGroup', 'proxyName']);
setGet(publicAPI, model, ['proxyManager']);
// group properties
const propertyMap = {};
const groupChildrenNames = {};
function registerProperties(descriptionList, currentGroupName) {
if (!groupChildrenNames[currentGroupName]) {
groupChildrenNames[currentGroupName] = [];
}
const childrenNames = groupChildrenNames[currentGroupName];
for (let i = 0; i < descriptionList.length; i++) {
childrenNames.push(descriptionList[i].name);
propertyMap[descriptionList[i].name] = descriptionList[i];
if (descriptionList[i].children && descriptionList[i].children.length) {
registerProperties(descriptionList[i].children, descriptionList[i].name);
}
}
}
registerProperties(model.ui, ROOT_GROUP_NAME);
publicAPI.updateUI = ui => {
model.ui = JSON.parse(JSON.stringify(ui || [])); // deep copy
Object.keys(propertyMap).forEach(k => delete propertyMap[k]);
Object.keys(groupChildrenNames).forEach(k => delete groupChildrenNames[k]);
registerProperties(model.ui, ROOT_GROUP_NAME);
publicAPI.modified();
};
function listProxyProperties() {
let gName = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ROOT_GROUP_NAME;
return groupChildrenNames[gName];
}
publicAPI.updateProxyProperty = (propertyName, propUI) => {
const prop = propertyMap[propertyName];
if (prop) {
Object.assign(prop, propUI);
} else {
propertyMap[propertyName] = {
...propUI
};
}
};
publicAPI.activate = () => {
if (model.proxyManager) {
const setActiveMethod = `setActive${_capitalize(publicAPI.getProxyGroup().slice(0, -1))}`;
if (model.proxyManager[setActiveMethod]) {
model.proxyManager[setActiveMethod](publicAPI);
}
}
};
// property link
model.propertyLinkSubscribers = {};
publicAPI.registerPropertyLinkForGC = (otherLink, type) => {
if (!(type in model.propertyLinkSubscribers)) {
model.propertyLinkSubscribers[type] = [];
}
model.propertyLinkSubscribers[type].push(otherLink);
};
publicAPI.gcPropertyLinks = type => {
const subscribers = model.propertyLinkSubscribers[type] || [];
while (subscribers.length) {
subscribers.pop().unbind(publicAPI);
}
};
model.propertyLinkMap = {};
publicAPI.getPropertyLink = function (id) {
let persistent = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
if (model.propertyLinkMap[id]) {
return model.propertyLinkMap[id];
}
let value = null;
const links = [];
let count = 0;
let updateInProgress = false;
function update(source) {
let force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
if (updateInProgress) {
return null;
}
const needUpdate = [];
let sourceLink = null;
count = links.length;
while (count--) {
const link = links[count];
if (link.instance === source) {
sourceLink = link;
} else {
needUpdate.push(link);
}
}
if (!sourceLink) {
return null;
}
const newValue = sourceLink.instance[`get${_capitalize(sourceLink.propertyName)}`]();
if (!shallowEquals(newValue, value) || force) {
value = newValue;
updateInProgress = true;
while (needUpdate.length) {
const linkToUpdate = needUpdate.pop();
linkToUpdate.instance.set({
[linkToUpdate.propertyName]: value
});
}
updateInProgress = false;
}
if (model.propertyLinkMap[id].persistent) {
model.propertyLinkMap[id].value = newValue;
}
return newValue;
}
function unbind(instance, propertyName) {
const indexToDelete = [];
count = links.length;
while (count--) {
const link = links[count];
if (link.instance === instance && (link.propertyName === propertyName || propertyName === undefined)) {
link.subscription.unsubscribe();
indexToDelete.push(count);
}
}
while (indexToDelete.length) {
links.splice(indexToDelete.pop(), 1);
}
}
function bind(instance, propertyName) {
let updateMe = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
const subscription = instance.onModified(update);
const other = links[0];
links.push({
instance,
propertyName,
subscription
});
if (updateMe) {
if (model.propertyLinkMap[id].persistent && model.propertyLinkMap[id].value !== undefined) {
instance.set({
[propertyName]: model.propertyLinkMap[id].value
});
} else if (other) {
update(other.instance, true);
}
}
return {
unsubscribe: () => unbind(instance, propertyName)
};
}
function unsubscribe() {
while (links.length) {
links.pop().subscription.unsubscribe();
}
}
const linkHandler = {
bind,
unbind,
unsubscribe,
persistent
};
model.propertyLinkMap[id] = linkHandler;
return linkHandler;
};
// extract values
function getProperties() {
let groupName = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ROOT_GROUP_NAME;
const values = [];
const id = model.proxyId;
const propertyNames = listProxyProperties(groupName) || [];
for (let i = 0; i < propertyNames.length; i++) {
const name = propertyNames[i];
const method = publicAPI[`get${_capitalize(name)}`];
const value = method ? method() : undefined;
const prop = {
id,
name,
value
};
const children = getProperties(name);
if (children.length) {
prop.children = children;
}
values.push(prop);
}
return values;
}
publicAPI.listPropertyNames = () => getProperties().map(p => p.name);
publicAPI.getPropertyByName = name => getProperties().find(p => p.name === name);
publicAPI.getPropertyDomainByName = name => (propertyMap[name] || {}).domain;
// ui section
publicAPI.getProxySection = () => ({
id: model.proxyId,
name: model.proxyGroup,
ui: model.ui,
properties: getProperties()
});
// free resources
publicAPI.delete = () => {
const list = Object.keys(model.propertyLinkMap);
let count = list.length;
while (count--) {
model.propertyLinkMap[list[count]].unsubscribe();
}
Object.keys(model.propertyLinkSubscribers).forEach(publicAPI.gcPropertyLinks);
parentDelete();
};
// @todo fix infinite recursion due to active source
publicAPI.getState = () => null;
function registerLinks() {
// Allow dynamic registration of links at the application level
if (model.links) {
for (let i = 0; i < model.links.length; i++) {
const {
link,
property,
persistent,
updateOnBind,
type
} = model.links[i];
if (type === 'application') {
const sLink = model.proxyManager.getPropertyLink(link, persistent);
publicAPI.registerPropertyLinkForGC(sLink, 'application');
sLink.bind(publicAPI, property, updateOnBind);
}
}
}
}
setImmediateVTK(registerLinks);
}
// ----------------------------------------------------------------------------
// proxyPropertyMapping(publicAPI, model, map)
//
// map = {
// opacity: { modelKey: 'property', property: 'opacity' },
// }
//
// Generated API:
// Elevate set/get methods from internal object stored in the model to current one
// ----------------------------------------------------------------------------
function proxyPropertyMapping(publicAPI, model, map) {
const parentDelete = publicAPI.delete;
const subscriptions = [];
const propertyNames = Object.keys(map);
let count = propertyNames.length;
while (count--) {
const propertyName = propertyNames[count];
const {
modelKey,
property,
modified = true
} = map[propertyName];
const methodSrc = _capitalize(property);
const methodDst = _capitalize(propertyName);
publicAPI[`get${methodDst}`] = model[modelKey][`get${methodSrc}`];
publicAPI[`set${methodDst}`] = model[modelKey][`set${methodSrc}`];
if (modified) {
subscriptions.push(model[modelKey].onModified(publicAPI.modified));
}
}
publicAPI.delete = () => {
while (subscriptions.length) {
subscriptions.pop().unsubscribe();
}
parentDelete();
};
}
// ----------------------------------------------------------------------------
// proxyPropertyState(publicAPI, model, state, defaults)
//
// state = {
// representation: {
// 'Surface with edges': { property: { edgeVisibility: true, representation: 2 } },
// Surface: { property: { edgeVisibility: false, representation: 2 } },
// Wireframe: { property: { edgeVisibility: false, representation: 1 } },
// Points: { property: { edgeVisibility: false, representation: 0 } },
// },
// }
//
// defaults = {
// representation: 'Surface',
// }
//
// Generated API
// get / set Representation ( string ) => push state to various internal objects
// ----------------------------------------------------------------------------
function proxyPropertyState(publicAPI, model) {
let state = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
let defaults = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
model.this = publicAPI;
function applyState(map) {
const modelKeys = Object.keys(map);
let count = modelKeys.length;
while (count--) {
const modelKey = modelKeys[count];
model[modelKey].set(map[modelKey]);
}
}
const modelKeys = Object.keys(defaults);
let count = modelKeys.length;
while (count--) {
// Add default
const key = modelKeys[count];
model[key] = defaults[key];
// Add set method
const mapping = state[key];
publicAPI[`set${_capitalize(key)}`] = value => {
if (value !== model[key]) {
model[key] = value;
const propValues = mapping[value];
applyState(propValues);
publicAPI.modified();
}
};
}
// Add getter
if (modelKeys.length) {
get(publicAPI, model, modelKeys);
}
}
// ----------------------------------------------------------------------------
// From : https://github.com/facebookarchive/fixed-data-table/blob/master/src/vendor_upstream/dom/normalizeWheel.js
//
//
// Copyright (c) 2015, Facebook, Inc.
// All rights reserved.
//
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
//
// Mouse wheel (and 2-finger trackpad) support on the web sucks. It is
// complicated, thus this doc is long and (hopefully) detailed enough to answer
// your questions.
//
// If you need to react to the mouse wheel in a predictable way, this code is
// like your bestest friend.// hugs//
//
// As of today, there are 4 DOM event types you can listen to:
//
// 'wheel' -- Chrome(31+), FF(17+), IE(9+)
// 'mousewheel' -- Chrome, IE(6+), Opera, Safari
// 'MozMousePixelScroll' -- FF(3.5 only!) (2010-2013) -- don't bother!
// 'DOMMouseScroll' -- FF(0.9.7+) since 2003
//
// So what to do? The is the best:
//
// normalizeWheel.getEventType();
//
// In your event callback, use this code to get sane interpretation of the
// deltas. This code will return an object with properties:
//
// spinX -- normalized spin speed (use for zoom) - x plane
// spinY -- " - y plane
// pixelX -- normalized distance (to pixels) - x plane
// pixelY -- " - y plane
//
// Wheel values are provided by the browser assuming you are using the wheel to
// scroll a web page by a number of lines or pixels (or pages). Values can vary
// significantly on different platforms and browsers, forgetting that you can
// scroll at different speeds. Some devices (like trackpads) emit more events
// at smaller increments with fine granularity, and some emit massive jumps with
// linear speed or acceleration.
//
// This code does its best to normalize the deltas for you:
//
// - spin is trying to normalize how far the wheel was spun (or trackpad
// dragged). This is super useful for zoom support where you want to
// throw away the chunky scroll steps on the PC and make those equal to
// the slow and smooth tiny steps on the Mac. Key data: This code tries to
// resolve a single slow step on a wheel to 1.
//
// - pixel is normalizing the desired scroll delta in pixel units. You'll
// get the crazy differences between browsers, but at least it'll be in
// pixels!
//
// - positive value indicates sc