/** * 图表工具函数 * 提供数据格式化、颜色处理等通用功能 */ /** * 格式化金额 * @param amount 金额 * @param decimals 小数位数 */ export function formatMoney(amount: number, decimals: number = 2): string { return amount.toFixed(decimals).replace(/\B(?=(\d{3})+(?!\d))/g, ',') } /** * 格式化百分比 * @param value 值 * @param total 总数 * @param decimals 小数位数 */ export function formatPercentage(value: number, total: number, decimals: number = 1): string { if (total === 0) return '0%' return ((value / total) * 100).toFixed(decimals) + '%' } /** * 生成渐变色 * @param color 基础颜色 * @param alpha 透明度 */ export function colorWithAlpha(color: string, alpha: number): string { // 如果是 hex 颜色,转换为 rgba if (color.startsWith('#')) { const r = parseInt(color.slice(1, 3), 16) const g = parseInt(color.slice(3, 5), 16) const b = parseInt(color.slice(5, 7), 16) return `rgba(${r}, ${g}, ${b}, ${alpha})` } // 如果已经是 rgb/rgba,替换 alpha return color.replace(/rgba?\(([^)]+)\)/, (match, values) => { const parts = values.split(',').slice(0, 3) return `rgba(${parts.join(',')}, ${alpha})` }) } /** * 创建渐变背景(用于折线图填充) * @param ctx Canvas 上下文 * @param chartArea 图表区域 * @param color 颜色 */ export function createGradient(ctx: CanvasRenderingContext2D, chartArea: any, color: string) { const gradient = ctx.createLinearGradient(0, chartArea.bottom, 0, chartArea.top) gradient.addColorStop(0, colorWithAlpha(color, 0.0)) gradient.addColorStop(0.5, colorWithAlpha(color, 0.1)) gradient.addColorStop(1, colorWithAlpha(color, 0.3)) return gradient } /** * 截断文本(移动端长标签处理) * @param text 文本 * @param maxLength 最大长度 */ export function truncateText(text: string, maxLength: number = 12): string { if (text.length <= maxLength) return text return text.slice(0, maxLength) + '...' } /** * 合并小分类为 "Others" * @param data 数据数组 { label, value, color } * @param threshold 阈值百分比(默认 3%) * @param maxCategories 最大分类数(默认 8) */ export function mergeSmallCategories( data: Array<{ label: string; value: number; color?: string }>, threshold: number = 0.03, maxCategories: number = 8 ) { const total = data.reduce((sum, item) => sum + item.value, 0) // 按值降序排序 const sorted = [...data].sort((a, b) => b.value - a.value) // 分离大分类和小分类 const main: typeof data = [] const others: typeof data = [] sorted.forEach((item) => { const percentage = item.value / total if (main.length < maxCategories && percentage >= threshold) { main.push(item) } else { others.push(item) } }) // 如果有小分类,合并为 "Others" if (others.length > 0) { const othersValue = others.reduce((sum, item) => sum + item.value, 0) main.push({ label: '其他', value: othersValue, color: '#bbb' }) } return main } /** * 数据抽样(用于大数据量场景) * @param data 数据数组 * @param maxPoints 最大点数 */ export function decimateData(data: T[], maxPoints: number = 100): T[] { if (data.length <= maxPoints) return data const step = Math.ceil(data.length / maxPoints) return data.filter((_, index) => index % step === 0) } /** * 检测是否为移动端 */ export function isMobile(): boolean { return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) } /** * 根据屏幕宽度调整字体大小 */ export function getResponsiveFontSize(baseSize: number): number { const screenWidth = window.innerWidth if (screenWidth < 375) { return Math.max(baseSize - 2, 10) } return baseSize }