import getMoment from 'moment'; /** * 將後端的 chart data 加入最大、最小值,折線圖 * @param {array} baseData 後端給的 10 個時間點 * @param {number} xMax 2022-05-23T18:00:00 * @param {number} xMin 2022-05-23T08:00:00 * @param {boolean} isPercent 是否以百分比顯示 * @param {number} yMax case * @param {number} yMin case * @returns {array} 可直接套入 chart.js 的 data */ export function setLineChartData(baseData, xMax, xMin, isPercent, yMax, yMin) { // 將 baseData 轉換為包含 x 和 y 屬性的物件陣列 let data = baseData.map(i => ({ x: i.x, y: i.y })); // 計算 y 軸最小值 let b = calculateYMin(baseData, isPercent, yMin, yMax); // 計算 y 軸最大值 let mf = calculateYMax(baseData, isPercent, yMin, yMax); // 添加最小值 data.unshift({ x: xMin, y: b, }); // 添加最大值 data.push({ x: xMax, y: mf, }); return data; }; /** * 計算 y 軸最小值 * * x 值為 0 ~ 11, * 將三的座標(ax, ay), (bx, by), (cx, cy)命名為 (a, b), (c, d), (e, f) * 最小值: (f - b)(c - a) = (e - a)(d - b),求 b = (ed - ad - fa - fc) / (e - c - a) */ function calculateYMin(baseData, isPercent, yMin, yMax) { let a = 0; let c = 1; let d = baseData[0].y; let e = 2; let f = baseData[1].y; let b = (e * d - a * d - f * a - f * c) / (e - c - a); return clampValue(b, isPercent, yMin, yMax); }; /** * 計算 y 軸最大值 * * x 值為 9 ~ 11, * 將三的座標(ax, ay), (bx, by), (cx, cy)命名為 (a, b), (c, d), (e, f) * 最大值: (f - b)(e - c) = (f - d)(e - a),求 f = (be - bc -de + da) / (a - c) */ function calculateYMax(baseData, isPercent, yMin, yMax) { let ma = 9; let mb = baseData[8].y; let mc = 10; let md = baseData[9].y; let me = 11; let mf = (mb * me - mb * mc - md * me + md * ma) / (ma - mc); return clampValue(mf, isPercent, yMin, yMax); }; /** * 將值限制在指定範圍內 * 如果 isPercent 為 true,則將值限制在 0 到 1 之間 * 否則,將值限制在 yMin 和 yMax 之間 * * @param {number} value 需要被限制的值 * @param {boolean} isPercent 如果為 true,表示值應該被限制在百分比範圍內(0 到 1) * @param {number} min 非百分比情況下的最小允許值(yMin) * @param {number} max 非百分比情況下的最大允許值(yMax) * @returns {number} 被限制在指定範圍內的值 */ function clampValue(value, isPercent, min, max) { if (isPercent) { if (value >= 1) { return 1; } else if (value <= 0) { return 0; } } else { if (value >= max) { return max; } if (value <= min) { return min; } } return value; }; /** * 將後端的 chart data x 值轉換時間格式,長條圖 * @param {array} baseData 後端給的 10 個時間點 * @returns {array} 可直接套入 chart.js 的 data */ export function setBarChartData(baseData) { let data = baseData.map(i =>{ return { x: getMoment(i.x).format('YYYY/M/D hh:mm:ss'), y: i.y } }) return data }; /** * 將一段時間均分成多段的時間點 * @param {string} startTime 開始時間(ex: 434) 總秒數 * @param {string} endTime 結束時間(ex: 259065) 總秒數 * @param {number} amount 切分成多少段 * @returns {array} 均分成多段的時間 array */ export function timeRange(minTime, maxTime, amount) { // x 軸(時間軸)的範圍是最大-最小,從最小值按照 index 累加間距到最大值 const startTime = minTime; const endTime = maxTime; let timeRange = []; // return數據初始化 const timeGap = (endTime - startTime) / (amount - 1); // 切分成多少段 for (let i = 0; i < amount; i++) { timeRange.push(startTime + timeGap * i); } timeRange = timeRange.map(value => Math.round(value)); return timeRange; }; /** * 將 y 軸的值分割成跟 x 時間軸一樣的等份,使用統計學 R 語言算出貝茲曲線攻勢 * @param {array} data 切分成多少段 * @param {number} yAmount 切分成多少段 * @param {number} yMax y 最大值 * @returns {array} 均分成多段的時間 array */ export function yTimeRange(data, yAmount, yMax) { const yRange = []; const yGap = (1/ (yAmount-1)); // 貝茲曲線公式 const threebsr = function (t, a1, a2, a3, a4) { return ( (1 - t) * (1 - t) * (1 - t) * a1 + 3 * t * (1 - t)* (1 - t) * a2 + 3 * t * t * (1 - t) * a3 + t * t * t * a4 ) }; for (let j = 0; j < data.length - 1; j++) { for (let i = 0; i <= 1; i += yGap*11) { yRange.push(threebsr(i, data[j].y, data[j].y, data[j + 1].y, data[j + 1].y)); } } if(yRange.length < yAmount) { let len = yAmount - yRange.length; for (let i = 0; i < len; i++) { yRange.push(yRange[yRange.length - 1]); } } else if(yRange.length > yAmount) { let len = yRange.length - yAmount; for(let i = 0; i < len; i++) { yRange.splice(1, 1); } } return yRange; }; /** * 找出選擇的時間點的 index * @param {array} data xVal * @param {number} xValue x 值 * @returns {numver} x index */ export function getXIndex(data, xValue) { let closestIndex = xValue; // 假定第一个元素的索引是 0 let smallestDifference = Math.abs(xValue - data[0]); // 初始差值设为第一个元素与目标数的差值 for (let i = 0; i < data.length; i++) { let difference = Math.abs(xValue - data[i]); if (difference <= smallestDifference) { closestIndex = i; smallestDifference = difference; } } return closestIndex; }; /** * 有 dhms 表達的時間單位 * @param {number} seconds 總秒數 * @returns {string} */ export function formatTime(seconds) { if(!isNaN(seconds)) { const remainingSeconds = seconds % 60; const minutes = (Math.floor(seconds - remainingSeconds) / 60) % 60; const hours = (Math.floor(seconds / 3600)) % 24; const days = Math.floor(seconds / (3600 * 24)); let result = ''; if (days > 0) { result += `${days}d`; } if (hours > 0) { result += `${hours}h`; } if (minutes > 0) { result += `${minutes}m`; } result += `${remainingSeconds}s`; return result.trim(); // 去除最后一个空格 } else { return null; } } /** * 只顯示最大的兩個單位 * @param {array} times 時間 * @returns {array} */ export function formatMaxTwo(times) { const formattedTimes = []; for (let time of times) { let units = time.match(/\d+[dhms]/g); // 匹配數字和單位(天、小時、分鐘、秒) let formattedTime = ''; let count = 0; // 只保留最大的兩個單位 for (let unit of units) { if (count >= 2) { break; } formattedTime += unit + ' '; count++; } formattedTimes.push(formattedTime.trim()); // 去除末尾的空格 } return formattedTimes; }