UNPKG

ml-array-xy-equally-spaced

Version:
323 lines (268 loc) 8.48 kB
'use strict'; var sequentialFill = require('ml-array-sequential-fill'); var mlZones = require('ml-zones'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var sequentialFill__default = /*#__PURE__*/_interopDefaultLegacy(sequentialFill); /** * function that retrieves the getEquallySpacedData with the variant "slot" * * @param {Array<number>} x * @param {Array<number>} y * @param {number} from - Initial point * @param {number} to - Final point * @param {number} numberOfPoints * @return {Array} - Array of y's equally spaced with the variant "slot" */ function equallySpacedSlot(x, y, from, to, numberOfPoints) { let xLength = x.length; let step = (to - from) / (numberOfPoints > 1 ? numberOfPoints - 1 : 1); let halfStep = step / 2; let lastStep = x[x.length - 1] - x[x.length - 2]; let start = from - halfStep; let output = new Array(numberOfPoints); // Init main variables let min = start; let max = start + step; let previousX = -Number.MAX_VALUE; let previousY = 0; let nextX = x[0]; let nextY = y[0]; let frontOutsideSpectra = 0; let backOutsideSpectra = true; let currentValue = 0; // for slot algorithm let currentPoints = 0; let i = 1; // index of input let j = 0; // index of output main: while (true) { if (previousX >= nextX) throw new Error('x must be a growing series'); while (previousX - max > 0) { // no overlap with original point, just consume current value if (backOutsideSpectra) { currentPoints++; backOutsideSpectra = false; } output[j] = currentPoints <= 0 ? 0 : currentValue / currentPoints; j++; if (j === numberOfPoints) { break main; } min = max; max += step; currentValue = 0; currentPoints = 0; } if (previousX > min) { currentValue += previousY; currentPoints++; } if (previousX === -Number.MAX_VALUE || frontOutsideSpectra > 1) { currentPoints--; } previousX = nextX; previousY = nextY; if (i < xLength) { nextX = x[i]; nextY = y[i]; i++; } else { nextX += lastStep; nextY = 0; frontOutsideSpectra++; } } return output; } /** * Function that calculates the integral of the line between two * x-coordinates, given the slope and intercept of the line. * @param {number} x0 * @param {number} x1 * @param {number} slope * @param {number} intercept * @return {number} integral value. */ function integral(x0, x1, slope, intercept) { return ( 0.5 * slope * x1 * x1 + intercept * x1 - (0.5 * slope * x0 * x0 + intercept * x0) ); } /** * function that retrieves the getEquallySpacedData with the variant "smooth" * * @param {Array<number>} x * @param {Array<number>} y * @param {number} from - Initial point * @param {number} to - Final point * @param {number} numberOfPoints * @return {Array} - Array of y's equally spaced with the variant "smooth" */ function equallySpacedSmooth(x, y, from, to, numberOfPoints) { let xLength = x.length; let step = (to - from) / (numberOfPoints > 1 ? numberOfPoints - 1 : 1); let halfStep = step / 2; let output = new Array(numberOfPoints); let initialOriginalStep = x[1] - x[0]; let lastOriginalStep = x[xLength - 1] - x[xLength - 2]; // Init main variables let min = from - halfStep; let max = from + halfStep; let previousX = Number.MIN_SAFE_INTEGER; let previousY = 0; let nextX = x[0] - initialOriginalStep; let nextY = 0; let currentValue = 0; let slope = 0; let intercept = 0; let sumAtMin = 0; let sumAtMax = 0; let i = 0; // index of input let j = 0; // index of output function getSlope(x0, y0, x1, y1) { return (y1 - y0) / (x1 - x0); } let add = 0; main: while (true) { if (previousX >= nextX) throw new Error('x must be a growing series'); if (previousX <= min && min <= nextX) { add = integral(0, min - previousX, slope, previousY); sumAtMin = currentValue + add; } while (nextX - max >= 0) { // no overlap with original point, just consume current value add = integral(0, max - previousX, slope, previousY); sumAtMax = currentValue + add; output[j++] = (sumAtMax - sumAtMin) / step; if (j === numberOfPoints) { break main; } min = max; max += step; sumAtMin = sumAtMax; } currentValue += integral(previousX, nextX, slope, intercept); previousX = nextX; previousY = nextY; if (i < xLength) { nextX = x[i]; nextY = y[i]; i++; } else if (i === xLength) { nextX += lastOriginalStep; nextY = 0; } slope = getSlope(previousX, previousY, nextX, nextY); intercept = -slope * previousX + previousY; } return output; } /** * Function that returns a Number array of equally spaced numberOfPoints * containing a representation of intensities of the spectra arguments x * and y. * * The options parameter contains an object in the following form: * from: starting point * to: last point * numberOfPoints: number of points between from and to * variant: "slot" or "smooth" - smooth is the default option * * The slot variant consist that each point in the new array is calculated * averaging the existing points between the slot that belongs to the current * value. The smooth variant is the same but takes the integral of the range * of the slot and divide by the step size between two points in the new array. * * If exclusions zone are present, zones are ignored ! * @param {object} [arrayXY={}] - object containing 2 properties x and y (both an array) * @param {object} [options={}] * @param {number} [options.from=x[0]] * @param {number} [options.to=x[x.length-1]] * @param {string} [options.variant='smooth'] * @param {number} [options.numberOfPoints=100] * @param {Array} [options.exclusions=[]] array of from / to that should be skipped for the generation of the points * @param {Array} [options.zones=[]] array of from / to that should be kept * @return {object<x: Array, y:Array>} new object with x / y array with the equally spaced data. */ function equallySpaced(arrayXY = {}, options = {}) { let { x, y } = arrayXY; let xLength = x.length; let reverse = false; if (x.length > 1 && x[0] > x[1]) { x = x.slice().reverse(); y = y.slice().reverse(); reverse = true; } let { from = x[0], to = x[xLength - 1], variant = 'smooth', numberOfPoints = 100, exclusions = [], zones = [], } = options; if (xLength !== y.length) { throw new RangeError("the x and y vector doesn't have the same size."); } if (typeof from !== 'number' || isNaN(from)) { throw new RangeError("'from' option must be a number"); } if (typeof to !== 'number' || isNaN(to)) { throw new RangeError("'to' option must be a number"); } if (typeof numberOfPoints !== 'number' || isNaN(numberOfPoints)) { throw new RangeError("'numberOfPoints' option must be a number"); } if (numberOfPoints < 2) { throw new RangeError("'numberOfPoints' option must be greater than 1"); } if (zones.length === 0) { zones = mlZones.invert(exclusions, { from, to }); } zones = mlZones.zonesWithPoints(zones, numberOfPoints, { from, to }); let xResult = []; let yResult = []; for (let zone of zones) { let zoneResult = processZone( x, y, zone.from, zone.to, zone.numberOfPoints, variant); xResult = xResult.concat(zoneResult.x); yResult = yResult.concat(zoneResult.y); } if (reverse) { if (from < to) { return { x: xResult.reverse(), y: yResult.reverse() }; } else { return { x: xResult, y: yResult }; } } else { if (from < to) { return { x: xResult, y: yResult }; } else { return { x: xResult.reverse(), y: yResult.reverse() }; } } } function processZone(x, y, from, to, numberOfPoints, variant) { if (numberOfPoints < 1) { throw new RangeError('the number of points must be at least 1'); } let output = variant === 'slot' ? equallySpacedSlot(x, y, from, to, numberOfPoints) : equallySpacedSmooth(x, y, from, to, numberOfPoints); return { x: sequentialFill__default["default"]({ from, to, size: numberOfPoints, }), y: output, }; } module.exports = equallySpaced;