UNPKG

wsemi

Version:

A support package for web developer.

721 lines (666 loc) 20.6 kB
import size from 'lodash/size' import get from 'lodash/get' import each from 'lodash/each' import reverse from 'lodash/reverse' import toNumber from 'lodash/toNumber' import isNumber from 'lodash/isNumber' import isnum from './isnum.mjs' import isarr from './isarr.mjs' import isestr from './isestr.mjs' let inXRange = 'in x-range' let outOfXRange = 'out of x-range' let orderList = 'order list' let reverseOrderList = 'reverse order list' function toArrayXY(ps, opt = {}) { //若無數據回傳空陣列 if (size(ps) <= 0) { return [] } //keyX let keyX = get(opt, 'keyX') if (!isestr(keyX)) { keyX = 'x' } //keyY let keyY = get(opt, 'keyY') if (!isestr(keyY)) { keyY = 'y' } //rs let rs = [] each(ps, (v) => { let x = null let y = null if (isarr(v) && size(v) >= 2) { x = get(v, 0) y = get(v, 1) } else { x = get(v, keyX, null) y = get(v, keyY, null) } if (isnum(x) && isnum(y)) { x = toNumber(x) y = toNumber(y) rs.push({ x, y, }) } }) return rs } function checkTrend(ps) { //check let bOrder = true let bReverseOrder = true for (let i = 1; i < size(ps); i++) { let p0 = ps[i - 1] let p1 = ps[i] if (bOrder && p0.x > p1.x) { //有遞減出現 bOrder = false } if (bReverseOrder && p0.x < p1.x) { //有遞增出現 bReverseOrder = false } if (p0.x === p1.x) { return { err: `x can not be equal: i=${i}, x=${p0.x}` } } } if (bOrder) { return orderList } if (bReverseOrder) { return reverseOrderList } return 'no' } function checkLimit(ps, x) { //xmin, xmax, 外部已確保x為遞增 let xmin = null let xmax = null xmin = ps[0].x xmax = ps[size(ps) - 1].x //lt, less than lower limit if (x < xmin) { return { err: outOfXRange, msg: `x[${x}] less than lower limit[${xmin}]`, data: { ps, x, xmin, xmax, }, } } //gt, greater than upper limit if (x > xmax) { return { err: outOfXRange, msg: `x[${x}] greater than upper limit[${xmax}]`, data: { ps, x, xmin, xmax, }, } } return inXRange } function interp1Linear(ps, x) { //尋找x對應y值, 採各點之間線性內插 //checkLimit let cl = checkLimit(ps, x) if (cl !== inXRange) { return cl } //r let r = null for (let i = 1; i < size(ps); i++) { let p0 = ps[i - 1] let p1 = ps[i] if (p0.x <= x && x <= p1.x) { r = ((x - p0.x) * p1.y + (p1.x - x) * p0.y) / (p1.x - p0.x) break } } //check, 有檢核x是否超過上下限, 此處應該不會用到 if (r === null) { return { err: outOfXRange } } return r } function interp1Stairs(ps, x, opt = {}) { //每個點為bar中點, 尋找x所在之bar值(y) //xMin, 擴充最小x值範圍 let xMin = get(opt, 'xMin') if (isNumber(xMin)) { //外部已確保x為遞增 if (xMin > ps[0].x) { return { err: `xMin=${xMin} > ps[0].x=${ps[0].x}` } } else if (xMin === ps[0].x) { //不用擴充 } else { let p = { x: xMin, y: ps[0].y, //使用第一筆數據y } ps = [p, ...ps] //遞增數據得添加xMin於最前 } } //xMax, 擴充最大x值範圍 let xMax = get(opt, 'xMax') if (isNumber(xMax)) { //外部已確保x為遞增 if (xMax < ps[size(ps) - 1].x) { return { err: `xMax=${xMax} < ps[size(ps)-1].x=${ps[size(ps) - 1].x}` } } else if (xMax === ps[size(ps) - 1].x) { //不用擴充 } else { let p = { x: xMax, y: ps[size(ps) - 1].y, //使用最後一筆數據y } ps = [...ps, p] //遞增數據得添加xMax於最後 } } //checkLimit let cl = checkLimit(ps, x) if (cl !== inXRange) { return cl } //r let r = null for (let i = 0; i < size(ps); i++) { let p0 = get(ps, i - 1, null) let p1 = get(ps, i, null) let p2 = get(ps, i + 1, null) let x0 = p1.x if (p0) { x0 = (p0.x + p1.x) / 2 //若有前者, 則取前者與自己x的平均值作為起點 } let x1 = p1.x if (p2) { x1 = (p2.x + p1.x) / 2 //若有後者, 則取後者與自己x的平均值作為終點 } if (x0 <= x && x <= x1) { r = p1.y break } } //check, 有檢核x是否超過上下限, 此處應該不會用到 if (r === null) { return { err: outOfXRange } } return r } function interp1Blocks(ps, x) { //1點為起點, 其餘各點為各bar終點, 尋找x所在之bar值(y) //checkLimit let cl = checkLimit(ps, x) if (cl !== inXRange) { return cl } //r let r = null for (let i = 1; i < size(ps); i++) { let p0 = ps[i - 1] let p1 = ps[i] let x0 = p0.x let x1 = p1.x if (x0 <= x && x <= x1) { r = p1.y break } } //check, 有檢核x是否超過上下限, 此處應該不會用到 if (r === null) { return { err: outOfXRange } } return r } /** * 一維數據內插 * * Unit Test: {@link https://github.com/yuda-lyu/wsemi/blob/master/test/interp1.test.mjs Github} * @memberOf wsemi * @param {Array} ps 輸入一維數據,格式可支援兩種,第一種各點為陣列[[x1,y1],[x2,y2],...],例如[[0.1,5],[0.2,12],...],第二種各點為物件,屬性至少要有xy,格式為[{x:x1,y:y1},{x:x2,y:y2},...],例如[{x:0.1,y:5},{x:0.2,y:12},...],key值xy可由opt更換 * @param {Number} x 輸入要內插點的x值 * @param {Object} [opt={}] 輸入設定物件,預設{} * @param {String} [opt.mode=''] 輸入內插方法,可選'linear''stairs''blocks',預設'linear' * @param {String} [opt.keyX='x'] 輸入若數據為物件陣列,取物件x值時的key字串,預設為'x' * @param {String} [opt.keyY='y'] 輸入若數據為物件陣列,取物件y值時的key字串,預設為'y' * @param {Number} [opt.xMin=undefined] 輸入若mode='stairs',更改x範圍下限值,預設為undefined * @param {Number} [opt.xMax=undefined] 輸入若mode='stairs',更改x範圍上限值,預設為undefined * @returns {Number|Object} 回傳內插結果數值,或是無法內插時之錯誤訊息物件 * @example * * let r * let x * * let ps = [ * { x: 1, y: 0.2 }, * { x: 3, y: 1.2 }, * { x: 4, y: 2 }, * ] * * let psInv = [ * { x: 4, y: 2 }, * { x: 3, y: 1.2 }, * { x: 1, y: 0.2 }, * ] * * let psErr = [ * { x: 'a', y: 0.2 }, * { x: 'mnop', y: 1.2 }, * { x: 'xyz', y: 2 }, * ] * * let psEmpty = [ * ] * * let psEffOne = [ * { x: 1, y: 0.2 }, * { x: 'mnop', y: 1.2 }, * { x: 'xyz', y: 2 }, * ] * * let psP = [ * { a: 1, b: 0.2 }, * { a: 3, b: 1.2 }, * { a: 4, b: 2 }, * ] * * let opt = { * mode: 'stairs', * } * * let optX = { * mode: 'stairs', * xMin: 0, * xMax: 4.5, * } * * let optP = { * keyX: 'a', * keyY: 'b', * } * * x = 0 * r = interp1(psErr, x) * console.log(`linear(error data): x=${x}`, 'r=' + JSON.stringify(r)) * // => linear(error data): x=0 r={"err":"ps(length=0) is not an effective array","ps":[{"x":"a","y":0.2},{"x":"mnop","y":1.2},{"x":"xyz","y":2}],"psEff":[]} * * x = 0 * r = interp1(psEmpty, x) * console.log(`linear(empty data): x=${x}`, 'r=' + JSON.stringify(r)) * // => linear(empty data): x=0 r={"err":"ps(length=0) is not an effective array","ps":[],"psEff":[]} * * x = 0 * r = interp1(psEffOne, x) * console.log(`linear(one point): x=${x}`, 'r=' + JSON.stringify(r)) * // => linear(one point): x=0 r={"err":"ps(length=1) is one point only","ps":[{"x":1,"y":0.2},{"x":"mnop","y":1.2},{"x":"xyz","y":2}],"psEff":[{"x":1,"y":0.2}]} * * x = 0 * r = interp1(ps, x) * console.log(`linear: x=${x}`, 'r=' + JSON.stringify(r)) * // => linear: x=0 r={"err":"x[0] less than lower limit[1]","data":{"ps":[{"x":1,"y":0.2},{"x":3,"y":1.2},{"x":4,"y":2}],"x":0,"trend":"increasing sequence","xmin":1,"xmax":4}} * * x = 1 * r = interp1(ps, x) * console.log(`linear: x=${x}`, 'r=' + JSON.stringify(r)) * // => linear: x=1 r=0.2 * * x = 2 * r = interp1(ps, x) * console.log(`linear: x=${x}`, 'r=' + JSON.stringify(r)) * // => linear: x=2 r=0.7 * * x = 2.6 * r = interp1(ps, x) * console.log(`linear: x=${x}`, 'r=' + JSON.stringify(r)) * // => linear: x=2.6 r=1 * * x = 3 * r = interp1(ps, x) * console.log(`linear: x=${x}`, 'r=' + JSON.stringify(r)) * // => linear: x=3 r=1.2 * * x = 3.5 * r = interp1(ps, x) * console.log(`linear: x=${x}`, 'r=' + JSON.stringify(r)) * // => linear: x=3.5 r=1.6 * * x = 4 * r = interp1(ps, x) * console.log(`linear: x=${x}`, 'r=' + JSON.stringify(r)) * // => linear: x=4 r=2 * * x = 5 * r = interp1(ps, x) * console.log(`linear: x=${x}`, 'r=' + JSON.stringify(r)) * // => linear: x=5 r={"err":"x[5] greater than upper limit[4]","data":{"ps":[{"x":1,"y":0.2},{"x":3,"y":1.2},{"x":4,"y":2}],"x":5,"trend":"increasing sequence","xmin":1,"xmax":4}} * * x = 0 * r = interp1(psInv, x) * console.log(`linear(inverse data): x=${x}`, 'r=' + JSON.stringify(r)) * // => linear(inverse data): x=0 r={"err":"x[0] less than lower limit[1]","data":{"ps":[{"x":1,"y":0.2},{"x":3,"y":1.2},{"x":4,"y":2}],"x":0,"trend":"increasing sequence","xmin":1,"xmax":4}} * * x = 1 * r = interp1(psInv, x) * console.log(`linear(inverse data): x=${x}`, 'r=' + JSON.stringify(r)) * // => linear(inverse data): x=1 r=0.2 * * x = 2 * r = interp1(psInv, x) * console.log(`linear(inverse data): x=${x}`, 'r=' + JSON.stringify(r)) * // => linear(inverse data): x=2 r=0.7 * * x = 2.6 * r = interp1(psInv, x) * console.log(`linear(inverse data): x=${x}`, 'r=' + JSON.stringify(r)) * // => linear(inverse data): x=2.6 r=1 * * x = 3 * r = interp1(psInv, x) * console.log(`linear(inverse data): x=${x}`, 'r=' + JSON.stringify(r)) * // => linear(inverse data): x=3 r=1.2 * * x = 3.5 * r = interp1(psInv, x) * console.log(`linear(inverse data): x=${x}`, 'r=' + JSON.stringify(r)) * // => linear(inverse data): x=3.5 r=1.6 * * x = 4 * r = interp1(psInv, x) * console.log(`linear(inverse data): x=${x}`, 'r=' + JSON.stringify(r)) * // => linear(inverse data): x=4 r=2 * * x = 5 * r = interp1(psInv, x) * console.log(`linear(inverse data): x=${x}`, 'r=' + JSON.stringify(r)) * // => linear: x=5 r={"err":"x[5] greater than upper limit[4]","data":{"ps":[{"x":1,"y":0.2},{"x":3,"y":1.2},{"x":4,"y":2}],"x":5,"trend":"increasing sequence","xmin":1,"xmax":4}} * * x = -1 * r = interp1(ps, x, opt) * console.log(`stairs: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs: x=-1 r={"err":"x[-1] less than lower limit[1]","data":{"ps":[{"x":1,"y":0.2},{"x":3,"y":1.2},{"x":4,"y":2}],"x":-1,"trend":"increasing sequence","xmin":1,"xmax":4}} * * x = 0.51 * r = interp1(ps, x, opt) * console.log(`stairs: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs: x=0.51 r={"err":"x[0.51] less than lower limit[1]","data":{"ps":[{"x":1,"y":0.2},{"x":3,"y":1.2},{"x":4,"y":2}],"x":0.51,"trend":"increasing sequence","xmin":1,"xmax":4}} * * x = 1 * r = interp1(ps, x, opt) * console.log(`stairs: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs: x=1 r=0.2 * * x = 1.9 * r = interp1(ps, x, opt) * console.log(`stairs: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs: x=1.9 r=0.2 * * x = 2 * r = interp1(ps, x, opt) * console.log(`stairs: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs: x=2 r=0.2 * * x = 2.1 * r = interp1(ps, x, opt) * console.log(`stairs: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs: x=2.1 r=1.2 * * x = 2.5 * r = interp1(ps, x, opt) * console.log(`stairs: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs: x=2.5 r=1.2 * * x = 3 * r = interp1(ps, x, opt) * console.log(`stairs: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs: x=3 r=1.2 * * x = 3.49 * r = interp1(ps, x, opt) * console.log(`stairs: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs: x=3.49 r=1.2 * * x = 3.5 * r = interp1(ps, x, opt) * console.log(`stairs: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs: x=3.5 r=1.2 * * x = 3.51 * r = interp1(ps, x, opt) * console.log(`stairs: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs: x=3.51 r=2 * * x = 4 * r = interp1(ps, x, opt) * console.log(`stairs: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs: x=4 r=2 * * x = 4.5 * r = interp1(ps, x, opt) * console.log(`stairs: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs: x=4.5 r={"err":"x[4.5] greater than upper limit[4]","data":{"ps":[{"x":1,"y":0.2},{"x":3,"y":1.2},{"x":4,"y":2}],"x":4.5,"trend":"increasing sequence","xmin":1,"xmax":4}} * * x = -1 * r = interp1(ps, x, optX) * console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs with x-limit: x=-1 r={"err":"x[-1] less than lower limit[0]","data":{"ps":[{"x":0,"y":0.2},{"x":1,"y":0.2},{"x":3,"y":1.2},{"x":4,"y":2},{"x":4.5,"y":2}],"x":-1,"trend":"increasing sequence","xmin":0,"xmax":4.5}} * * x = 0 * r = interp1(ps, x, optX) * console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs with x-limit: x=0 r=0.2 * * x = 0.49 * r = interp1(ps, x, optX) * console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs with x-limit: x=0.49 r=0.2 * * x = 0.5 * r = interp1(ps, x, optX) * console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs with x-limit: x=0.5 r=0.2 * * x = 0.51 * r = interp1(ps, x, optX) * console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs with x-limit: x=0.51 r=0.2 * * x = 1 * r = interp1(ps, x, optX) * console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs with x-limit: x=1 r=0.2 * * x = 1.9 * r = interp1(ps, x, optX) * console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs with x-limit: x=1.9 r=0.2 * * x = 2 * r = interp1(ps, x, optX) * console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs with x-limit: x=2 r=0.2 * * x = 2.1 * r = interp1(ps, x, optX) * console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs with x-limit: x=2.1 r=1.2 * * x = 2.5 * r = interp1(ps, x, optX) * console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs with x-limit: x=2.5 r=1.2 * * x = 3 * r = interp1(ps, x, optX) * console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs with x-limit: x=3 r=1.2 * * x = 3.49 * r = interp1(ps, x, optX) * console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs with x-limit: x=3.49 r=1.2 * * x = 3.5 * r = interp1(ps, x, optX) * console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs with x-limit: x=3.5 r=1.2 * * x = 3.51 * r = interp1(ps, x, optX) * console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs with x-limit: x=3.51 r=2 * * x = 4 * r = interp1(ps, x, optX) * console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs with x-limit: x=4 r=2 * * x = 4.49 * r = interp1(ps, x, optX) * console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs with x-limit: x=4.49 r=2 * * x = 4.5 * r = interp1(ps, x, optX) * console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs with x-limit: x=4.5 r=2 * * x = 4.51 * r = interp1(ps, x, optX) * console.log(`stairs with x-limit: x=${x}`, 'r=' + JSON.stringify(r)) * // => stairs with x-limit: x=4.51 r={"err":"x[4.51] greater than upper limit[4.5]","data":{"ps":[{"x":0,"y":0.2},{"x":1,"y":0.2},{"x":3,"y":1.2},{"x":4,"y":2},{"x":4.5,"y":2}],"x":4.51,"trend":"increasing sequence","xmin":0,"xmax":4.5}} * * x = 0 * r = interp1(psP, x, optP) * console.log(`linear by keyX & keyY: x=${x}`, 'r=' + JSON.stringify(r)) * // => linear by keyX & keyY: x=0 r={"err":"x[0] less than lower limit[1]","data":{"ps":[{"x":1,"y":0.2},{"x":3,"y":1.2},{"x":4,"y":2}],"x":0,"trend":"increasing sequence","xmin":1,"xmax":4}} * * x = 1 * r = interp1(psP, x, optP) * console.log(`linear by keyX & keyY: x=${x}`, 'r=' + JSON.stringify(r)) * // => linear by keyX & keyY: x=1 r=0.2 * * x = 2 * r = interp1(psP, x, optP) * console.log(`linear by keyX & keyY: x=${x}`, 'r=' + JSON.stringify(r)) * // => linear by keyX & keyY: x=2 r=0.7 * * x = 2.6 * r = interp1(psP, x, optP) * console.log(`linear by keyX & keyY: x=${x}`, 'r=' + JSON.stringify(r)) * // => linear by keyX & keyY: x=2.6 r=1 * * x = 3 * r = interp1(psP, x, optP) * console.log(`linear by keyX & keyY: x=${x}`, 'r=' + JSON.stringify(r)) * // => linear by keyX & keyY: x=3 r=1.2 * * x = 3.5 * r = interp1(psP, x, optP) * console.log(`linear by keyX & keyY: x=${x}`, 'r=' + JSON.stringify(r)) * // => linear by keyX & keyY: x=3.5 r=1.6 * * x = 4 * r = interp1(psP, x, optP) * console.log(`linear by keyX & keyY: x=${x}`, 'r=' + JSON.stringify(r)) * // => linear by keyX & keyY: x=4 r=2 * * x = 5 * r = interp1(psP, x, optP) * console.log(`linear by keyX & keyY: x=${x}`, 'r=' + JSON.stringify(r)) * // => linear by keyX & keyY: x=5 r={"err":"x[5] greater than upper limit[4]","data":{"ps":[{"x":1,"y":0.2},{"x":3,"y":1.2},{"x":4,"y":2}],"x":5,"trend":"increasing sequence","xmin":1,"xmax":4}} * */ function interp1(ps, x, opt = {}) { //check if (!isarr(ps)) { return { err: 'ps is not an array' } } if (!isNumber(x)) { return { err: 'x is not a number' } } //toArrayXY let psEff = toArrayXY(ps, opt) //check if (get(psEff, 'err')) { return get(psEff, 'err') } //check if (size(psEff) === 0) { return { err: 'ps(length=0) is not an effective array', ps, psEff, } } else if (size(psEff) === 1) { return { err: 'ps(length=1) is one point only', ps, psEff, } } //checkIncrea let trend = checkTrend(psEff) //check if (trend === 'no') { return { err: 'psEff is not increasing or decreasing' } } //反序 if (trend === reverseOrderList) { trend = orderList psEff = reverse(psEff) } //mode let mode = get(opt, 'mode') if (mode !== 'linear' && mode !== 'stairs' && mode !== 'blocks') { mode = 'linear' } try { if (mode === 'linear') { return interp1Linear(psEff, x) } else if (mode === 'blocks') { return interp1Blocks(psEff, x) } else if (mode === 'stairs') { return interp1Stairs(psEff, x, opt) } } catch (err) { return { err: err.toString(), } } } export default interp1