import { number } from "vue-types" const dataProcessing = (arr: any[]) => { return arr .filter(item => typeof item === 'number' || (typeof item === 'string' && !isNaN(parseFloat(item)))) .map(item => (typeof item === 'number' ? item : parseFloat(item))) } const calculateValue = (o: number, value: number, num: number, isMin: boolean) => { if (value === 0) { return 0 } else if (value > 0 && Math.abs(value) < 1 && isMin == true) { return 0 } else if (value > -1 && value < 0 && isMin == false) { return 0 } let base if (Math.abs(o) >= 100) { base = 100 } else if (Math.abs(o) >= 10) { base = 10 } else if (Math.abs(o) >= 1) { base = 1 } else { const multiple = 1 / 0.1 // 先放大→向上取整→再缩小 return Math.ceil(Math.abs(o) * multiple) / multiple } let calculatedValue if (isMin) { if (value < 0) { calculatedValue = value + num * value } else { calculatedValue = value - num * value } } else { if (value < 0) { calculatedValue = value - num * value } else { calculatedValue = value + num * value } } if (base === 0.1) { return parseFloat(calculatedValue.toFixed(1)) } else if (isMin) { return Math.floor(calculatedValue / base) * base } else { return Math.ceil(calculatedValue / base) * base } } // 处理y轴最大最小值 export const yMethod = (arr: any) => { let num = 0.2 let numList = dataProcessing(arr) let maxValue = 0 let minValue = 0 let max = 0 let min = 0 maxValue = Math.max(...numList) minValue = Math.min(...numList) const o = maxValue - minValue min = calculateValue(o, minValue, num, true) max = calculateValue(o, maxValue, num, false) // if (-100 >= minValue) { // min = Math.floor((minValue + num * minValue) / 100) * 100 // } else if (-10 >= minValue && minValue > -100) { // min = Math.floor((minValue + num * minValue) / 10) * 10 // } else if (-1 >= minValue && minValue > -10) { // min = Math.floor(minValue + num * minValue) // } else if (0 > minValue && minValue > -1) { // min = parseFloat((minValue + num * minValue).toFixed(1)) // } else if (minValue == 0) { // min = 0 // } else if (0 < minValue && minValue < 1) { // min = parseFloat((minValue - num * minValue).toFixed(1)) // } else if (1 <= minValue && minValue < 10) { // min = Math.floor(minValue - num * minValue) // } else if (10 <= minValue && minValue < 100) { // min = Math.floor((minValue - num * minValue) / 10) * 10 // } else if (100 <= minValue) { // min = Math.floor((minValue - num * minValue) / 100) * 100 // } // if (-100 >= maxValue) { // max = Math.ceil((maxValue - num * maxValue) / 100) * 100 // } else if (-10 >= maxValue && maxValue > -100) { // max = Math.ceil((maxValue - num * maxValue) / 10) * 10 // } else if (-1 >= maxValue && maxValue > -10) { // max = Math.ceil(maxValue - num * maxValue) // } else if (0 > maxValue && maxValue > -1) { // max = parseFloat((maxValue - num * maxValue).toFixed(1)) // } else if (maxValue == 0) { // max = 0 // } else if (0 < maxValue && maxValue < 1) { // max = parseFloat((maxValue + num * maxValue).toFixed(1)) // } else if (1 <= maxValue && maxValue < 10) { // max = Math.ceil(maxValue + num * maxValue) // } else if (10 <= maxValue && maxValue < 100) { // max = Math.ceil((maxValue + num * maxValue) / 10) * 10 // } else if (100 <= maxValue) { // max = Math.ceil((maxValue + num * maxValue) / 100) * 100 // } // if (maxValue > 1000 || minValue < -1000) { // max = Math.ceil(maxValue / 100) * 100 // if (minValue == 0) { // min = 0 // } else { // min = Math.floor(minValue / 100) * 100 // } // } else if (maxValue < 60 && minValue > 40) { // max = 60 // min = 40 // } else if (maxValue == minValue && maxValue < 10 && minValue > 0) { // max = Math.ceil(maxValue / 10) * 10 // min = Math.floor(minValue / 10) * 10 // } else if (maxValue == minValue && maxValue != 0 && minValue != 0) { // max = Math.ceil(maxValue / 10 + 1) * 10 // min = Math.floor(minValue / 10 - 1) * 10 // } else { // max = Math.ceil(maxValue / 10) * 10 // min = Math.floor(minValue / 10) * 10 // } // if (maxValue > 0 && maxValue < 1) { // max = 1 // } else if (max == 0 && minValue > -1 && minValue < 0) { // min = -1 // } return [min, max] } /** * title['A相','B相',] * data[[1,2],[3,4]] */ // 导出csv文件 const convertToCSV = (title: object, data: any) => { let csv = '' // 添加列头 csv += ',' + title.join(',') + '\n' // 遍历数据并添加到CSV字符串中 data?.map(item => { csv += '\u200B' + item.join(',') + '\n' }) return csv } export const exportCSV = (title: object, data: any, filename: string) => { const csv = convertToCSV(title, data) const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }) const link = document.createElement('a') link.href = URL.createObjectURL(blob) link.download = filename link.click() // 释放URL对象 URL.revokeObjectURL(link.href) } /** * 补全时间序列数据中缺失的条目 * @param rawData 原始数据,格式为 [["时间字符串", "数值", "单位", "类型"], ...] * @returns 补全后的数据,缺失条目数值为 null */ export const completeTimeSeries = (rawData: string[][]): (string | null)[][] => { // 步骤1:校验原始数据并解析时间 if (rawData.length < 2) { console.warn('数据量不足2条,无法计算时间间隔,直接返回原始数据') return rawData.map(item => [...item]) } // 解析所有时间为Date对象,过滤无效时间并按时间排序 const validData = rawData .map(item => { // 确保至少有时间和数值字段 if (!item[0]) { return { time: new Date(0), item, isValid: false } } const time = new Date(item[0]) return { time, item, isValid: !isNaN(time.getTime()) } }) .filter(data => data.isValid) .sort((a, b) => a.time.getTime() - b.time.getTime()) // 确保数据按时间排序 .map(data => data.item) if (validData.length < 2) { throw new Error('有效时间数据不足2条,无法继续处理') } // 步骤2:计算时间间隔(分析前几条数据确定最可能的间隔) const intervals: number[] = [] // 分析前10条数据来确定间隔,避免单一间隔出错 const analyzeCount = Math.min(10, validData.length - 1) for (let i = 0; i < analyzeCount; i++) { const currentTime = new Date(validData[i][0]!).getTime() const nextTime = new Date(validData[i + 1][0]!).getTime() const interval = nextTime - currentTime if (interval > 0) { intervals.push(interval) } } // 取最常见的间隔作为标准间隔 const timeInterval = getMostFrequentValue(intervals) if (timeInterval <= 0) { throw new Error('无法确定有效的时间间隔') } // 步骤3:生成完整的时间序列范围(从第一条到最后一条) const startTime = new Date(validData[0][0]!).getTime() const endTime = new Date(validData[validData.length - 1][0]!).getTime() const completeTimes: Date[] = [] // 生成从 startTime 到 endTime 的所有间隔时间点 for (let time = startTime; time <= endTime; time += timeInterval) { completeTimes.push(new Date(time)) } // 步骤4:将原始数据转为时间映射表,使用精确的时间字符串匹配 const timeDataMap = new Map() validData.forEach(item => { // 使用原始时间字符串作为键,避免格式转换导致的匹配问题 if (item[0]) { timeDataMap.set(item[0], item) } }) // 提取模板数据(从第一条有效数据中提取单位和类型,处理可能的缺失) const template = validData[0] // 步骤5:对比补全数据,缺失条目数值为 null const completedData = completeTimes.map(time => { // 保持与原始数据相同的时间格式 const timeStr = formatTime(time) const existingItem = timeDataMap.get(timeStr) if (existingItem) { // 存在该时间,返回原始数据 return [...existingItem] } else { // 缺失该时间,数值设为 null,其他字段沿用第一个有效数据的格式 // 处理可能缺失的单位和类型字段 const result: (string | null | undefined)[] = [timeStr, '/'] // 仅在原始数据有单位字段时才添加 if (template.length > 2) { result.push(template[2]) } // 仅在原始数据有类型字段时才添加 if (template.length > 3) { result.push(template[3]) } return result } }) return completedData } /** * 格式化时间为 "YYYY-MM-DD HH:mm:ss" 格式 * @param date 日期对象 * @returns 格式化后的时间字符串 */ function formatTime(date: Date): string { const year = date.getFullYear() const month = String(date.getMonth() + 1).padStart(2, '0') const day = String(date.getDate()).padStart(2, '0') const hours = String(date.getHours()).padStart(2, '0') const minutes = String(date.getMinutes()).padStart(2, '0') const seconds = String(date.getSeconds()).padStart(2, '0') return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}` } /** * 获取数组中出现频率最高的值 * @param arr 数字数组 * @returns 出现频率最高的值 */ function getMostFrequentValue(arr: number[]): number { if (arr.length === 0) return 0 const frequencyMap = new Map() arr.forEach(num => { frequencyMap.set(num, (frequencyMap.get(num) || 0) + 1) }) let maxFrequency = 0 let mostFrequent = arr[0] frequencyMap.forEach((frequency, num) => { if (frequency > maxFrequency) { maxFrequency = frequency mostFrequent = num } }) return mostFrequent }