297 lines
11 KiB
TypeScript
297 lines
11 KiB
TypeScript
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 {
|
||
base = 0.1
|
||
}
|
||
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<string, (string | undefined)[]>()
|
||
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<number, number>()
|
||
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
|
||
}
|