UNPKG

@kitware/vtk.js

Version:

Visualization Toolkit for the Web

1,491 lines (1,409 loc) 57 kB
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