Add JSDoc documentation and file headers to all source files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 18:55:36 +08:00
parent 3b7b6ae859
commit 7fec6cb63f
199 changed files with 2764 additions and 503 deletions

View File

@@ -1,7 +1,18 @@
// The Lucia project.
// Copyright 2023-2026 DSP, inc. All rights reserved.
// Authors:
// chiayin.kuo@dsp.im (chiayin), 2023/1/31
// imacat.yang@dsp.im (imacat), 2023/9/23
// cindy.chang@dsp.im (Cindy Chang), 2024/5/30
/** @module abbreviateNumber Duration abbreviation formatting. */
/**
* 將數字轉換成簡寫的形式,設定 dhms 的數值
* @param {number} totalSeconds 總秒數
* @returns {string}
* Converts a total number of seconds into a human-readable abbreviated
* duration string using days, hours, minutes, and seconds.
*
* @param {number} totalSeconds - The total number of seconds to convert.
* @returns {string} The abbreviated duration string (e.g. "2d 3h 15m 30s"),
* or "0" if totalSeconds is zero.
*/
export default function abbreviateNumber(totalSeconds) {
let seconds = 0;

View File

@@ -1,3 +1,11 @@
// The Lucia project.
// Copyright 2023-2026 DSP, inc. All rights reserved.
// Authors:
// chiayin.kuo@dsp.im (chiayin), 2023/1/31
// imacat.yang@dsp.im (imacat), 2023/9/23
// cindy.chang@dsp.im (Cindy Chang), 2024/5/30
/** @module alertModal SweetAlert2 modal dialogs for user interactions. */
import Swal from 'sweetalert2';
import { useAllMapDataStore } from '@/stores/allMapData';
import { useConformanceStore } from '@/stores/conformance';
@@ -18,8 +26,11 @@ const customClass = {
cancelButton: '!inline-block !rounded-full !text-sm !font-medium !text-center !align-middle !transition-colors !duration-300 !px-5 !py-2 !w-[100px] !h-[40px] ',
};
/**
* Map Saved
* @param { function } addFilterId 後端 API
* Shows a modal dialog to save a new filter with a user-provided name.
*
* @param {Function} addFilterId - Backend API function to create the filter.
* @param {Function|null} [next=null] - Vue Router next() guard callback.
* @returns {Promise<boolean>} True if the filter was saved, false otherwise.
*/
export async function saveFilter(addFilterId, next = null) {
let fileName = '';
@@ -67,8 +78,10 @@ export async function saveFilter(addFilterId, next = null) {
}
}
/**
* Saved Success
* @param { string } value File's name
* Shows a timed success notification after a file has been saved.
*
* @param {string} value - The name of the saved file.
* @returns {Promise<void>}
*/
export async function savedSuccessfully(value) {
value = value || '';
@@ -83,11 +96,16 @@ export async function savedSuccessfully(value) {
})
};
/**
* leave Map page
* @param { function } next 執行完函式後的步驟
* @param { function } addFilterId 後端 API
* @param { string } toPath route path
* @param { function } logOut 登出函式
* Prompts the user to save unsaved filter changes before leaving the
* Map page. Handles confirm (save), cancel (discard), and backdrop
* (stay) scenarios.
*
* @param {Function} next - Vue Router next() guard callback.
* @param {Function} addFilterId - Backend API function to create the filter.
* @param {string} toPath - The destination route path.
* @param {Function} [logOut] - Optional logout function to call instead
* of navigating.
* @returns {Promise<void>}
*/
export async function leaveFilter(next, addFilterId, toPath, logOut) {
const allMapDataStore = useAllMapDataStore();
@@ -140,8 +158,12 @@ export async function leaveFilter(next, addFilterId, toPath, logOut) {
};
/**
* Conformance Saved
* @param { function } addConformanceCreateCheckId 後端 API
* Shows a modal dialog to save a new conformance rule with a
* user-provided name.
*
* @param {Function} addConformanceCreateCheckId - Backend API function
* to create the conformance check.
* @returns {Promise<boolean>} True if the rule was saved, false otherwise.
*/
export async function saveConformance(addConformanceCreateCheckId) {
let fileName = '';
@@ -179,11 +201,16 @@ export async function saveConformance(addConformanceCreateCheckId) {
}
}
/**
* leave Conformance page
* @param { function } next 執行完函式後的步驟
* @param { function } addConformanceCreateCheckId 後端 API
* @param { string } toPath route path
* @param { function } logOut 登出函式
* Prompts the user to save unsaved conformance rule changes before
* leaving the Conformance page. Delegates to helper functions for
* confirm, cancel, and backdrop scenarios.
*
* @param {Function} next - Vue Router next() guard callback.
* @param {Function} addConformanceCreateCheckId - Backend API function
* to create the conformance check.
* @param {string} toPath - The destination route path.
* @param {Function} [logOut] - Optional logout function.
* @returns {Promise<void>}
*/
export async function leaveConformance(next, addConformanceCreateCheckId, toPath, logOut) {
const conformanceStore = useConformanceStore();
@@ -195,6 +222,10 @@ export async function leaveConformance(next, addConformanceCreateCheckId, toPath
await handleDismiss(result.dismiss, conformanceStore, next, toPath, logOut);
}
}
/**
* Displays the "SAVE YOUR RULE?" confirmation dialog.
* @returns {Promise<Object>} The SweetAlert2 result object.
*/
async function showConfirmationDialog() {
return Swal.fire({
title: 'SAVE YOUR RULE?',
@@ -210,6 +241,12 @@ async function showConfirmationDialog() {
});
}
/**
* Handles the confirmed save action for conformance rules.
* @param {Object} conformanceStore - The conformance Pinia store.
* @param {Function} addConformanceCreateCheckId - API function to create check.
* @returns {Promise<void>}
*/
async function handleConfirmed(conformanceStore, addConformanceCreateCheckId) {
if (conformanceStore.conformanceFilterCreateCheckId || conformanceStore.conformanceLogCreateCheckId) {
await conformanceStore.updateConformance();
@@ -221,6 +258,15 @@ async function handleConfirmed(conformanceStore, addConformanceCreateCheckId) {
}
}
/**
* Handles dismiss actions (cancel or backdrop click) for conformance modals.
* @param {string} dismissType - The SweetAlert2 dismiss reason.
* @param {Object} conformanceStore - The conformance Pinia store.
* @param {Function} next - Vue Router next() guard callback.
* @param {string} toPath - The destination route path.
* @param {Function} [logOut] - Optional logout function.
* @returns {Promise<void>}
*/
async function handleDismiss(dismissType, conformanceStore, next, toPath, logOut) {
switch (dismissType) {
case 'cancel':
@@ -237,15 +283,23 @@ async function handleDismiss(dismissType, conformanceStore, next, toPath, logOut
}
}
/**
* Resets temporary conformance check IDs to null.
* @param {Object} conformanceStore - The conformance Pinia store.
*/
function resetTempCheckId(conformanceStore) {
conformanceStore.conformanceFilterTempCheckId = null;
conformanceStore.conformanceLogTempCheckId = null;
}
/**
* Upload failde First
* @param { string } failureType 後端檔案錯誤類型
* @param { string } failureMsg 後端檔案錯誤訊息
* Shows an error modal for the first stage of upload validation failure.
*
* @param {string} failureType - The error type from the backend (e.g.
* "encoding", "insufficient_columns", "empty", "name_suffix", "mime_type").
* @param {string} failureMsg - The raw error message from the backend.
* @param {number} [failureLoc] - The row number where the error occurred.
* @returns {Promise<void>}
*/
export async function uploadFailedFirst(failureType, failureMsg, failureLoc) {
// msg: 'not in UTF-8' | 'insufficient columns' | 'the csv file is empty' | 'the filename does not ends with .csv' | 'not a CSV file'
@@ -283,8 +337,12 @@ export async function uploadFailedFirst(failureType, failureMsg, failureLoc) {
})
};
/**
* Upload failde Second
* @param { array } detail 後端回傳的失敗訊息
* Shows an error modal for the second stage of upload validation failure,
* listing individual row-level errors.
*
* @param {Array<{type: string, loc: Array, input: string}>} detail -
* Array of error detail objects from the backend.
* @returns {Promise<void>}
*/
export async function uploadFailedSecond(detail) {
let srt = '';
@@ -341,8 +399,8 @@ export async function uploadFailedSecond(detail) {
srt = '';
};
/**
* Upload Success
* @param { string } value File's name
* Shows a timed success notification after a file upload completes.
* @returns {Promise<void>}
*/
export async function uploadSuccess() {
await Swal.fire({
@@ -355,8 +413,11 @@ export async function uploadSuccess() {
})
};
/**
* Confirm whether to upload the file
* @param { object } fetchData 後端 API POST data
* Shows a confirmation dialog before uploading a file. Proceeds with
* the upload only if the user confirms.
*
* @param {Object} fetchData - The upload request payload for the backend.
* @returns {Promise<void>}
*/
export async function uploadConfirm(fetchData) {
const filesStore = useFilesStore();
@@ -379,7 +440,8 @@ export async function uploadConfirm(fetchData) {
}
};
/**
* Upload loader
* Shows a non-dismissable loading spinner during file upload.
* @returns {Promise<void>}
*/
export async function uploadloader() {
await Swal.fire({
@@ -390,11 +452,13 @@ export async function uploadloader() {
})
};
/**
* Rename Modal
* @param { function } rename 後端 API
* @param { string } type File 檔案類型
* @param { number } id File ID
* @param { string } baseName 改名前的檔名
* Shows a modal dialog for renaming a file.
*
* @param {Function} rename - Backend API function to rename the file.
* @param {string} type - The file type (e.g. "log", "filter").
* @param {number} id - The file ID.
* @param {string} baseName - The current file name.
* @returns {Promise<void>}
*/
export async function renameModal(rename, type, id, baseName) {
const fileName = baseName;
@@ -434,11 +498,13 @@ export async function renameModal(rename, type, id, baseName) {
// 清空欄位 fileName = '';
}
/**
* Delete File
* @param { string } files 有關連的檔案
* @param { string } type File 檔案類型
* @param { number } id File ID
* @param { string } name 原本的檔案
* Shows a confirmation dialog for deleting a file and its dependents.
*
* @param {string} files - HTML string listing dependent files to be deleted.
* @param {string} type - The file type (e.g. "log", "filter").
* @param {number} id - The file ID.
* @param {string} name - The file name.
* @returns {Promise<void>}
*/
export async function deleteFileModal(files, type, id, name) {
const filesStore = useFilesStore();
@@ -471,7 +537,8 @@ export async function deleteFileModal(files, type, id, name) {
}
};
/**
* Delete Success
* Shows a timed success notification after file deletion.
* @returns {Promise<void>}
*/
export async function deleteSuccess() {
await Swal.fire({
@@ -484,9 +551,13 @@ export async function deleteSuccess() {
})
};
/**
* Really deleted information
* @param {string} files 被刪除的檔案 html
* @param {array} reallyDeleteData 被刪除的檔案 data未取得 recordId 而傳入
* Shows an informational modal about files deleted by other users,
* then records the deletions and refreshes the file list.
*
* @param {string} files - HTML string listing the deleted files.
* @param {Array<{id: number}>} reallyDeleteData - Array of deleted file
* objects with their IDs.
* @returns {Promise<void>}
*/
export async function reallyDeleteInformation(files, reallyDeleteData) {
const filesStore = useFilesStore();
@@ -513,8 +584,9 @@ export async function reallyDeleteInformation(files, reallyDeleteData) {
}
/**
* When user is leaving the acct mgmt page but hasn't finished editing,
* we jump out this alert modal to remind her.
* Shows a reminder modal when the user leaves the account management
* page with unsaved edits.
* @returns {Promise<void>}
*/
export async function leaveAccountManagementToRemind(){
const modalStore = useModalStore();

View File

@@ -1,15 +1,30 @@
// The Lucia project.
// Copyright 2023-2026 DSP, inc. All rights reserved.
// Authors:
// chiayin.kuo@dsp.im (chiayin), 2023/1/31
// imacat.yang@dsp.im (imacat), 2023/9/23
// cindy.chang@dsp.im (Cindy Chang), 2024/5/30
/** @module apiError Centralized API error handler with toast notifications. */
import { useLoadingStore } from '@/stores/loading';
import {useToast} from 'vue-toast-notification';
import 'vue-toast-notification/dist/theme-sugar.css';
// Delay loading and toast
/**
* Returns a promise that resolves after the specified milliseconds.
* @param {number} [s=0] - The delay in milliseconds.
* @returns {Promise<void>} A promise that resolves after the delay.
*/
const delay = (s = 0) => new Promise((resolve, reject) => setTimeout(resolve, s));
/**
* API catch error function.
* 401 errors are handled by the axios response interceptor in api/client.js.
* @param {object} error 後端 ERROR
* @param {string} toastMessage Toast 的提示文字
* Handles API errors by showing a loading spinner followed by a toast
* notification. 401 errors are handled by the axios response interceptor
* in api/client.js.
*
* @param {Object} error - The error object from the API call.
* @param {string} toastMessage - The message to display in the toast.
* @returns {Promise<void>}
*/
export default async function apiError(error, toastMessage) {
const loading = useLoadingStore();

View File

@@ -1,3 +1,14 @@
// The Lucia project.
// Copyright 2023-2026 DSP, inc. All rights reserved.
// Authors:
// chiayin.kuo@dsp.im (chiayin), 2023/1/31
// imacat.yang@dsp.im (imacat), 2023/9/23
// cindy.chang@dsp.im (Cindy Chang), 2024/5/30
/**
* @module cytoscapeMap Cytoscape.js process map rendering with
* interactive node/edge highlighting, tooltips, and position persistence.
*/
import cytoscape from 'cytoscape';
import spread from 'cytoscape-spread';
import dagre from 'cytoscape-dagre';
@@ -6,11 +17,20 @@ import cola from 'cytoscape-cola';
import tippy from 'tippy.js';
import 'tippy.js/dist/tippy.css';
import { useMapPathStore } from '@/stores/mapPathStore';
import { getTimeLabel } from '@/module/timeLabel.js'; // 時間格式轉換器
import { getTimeLabel } from '@/module/timeLabel.js';
import { useCytoscapeStore } from '@/stores/cytoscapeStore';
import { SAVE_KEY_NAME } from '@/constants/constants.js';
const composeFreqTypeText = (baseText, dataLayerOption, optionValue) => { //sonar-qube
/**
* Composes display text for frequency-type data layer values.
*
* @param {string} baseText - The base label text prefix.
* @param {string} dataLayerOption - The data layer option key
* (e.g. "rel_freq" for relative frequency).
* @param {number} optionValue - The numeric value to format.
* @returns {string} The formatted text with the value appended.
*/
const composeFreqTypeText = (baseText, dataLayerOption, optionValue) => {
let text = baseText;
const textInt = dataLayerOption === 'rel_freq' ? baseText + optionValue * 100 + "%" : baseText + optionValue;
const textFloat = dataLayerOption === 'rel_freq' ? baseText + (optionValue * 100).toFixed(2) + "%" : baseText + optionValue.toFixed(2);
@@ -26,18 +46,23 @@ cytoscape.use(fcose);
cytoscape.use(cola);
/**
* @param {object} mapData processMapData | bpmnData可選以上任一。
* mapData:{
* startId: 0,
* endId: 1,
* nodes: [],
* edges: []
* }
* @param {string} dataLayerType DataLayer's type
* @param {string} dataLayerOption DataLayer's options
* @param {string} curve Curve's type
* @param {string} graphId cytoscape's container
* @return {cytoscape.Core} cy
* Creates and configures a Cytoscape.js process map instance with
* interactive features including node/edge highlighting, tooltips,
* and position persistence via localStorage.
*
* @param {Object} mapData - The map data containing nodes and edges.
* @param {number} mapData.startId - The start node ID.
* @param {number} mapData.endId - The end node ID.
* @param {Array} mapData.nodes - Array of node data objects.
* @param {Array} mapData.edges - Array of edge data objects.
* @param {string} dataLayerType - The data layer type ("freq" or "duration").
* @param {string} dataLayerOption - The data layer option key
* (e.g. "abs_freq", "rel_freq", "rel_duration").
* @param {string} curveStyle - The edge curve style
* ("unbundled-bezier" or "taxi").
* @param {string} rank - The layout direction ("TB" or "LR").
* @param {HTMLElement} graphId - The DOM container element for Cytoscape.
* @returns {cytoscape.Core} The configured Cytoscape instance.
*/
export default function cytoscapeMap(mapData, dataLayerType, dataLayerOption, curveStyle, rank, graphId) {
// 設定每個 node, edges 的顏色與樣式

View File

@@ -1,3 +1,14 @@
// The Lucia project.
// Copyright 2023-2026 DSP, inc. All rights reserved.
// Authors:
// chiayin.kuo@dsp.im (chiayin), 2023/1/31
// imacat.yang@dsp.im (imacat), 2023/9/23
// cindy.chang@dsp.im (Cindy Chang), 2024/5/30
/**
* @module cytoscapeMapTrace Cytoscape.js process map rendering for
* individual trace visualization.
*/
import cytoscape from 'cytoscape';
import dagre from 'cytoscape-dagre';
import tippy from 'tippy.js';
@@ -6,19 +17,14 @@ import 'tippy.js/dist/tippy.css';
cytoscape.use( dagre );
/**
* draw processmap for trace
* @param {array} nodes array of an object, it contains
* data:{
* backgroundColor: string
* bordercolor: string
* height: number
* id: number
* label: string
* shape: string
* width: number
* }
* @param {array} edges it's similar to nodes
* @param {string} graphId graph Id
* Creates a Cytoscape.js instance for rendering a single trace's
* process map with left-to-right dagre layout and tooltips.
*
* @param {Array<Object>} nodes - Array of node data objects with
* backgroundColor, bordercolor, height, id, label, shape, and width.
* @param {Array<Object>} edges - Array of edge data objects.
* @param {HTMLElement} graphId - The DOM container element for Cytoscape.
* @returns {cytoscape.Core} The configured Cytoscape instance.
*/
export default function cytoscapeMapTrace(nodes, edges, graphId) {
// create Cytoscape

View File

@@ -1,25 +1,32 @@
// sonar-qube replace regex method
// The Lucia project.
// Copyright 2023-2026 DSP, inc. All rights reserved.
// Authors:
// chiayin.kuo@dsp.im (chiayin), 2023/1/31
// imacat.yang@dsp.im (imacat), 2023/9/23
// cindy.chang@dsp.im (Cindy Chang), 2024/5/30
/** @module numberLabel Number formatting with comma separators. */
/**
* Formats an integer string by inserting commas every three digits.
* @param {string} numberStr - A string of digits to format.
* @returns {string} The formatted string with commas (e.g. "1,000,000").
*/
const formatNumberWithCommas = (numberStr) => {
// 反轉字符串
let reversedStr = numberStr.split('').reverse().join('');
// 將反轉後的字符串每 3 個字符為一組進行分割
let groupedStr = reversedStr.match(/.{1,3}/g);
// 用逗號將這些組連接起來
let joinedStr = groupedStr.join(',');
// 再次反轉回原來的順序
let finalStr = joinedStr.split('').reverse().join('');
return finalStr;
}
/**
* 將數字轉換成帶有逗號格式(ex: 1000 -> 1,000)
* 也可以改用原生 JS 方法 `.toLocaleString('en-US')`
* @param {number} num 數字
* @returns
* Converts a number to a string with comma-separated thousands.
*
* Handles decimal numbers by formatting only the integer part.
*
* @param {number} num - The number to format.
* @returns {string} The formatted number string (e.g. "1,234.56").
*/
export default function numberLabel(num) {
let parts = num.toString().split('.');

View File

@@ -1,14 +1,27 @@
// The Lucia project.
// Copyright 2023-2026 DSP, inc. All rights reserved.
// Authors:
// chiayin.kuo@dsp.im (chiayin), 2023/1/31
// imacat.yang@dsp.im (imacat), 2023/9/23
// cindy.chang@dsp.im (Cindy Chang), 2024/5/30
/** @module setChartData Chart.js data transformation utilities. */
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
* Extends backend chart data with extrapolated boundary points for
* line charts. Prepends a calculated minimum and appends a calculated
* maximum data point based on linear extrapolation.
*
* @param {Array<{x: number, y: number}>} baseData - The 10 data points
* from the backend.
* @param {number} xMax - The maximum x-axis timestamp.
* @param {number} xMin - The minimum x-axis timestamp.
* @param {boolean} isPercent - Whether values are percentages (0-1 range).
* @param {number} yMax - The maximum y-axis value for clamping.
* @param {number} yMin - The minimum y-axis value for clamping.
* @returns {Array<{x: number, y: number}>} The extended data array
* with boundary points.
*/
export function setLineChartData(baseData, xMax, xMin, isPercent, yMax, yMin) {
// 將 baseData 轉換為包含 x 和 y 屬性的物件陣列
@@ -35,11 +48,14 @@ export function setLineChartData(baseData, xMax, xMin, isPercent, yMax, yMin) {
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)
* Extrapolates the Y-axis minimum boundary value using linear
* interpolation from the first two data points.
*
* @param {Array<{x: number, y: number}>} baseData - The base data points.
* @param {boolean} isPercent - Whether to clamp to 0-1 range.
* @param {number} yMin - The minimum allowed Y value.
* @param {number} yMax - The maximum allowed Y value.
* @returns {number} The extrapolated and clamped Y minimum value.
*/
function calculateYMin(baseData, isPercent, yMin, yMax) {
let a = 0;
@@ -51,11 +67,14 @@ function calculateYMin(baseData, isPercent, yMin, yMax) {
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)
* Extrapolates the Y-axis maximum boundary value using linear
* interpolation from the last two data points.
*
* @param {Array<{x: number, y: number}>} baseData - The base data points.
* @param {boolean} isPercent - Whether to clamp to 0-1 range.
* @param {number} yMin - The minimum allowed Y value.
* @param {number} yMax - The maximum allowed Y value.
* @returns {number} The extrapolated and clamped Y maximum value.
*/
function calculateYMax(baseData, isPercent, yMin, yMax) {
let ma = 9;
@@ -67,15 +86,14 @@ function calculateYMax(baseData, isPercent, yMin, yMax) {
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} 被限制在指定範圍內的值
* Clamps a value within a specified range. If isPercent is true, the
* value is clamped to [0, 1]; otherwise to [min, max].
*
* @param {number} value - The value to clamp.
* @param {boolean} isPercent - Whether to use the percentage range [0, 1].
* @param {number} min - The minimum bound (used when isPercent is false).
* @param {number} max - The maximum bound (used when isPercent is false).
* @returns {number} The clamped value.
*/
function clampValue(value, isPercent, min, max) {
if (isPercent) {
@@ -95,9 +113,13 @@ function clampValue(value, isPercent, min, max) {
return value;
};
/**
* 將後端的 chart data x 值轉換時間格式,長條圖
* @param {array} baseData 後端給的 10 個時間點
* @returns {array} 可直接套入 chart.js 的 data
* Converts backend chart data timestamps to formatted date strings
* for bar charts.
*
* @param {Array<{x: string, y: number}>} baseData - The data points from
* the backend with ISO timestamp x values.
* @returns {Array<{x: string, y: number}>} Data with x values formatted
* as "YYYY/M/D hh:mm:ss".
*/
export function setBarChartData(baseData) {
let data = baseData.map(i =>{
@@ -109,11 +131,13 @@ export function setBarChartData(baseData) {
return data
};
/**
* 將一段時間均分成多段的時間點
* @param {string} startTime 開始時間(ex: 434) 總秒數
* @param {string} endTime 結束時間(ex: 259065) 總秒數
* @param {number} amount 切分成多少段
* @returns {array} 均分成多段的時間 array
* Divides a time range into evenly spaced time points.
*
* @param {number} minTime - The start time in seconds.
* @param {number} maxTime - The end time in seconds.
* @param {number} amount - The number of time points to generate.
* @returns {Array<number>} An array of evenly spaced, rounded time
* values in seconds.
*/
export function timeRange(minTime, maxTime, amount) {
// x 軸(時間軸)的範圍是最大-最小,從最小值按照 index 累加間距到最大值
@@ -129,11 +153,13 @@ export function timeRange(minTime, maxTime, amount) {
return timeRange;
};
/**
* 將 y 軸的值分割成跟 x 時間軸一樣的等份,使用統計學 R 語言算出貝茲曲線攻勢
* @param {array} data 切分成多少段
* @param {number} yAmount 切分成多少段
* @param {number} yMax y 最大值
* @returns {array} 均分成多段的時間 array
* Generates smooth Y-axis values using cubic Bezier interpolation
* to produce evenly spaced ticks matching the X-axis divisions.
*
* @param {Array<{x: number, y: number}>} data - The source data points.
* @param {number} yAmount - The desired number of Y-axis ticks.
* @param {number} yMax - The maximum Y value (unused, kept for API).
* @returns {Array<number>} An array of interpolated Y values.
*/
export function yTimeRange(data, yAmount, yMax) {
const yRange = [];
@@ -171,10 +197,11 @@ export function yTimeRange(data, yAmount, yMax) {
return yRange;
};
/**
* 找出選擇的時間點的 index
* @param {array} data xVal
* @param {number} xValue x 值
* @returns {numver} x index
* Finds the index of the closest value in an array to the given target.
*
* @param {Array<number>} data - The array of values to search.
* @param {number} xValue - The target value to find the closest match for.
* @returns {number} The index of the closest value in the array.
*/
export function getXIndex(data, xValue) {
let closestIndex = xValue; // 假定第一个元素的索引是 0
@@ -192,9 +219,11 @@ export function getXIndex(data, xValue) {
return closestIndex;
};
/**
* 有 dhms 表達的時間單位
* @param {number} seconds 總秒數
* @returns {string}
* Formats a duration in seconds to a compact string with d/h/m/s units.
*
* @param {number} seconds - The total number of seconds.
* @returns {string|null} The formatted string (e.g. "2d3h15m30s"),
* or null if the input is NaN.
*/
export function formatTime(seconds) {
if(!isNaN(seconds)) {
@@ -221,9 +250,10 @@ export function formatTime(seconds) {
}
}
/**
* 只顯示最大的兩個單位
* @param {array} times 時間
* @returns {array}
* Truncates each time string to show only the two largest time units.
*
* @param {Array<string>} times - Array of duration strings (e.g. "2d3h15m30s").
* @returns {Array<string>} Array of truncated strings (e.g. "2d 3h").
*/
export function formatMaxTwo(times) {
const formattedTimes = [];

View File

@@ -1,19 +1,29 @@
// The Lucia project.
// Copyright 2023-2026 DSP, inc. All rights reserved.
// Authors:
// chiayin.kuo@dsp.im (chiayin), 2023/1/31
// imacat.yang@dsp.im (imacat), 2023/9/23
// cindy.chang@dsp.im (Cindy Chang), 2024/5/30
/** @module shortScaleNumber Short-scale number formatting. */
/**
* 數量單位轉換,輸出 k m b t 數值。
* @param {number} number 總秒數
* @returns {string}
* Converts a number to a short-scale abbreviated string with a suffix
* (k for thousands, m for millions, b for billions, t for trillions).
*
* Values are rounded up to one decimal place.
*
* @param {number} number - The number to abbreviate.
* @returns {string} The abbreviated string (e.g. "1.5k ", "2.3m ").
*/
export default function shortScaleNumber(number) {
const abbreviations = ["", "k", "m", "b", "t"];
let index = 0;
let num = number;
// 確保在轉換數字時,不會超出索引範圍。如果 index 已經達到了最後一個單位縮寫t那麼我們就不再進行轉換以免超出數組的範圍。
while (num >= 1000 && index < abbreviations.length - 1) {
num /= 1000;
index++;
}
// 使用 Math.ceil 來無條件進位
num = Math.ceil(num * 10) / 10;
return num + abbreviations[index] + " " ;
}

View File

@@ -1,43 +1,52 @@
// The Lucia project.
// Copyright 2023-2026 DSP, inc. All rights reserved.
// Authors:
// chiayin.kuo@dsp.im (chiayin), 2023/1/31
// imacat.yang@dsp.im (imacat), 2023/9/23
// cindy.chang@dsp.im (Cindy Chang), 2024/5/30
/** @module sortNumEngZhtw Sorting for mixed numeric/text data. */
/**
* 數字、英文、中文,排序
* @param {array} data List
* @returns
* Sorts an array of mixed numeric and text values in place.
*
* Numbers come first (ascending), followed by text sorted by
* Traditional Chinese locale (zh-Hant-TW).
*
* @param {Array<string|number>} data - The array to sort.
* @returns {Array<string|number>} The sorted array (same reference).
*/
export function sortNumEngZhtw(data) {
return data.sort((a, b) => {
// 檢查兩個值是否都是數字
const isANumber = !isNaN(parseFloat(a)) && isFinite(a);
const isBNumber = !isNaN(parseFloat(b)) && isFinite(b);
// 如果兩個值都是數字,直接比較大小
if (isANumber && isBNumber) return parseFloat(a) - parseFloat(b);
// 如果其中一個值是數字,將數字視為最小,排在前面
if (isANumber) return -1;
if (isBNumber) return 1;
// 其他情況下,使用 localeCompare 方法進行中文排序
return a.localeCompare(b, 'zh-Hant-TW', { sensitivity: 'accent' });
});
}
/**
* 數字、英文、中文,給 Filter Table 排序
* @param {string} a label
* @param {string} b label
* @returns
* Comparator function for sorting mixed numeric and text values.
*
* Suitable for use as an Array.sort() callback. Numbers sort before
* text, and text is sorted by Traditional Chinese locale.
*
* @param {string|number} a - First value to compare.
* @param {string|number} b - Second value to compare.
* @returns {number} Negative if a < b, positive if a > b, zero if equal.
*/
export function sortNumEngZhtwForFilter(a, b) {
// 檢查兩個值是否都是數字
const isANumber = !isNaN(parseFloat(a)) && isFinite(a);
const isBNumber = !isNaN(parseFloat(b)) && isFinite(b);
// 如果兩個值都是數字,直接比較大小
if (isANumber && isBNumber) return parseFloat(a) - parseFloat(b);
// 如果其中一個值是數字,將數字視為最小,排在前面
if (isANumber) return -1;
if (isBNumber) return 1;
// 其他情況下,使用 localeCompare 方法進行中文排序
return a.localeCompare(b, 'zh-Hant-TW', { sensitivity: 'accent' });
}

View File

@@ -1,11 +1,23 @@
// The Lucia project.
// Copyright 2023-2026 DSP, inc. All rights reserved.
// Authors:
// chiayin.kuo@dsp.im (chiayin), 2023/1/31
// imacat.yang@dsp.im (imacat), 2023/9/23
// cindy.chang@dsp.im (Cindy Chang), 2024/5/30
/** @module timeLabel Time formatting and chart axis tick utilities. */
import moment from 'moment';
/** @constant {number} Number of decimal places for formatted time values. */
const TOFIXED_DEICMAL = 1;
/**
* 若想要有等差的Y軸刻度則必須確保index是乘以一個共通的被乘數
* 此被乘數就是 stepSize
* @param {number} maxTimeInSecond
* @returns {object} {resultStepSize, unitToUse}
* Calculates the step size and time unit for evenly spaced Y-axis ticks.
*
* @param {number} maxTimeInSecond - The maximum time value in seconds.
* @param {number} numOfParts - The number of equal parts to divide into.
* @returns {{resultStepSize: number, unitToUse: string}} The step size
* and the time unit character ("d", "h", "m", or "s").
*/
export const getStepSizeOfYTicks = (maxTimeInSecond, numOfParts) => {
const {unitToUse, timeValue} = getTimeUnitAndValueToUse(maxTimeInSecond);
@@ -16,9 +28,12 @@ export const getStepSizeOfYTicks = (maxTimeInSecond, numOfParts) => {
}
/**
* Convert second to possibly day or hour or mins.
* @param {number} secondToDecide
* @returns {object} {unitToUse, timeValue} where timeValue is measured in unitToUse
* Determines the most appropriate time unit for a given number of seconds
* and converts the value to that unit.
*
* @param {number} secondToDecide - The time in seconds to evaluate.
* @returns {{unitToUse: string, timeValue: number}} The chosen unit
* character ("d", "h", "m", or "s") and the converted time value.
*/
const getTimeUnitAndValueToUse = (secondToDecide) => {
const day = 24 * 60 * 60;
@@ -52,12 +67,13 @@ const getTimeUnitAndValueToUse = (secondToDecide) => {
};
/**
* For the display of PrimeVue chart.
* According to the max time of the data given,
* split the Y axis into equal part as ticks.
* @param {number} stepSize stepSize of Y axis
* @param {number} index index of current visiting tick on Y axis
* @param {string} unitToUse time unit to display; "dhms" usually
* Formats a Y-axis tick value by multiplying the step size by the
* tick index and appending the time unit suffix.
*
* @param {number} stepSize - The step size between Y-axis ticks.
* @param {number} index - The zero-based index of the current tick.
* @param {string} unitToUse - The time unit suffix ("d", "h", "m", or "s").
* @returns {string} The formatted tick label (e.g. "2.5h").
*/
export function getYTicksByIndex(stepSize, index, unitToUse){
const rawStepsizeMultIndex = (stepSize * index).toString();
@@ -67,10 +83,12 @@ export function getYTicksByIndex(stepSize, index, unitToUse){
};
/**
* 將秒數轉換成帶有時間單位的格式
* @param {number} second 總秒數
* @param {number} fixedNumber 小數點後幾位
* @returns {string} 轉換完的格式(ex: 1 day, 6.8 hrs)
* Converts seconds to a human-readable time string with full unit names.
*
* @param {number} second - The total number of seconds.
* @param {number} [fixedNumber=0] - Number of decimal places.
* @returns {string} The formatted string (e.g. "1 days", "6.8 hrs",
* "30 mins", "5 sec").
*/
export function getTimeLabel(second, fixedNumber = 0) {
const day = 24 * 60 * 60;
@@ -99,15 +117,14 @@ export function getTimeLabel(second, fixedNumber = 0) {
}
/**
* 將秒數轉換成帶有縮寫時間單位的格式
* @param {number} second 總秒數
* @param {number} fixedNumber 小數點後幾位
* @returns {string} 轉換完的格式(ex: 1 d, 6.8 h)
* 如果秒數second大於等於一天86400 秒),返回以天為單位的時間,帶有 d 標誌。
* 如果秒數小於一天但大於等於一小時3600 秒),返回以小時為單位的時間,帶有 h 標誌。
* 如果秒數小於一小時但大於等於一分鐘60 秒),返回以分鐘為單位的時間,帶有 m 標誌。
* 如果秒數等於 0 秒,返回 "0s"。
* 如果秒數小於 60 秒但大於 0 秒,返回原始秒數,帶有 s 標誌。
* Converts seconds to a compact time string with abbreviated unit suffixes.
*
* Automatically selects the most appropriate unit: "d" for days,
* "h" for hours, "m" for minutes, or "s" for seconds.
*
* @param {number} second - The total number of seconds.
* @param {number} [fixedNumber=0] - Number of decimal places.
* @returns {string} The formatted string (e.g. "1d", "6.8h", "30m", "5s").
*/
export function simpleTimeLabel(second, fixedNumber = 0) {
const day = 24 * 60 * 60;
@@ -132,14 +149,16 @@ export function simpleTimeLabel(second, fixedNumber = 0) {
return second + "s";
}
/**
* 考慮到每包資料內的時間範圍有的大有的小,
* 需要根據不同時間範圍級別的資料刻劃不同的Y軸座標
* 因此需要根據最大的時間值來決定Y軸刻度怎麼切分。
* 跟隨最大值的時間單位,將秒數轉換成帶有縮寫時間單位的格式
* @param {number} second 要轉換單位的秒數
* @param {number} max 該 data 中的最大值
* @param {number} fixedNumber 小數點後幾位
* @returns {string} 轉換完的格式(ex: 1 d, 6.8 h)
* Converts seconds to a time string using the same unit as the maximum
* value in the dataset, ensuring consistent Y-axis labels.
*
* The unit is determined by the max value: days if max > 1 day,
* hours if max > 1 hour, minutes if max > 1 minute, else seconds.
*
* @param {number} second - The time value in seconds to format.
* @param {number} max - The maximum time value in the dataset (seconds).
* @param {number} [fixedNumber=0] - Number of decimal places.
* @returns {string} The formatted string (e.g. "1.5d", "6.8h").
*/
export function followTimeLabel(second, max, fixedNumber = 0) {
const day = 24 * 60 * 60;
@@ -192,15 +211,15 @@ export function followTimeLabel(second, max, fixedNumber = 0) {
/**
* Select an appropriate time format based on the time difference.
* 根據時間差距選擇適合的時間格式
* 舉例 月: 2022/06
* 舉例 日: 06/06
* 舉例 時: 03/05 12:00
* 舉例 分: 03/05 12:15
* 舉例 秒: 09:05:32
* @param {Array<number>} timestamps - An array of timestamps.
* @returns {string} - The suitable time format string.
* Selects an appropriate moment.js date format string based on the
* difference between the minimum and maximum timestamps.
*
* Returns "HH:mm:ss" for sub-minute ranges, "MM/DD HH:mm" for
* sub-day ranges, and "YYYY/MM/DD" for longer ranges.
*
* @param {number} minTimeStamp - The minimum timestamp in seconds.
* @param {number} maxTimeStamp - The maximum timestamp in seconds.
* @returns {string} A moment.js format string.
*/
export const setTimeStringFormatBaseOnTimeDifference = (minTimeStamp, maxTimeStamp) => {
const timeDifferenceInSeconds = maxTimeStamp - minTimeStamp;
@@ -222,11 +241,13 @@ export const setTimeStringFormatBaseOnTimeDifference = (minTimeStamp, maxTimeSta
};
/**
* Converts an array of Unix timestamps to formatted date strings based on the
* specified format for use as axis ticks.
* 根據指定的格式將 Unix 時間戳數組轉換為軸標籤的格式化日期字符串。
* @param {Array<number>} timeStampArr
* @param {string} timeFormat For example, 'MM/DD'
* Converts an array of Unix timestamps to formatted date strings for
* use as chart axis tick labels.
*
* @param {Array<number>} timeStampArr - Array of Unix timestamps.
* @param {string} timeFormat - A moment.js format string (e.g. "MM/DD").
* @returns {Array<string>|undefined} Array of formatted date strings,
* or undefined if timeStampArr is falsy.
*/
export const mapTimestampToAxisTicksByFormat = (timeStampArr, timeFormat) => {
if (timeStampArr) {