// 图表实例,方便后续更新 window.chartInstance = null; // 注册Chart.js插件 if (Chart && Chart.register) { // 如果有ChartDataLabels插件,注册它 if (window.ChartDataLabels) { Chart.register(ChartDataLabels); } } // 生成图表的主函数 function createChart(data, settings) { // 获取Canvas元素 const canvas = document.getElementById('chart-canvas'); const ctx = canvas.getContext('2d'); // 如果已有图表,先销毁 if (window.chartInstance) { window.chartInstance.destroy(); } // 应用颜色方案 applyColorScheme(data, settings.colorScheme, settings.type); // 配置图表选项 const options = getChartOptions(settings); // 处理特殊图表类型 let type = settings.type; let chartData = {...data}; // 检查是否为首系列图表类型 const isFirstSeriesOnly = settings.type.includes(" (首系列)"); if (isFirstSeriesOnly) { // 提取真正的图表类型 type = settings.type.replace(" (首系列)", ""); // 只保留第一个数据系列 if (chartData.datasets.length > 1) { const firstDataset = chartData.datasets[0]; chartData.datasets = [{ ...firstDataset, label: firstDataset.label || '数据' }]; } } // 移除可能存在的旧堆叠设置 if (options.scales && options.scales.x) { delete options.scales.x.stacked; } if (options.scales && options.scales.y) { delete options.scales.y.stacked; } // 移除旧的填充设置和其他特殊属性 chartData.datasets.forEach(dataset => { delete dataset.fill; delete dataset.tension; delete dataset.stepped; delete dataset.borderDash; }); // 基本图表类型处理 switch(type) { // 柱状图系列 case 'horizontalBar': type = 'bar'; options.indexAxis = 'y'; break; case 'stackedBar': type = 'bar'; if (!options.scales) options.scales = {}; if (!options.scales.x) options.scales.x = {}; if (!options.scales.y) options.scales.y = {}; options.scales.x.stacked = true; options.scales.y.stacked = true; break; case 'groupedBar': type = 'bar'; // 分组柱状图是默认行为 break; case 'gradientBar': type = 'bar'; // 渐变效果在applyColorScheme函数中处理 break; case 'barWithError': type = 'bar'; // 添加误差线 chartData.datasets.forEach(dataset => { dataset.errorBars = { y: { plus: dataset.data.map(() => Math.random() * 5 + 2), minus: dataset.data.map(() => Math.random() * 5 + 2) } }; }); break; case 'rangeBar': type = 'bar'; // 转换数据为范围格式 chartData.datasets.forEach(dataset => { dataset.data = dataset.data.map(value => { const min = Math.max(0, value - Math.random() * value * 0.4); return [min, value]; }); }); break; // 线/面积图系列 case 'area': type = 'line'; chartData.datasets.forEach(dataset => { dataset.fill = true; }); break; case 'curvedLine': type = 'line'; chartData.datasets.forEach(dataset => { dataset.tension = 0.4; // 更平滑的曲线 }); break; case 'stepLine': type = 'line'; chartData.datasets.forEach(dataset => { dataset.stepped = true; }); break; case 'stackedArea': type = 'line'; chartData.datasets.forEach(dataset => { dataset.fill = true; }); if (!options.scales) options.scales = {}; if (!options.scales.y) options.scales.y = {}; options.scales.y.stacked = true; break; case 'streamgraph': type = 'line'; // 流图效果:堆叠面积图 + 居中对齐 chartData.datasets.forEach(dataset => { dataset.fill = true; }); if (!options.scales) options.scales = {}; if (!options.scales.y) options.scales.y = {}; options.scales.y.stacked = true; options.scales.y.offset = true; // 居中对齐堆叠 break; case 'timeline': type = 'line'; chartData.datasets.forEach(dataset => { dataset.stepped = 'before'; dataset.borderDash = [5, 5]; // 虚线效果 }); break; // 饼图/环形图系列 case 'halfPie': type = 'doughnut'; options.circumference = Math.PI; options.rotation = -Math.PI / 2; break; case 'nestedPie': type = 'doughnut'; // 嵌套效果通过多个饼图叠加实现,简化实现仅调整内外半径 if (chartData.datasets.length > 0) { chartData.datasets[0].radius = '70%'; chartData.datasets[0].weight = 0.7; } break; // 散点/气泡图系列 case 'scatter': chartData.datasets = transformScatterData(chartData.datasets); break; case 'bubble': chartData.datasets = transformBubbleData(chartData.datasets); type = 'bubble'; break; case 'scatterSmooth': chartData.datasets = transformScatterData(chartData.datasets); type = 'scatter'; // 添加趋势线 chartData.datasets.forEach(dataset => { const smoothedDataset = { ...dataset, type: 'line', data: [...dataset.data], pointRadius: 0, tension: 0.4, fill: false }; chartData.datasets.push(smoothedDataset); }); break; // 专业图表系列 case 'funnel': type = 'bar'; // 简化的漏斗图实现 options.indexAxis = 'y'; if (chartData.datasets.length > 0) { // 对数据进行排序 const sortedData = [...chartData.datasets[0].data].sort((a, b) => b - a); chartData.datasets[0].data = sortedData; // 确保Y轴反转 if (!options.scales) options.scales = {}; if (!options.scales.y) options.scales.y = {}; options.scales.y.reverse = true; } break; case 'gauge': type = 'doughnut'; // 简化的仪表盘实现 if (chartData.datasets.length > 0 && chartData.datasets[0].data.length > 0) { const value = chartData.datasets[0].data[0]; const max = Math.max(...chartData.datasets[0].data) * 1.2; const remainder = max - value; chartData.datasets[0].data = [value, remainder]; chartData.datasets[0].backgroundColor = ['#36A2EB', '#E0E0E0']; chartData.labels = ['Value', '']; options.circumference = Math.PI; options.rotation = -Math.PI; options.cutout = '70%'; } break; case 'boxplot': // 简化的箱线图实现(基于柱状图) type = 'bar'; // 转换数据为箱线图格式 chartData.datasets.forEach(dataset => { dataset.data = dataset.data.map(value => { const q1 = Math.max(0, value * 0.7); const median = value * 0.85; const q3 = value * 1.15; const min = Math.max(0, q1 - (median - q1)); const max = q3 + (q3 - median); return [min, q1, median, q3, max]; }); }); break; case 'waterfall': type = 'bar'; // 瀑布图实现 if (chartData.datasets.length > 0) { const data = chartData.datasets[0].data; let cumulative = 0; // 创建新的数据数组,包含每个点的起点和终点 const waterfallData = data.map((value, index) => { const start = cumulative; cumulative += value; return { start: start, end: cumulative, value: value }; }); // 转换为柱状图数据 chartData.datasets[0].data = waterfallData.map(d => d.end - d.start); // 添加起点数据集 chartData.datasets.push({ label: '起点', data: waterfallData.map(d => d.start), backgroundColor: 'rgba(0,0,0,0)', borderColor: 'rgba(0,0,0,0)', stack: 'waterfall' }); // 设置为堆叠柱状图 if (!options.scales) options.scales = {}; if (!options.scales.x) options.scales.x = {}; if (!options.scales.y) options.scales.y = {}; options.scales.x.stacked = true; options.scales.y.stacked = true; } break; case 'treemap': case 'sunburst': case 'sankey': case 'chord': case 'network': // 这些高级图表需要专门的库支持,这里简化为提示信息 type = 'bar'; if (chartData.datasets.length > 0) { // 显示一个提示信息 chartData.datasets = [{ label: `${settings.type}需要专门的图表库支持`, data: [100], backgroundColor: '#f8d7da' }]; chartData.labels = ['请尝试其他图表类型']; } break; } // 热力图特殊处理 if (type === 'heatmap') { // 热力图不是Chart.js的标准类型,需要使用插件或自定义渲染 // 简单实现一个基于颜色渐变的矩阵图 type = 'matrix'; renderHeatmap(ctx, chartData, options); return; } // 饼图、环形图和极地面积图特殊处理 (如果不是由其他类型转换而来的) if (['pie', 'doughnut', 'polarArea'].includes(type) && !['halfPie', 'nestedPie', 'gauge'].includes(settings.type) && !isFirstSeriesOnly) { // 如果有多个数据集,只取第一个 if (chartData.datasets.length > 1) { const firstDataset = chartData.datasets[0]; chartData.datasets = [{ ...firstDataset, label: undefined // 这些图表类型不需要数据集标签 }]; } } // 创建图表实例 window.chartInstance = new Chart(ctx, { type: type, data: chartData, options: options }); // 设置标记属性,表示图表已渲染,去除背景 canvas.setAttribute('data-chart-rendered', 'true'); return window.chartInstance; } // 辅助函数:获取当前图表实例 function getChartInstance() { return window.chartInstance; } // 辅助函数:设置当前图表实例 function setChartInstance(instance) { window.chartInstance = instance; } // 获取图表配置选项 function getChartOptions(settings) { const options = { responsive: true, maintainAspectRatio: false, plugins: { title: { display: !!settings.title, text: settings.title, font: { size: 18, weight: 'bold' }, padding: { top: 10, bottom: 20 } }, legend: { display: settings.legendPosition !== 'none', position: settings.legendPosition === 'none' ? 'top' : settings.legendPosition, labels: { usePointStyle: true, padding: 15, font: { size: 12 } } }, tooltip: { enabled: true, backgroundColor: 'rgba(0, 0, 0, 0.7)', titleFont: { size: 14 }, bodyFont: { size: 13 }, padding: 10, displayColors: true } }, animation: { duration: settings.animateChart ? 1000 : 0, easing: 'easeOutQuart' } }; // 检查是否为简单数据模式(只有一个数据集) const isSimpleData = settings.isSimpleData || (window.chartData && window.chartData.datasets && window.chartData.datasets.length === 1); // 如果是简单数据模式,隐藏图例 if (isSimpleData) { options.plugins.legend.display = false; } // 只有部分图表类型需要轴线配置 if (!['pie', 'doughnut', 'polarArea'].includes(settings.type.replace(" (首系列)", ""))) { // 兼容(首系列)后缀 options.scales = { x: { title: { display: !!settings.xAxisLabel, text: settings.xAxisLabel, font: { size: 14, weight: 'bold' }, padding: { top: 10 } }, grid: { display: settings.showGridLines, color: 'rgba(0, 0, 0, 0.1)' }, ticks: { font: { size: 12 } } }, y: { title: { display: !!settings.yAxisLabel, text: settings.yAxisLabel, font: { size: 14, weight: 'bold' }, padding: { bottom: 10 } }, grid: { display: settings.showGridLines, color: 'rgba(0, 0, 0, 0.1)' }, ticks: { font: { size: 12 }, beginAtZero: true } } }; // 水平柱状图X和Y轴配置需要互换 if (settings.type === 'horizontalBar') { const temp = options.scales.x; options.scales.x = options.scales.y; options.scales.y = temp; } } // 数据标签配置 if (settings.showDataLabels) { options.plugins.datalabels = { display: true, color: function(context) { const actualType = settings.type.replace(" (首系列)", ""); const dataset = context.dataset; // 首先检查数据集是否有自定义的datalabels配置 if (dataset.datalabels && dataset.datalabels.color) { const labelColors = dataset.datalabels.color; // 如果color是数组,则使用对应索引的颜色 if (Array.isArray(labelColors)) { return labelColors[context.dataIndex] || '#333333'; } // 如果color是单个颜色值 return labelColors; } // 如果没有自定义配置,则使用智能检测 // 为饼图和环形图使用对比色 if (['pie', 'doughnut', 'polarArea'].includes(actualType)) { // 获取背景色 const index = context.dataIndex; const backgroundColor = dataset.backgroundColor[index]; // 计算背景色的亮度 return isColorDark(backgroundColor) ? '#ffffff' : '#000000'; } else if (actualType === 'bar' || actualType === 'horizontalBar' || actualType === 'stackedBar' || actualType === 'gradientBar') { // 柱状图系列也需要对比色 let backgroundColor; // 背景色可能是数组或单个颜色 if (Array.isArray(dataset.backgroundColor)) { backgroundColor = dataset.backgroundColor[context.dataIndex]; } else { backgroundColor = dataset.backgroundColor; } return isColorDark(backgroundColor) ? '#ffffff' : '#333333'; } else { // 其他图表类型使用默认深色 return '#333333'; } }, align: function(context) { const dataset = context.dataset; // 使用数据集中的align配置(如果有的话) if (dataset.datalabels && dataset.datalabels.align) { return dataset.datalabels.align; } // 默认配置 const chartType = settings.type.replace(" (首系列)", ""); if (['line', 'area', 'scatter', 'bubble'].includes(chartType)) { return 'top'; } return 'center'; }, font: { weight: 'bold' }, formatter: function(value, context) { const actualType = settings.type.replace(" (首系列)", ""); // 饼图、环形图和极地面积图显示百分比 if (['pie', 'doughnut', 'polarArea'].includes(actualType)) { // 计算百分比 const dataset = context.chart.data.datasets[context.datasetIndex]; const total = dataset.data.reduce((total, value) => total + value, 0); const percentage = ((value / total) * 100).toFixed(1) + '%'; // 对于较小的扇区只显示百分比,否则显示值和百分比 const percent = value / total * 100; if (percent < 5) { return percentage; } else { return `${value} (${percentage})`; } } // 对散点图特殊处理 if (settings.type === 'scatter') { if (context && context.dataset && context.dataset.data && context.dataset.data[context.dataIndex] && typeof context.dataset.data[context.dataIndex].y !== 'undefined') { return context.dataset.data[context.dataIndex].y; } return ''; } return value; } }; } return options; } // 判断颜色是否为深色 function isColorDark(color) { // 处理rgba格式 if (color && color.startsWith('rgba')) { const parts = color.match(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)/); if (parts) { const r = parseInt(parts[1]); const g = parseInt(parts[2]); const b = parseInt(parts[3]); // 计算亮度 (根据人眼对RGB的敏感度加权) const brightness = (r * 0.299 + g * 0.587 + b * 0.114) / 255; return brightness < 0.7; // 亮度小于0.7认为是深色 } } // 处理rgb格式 if (color && color.startsWith('rgb(')) { const parts = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/); if (parts) { const r = parseInt(parts[1]); const g = parseInt(parts[2]); const b = parseInt(parts[3]); const brightness = (r * 0.299 + g * 0.587 + b * 0.114) / 255; return brightness < 0.7; } } // 处理十六进制格式 if (color && color.startsWith('#')) { color = color.replace('#', ''); const r = parseInt(color.length === 3 ? color.substring(0, 1).repeat(2) : color.substring(0, 2), 16); const g = parseInt(color.length === 3 ? color.substring(1, 2).repeat(2) : color.substring(2, 4), 16); const b = parseInt(color.length === 3 ? color.substring(2, 3).repeat(2) : color.substring(4, 6), 16); const brightness = (r * 0.299 + g * 0.587 + b * 0.114) / 255; return brightness < 0.7; } // 默认返回true,使用白色文本 return true; } // 应用颜色方案 function applyColorScheme(data, colorScheme, chartType) { // 定义颜色方案 - 全新设计,确保各个方案风格迥异 const colorSchemes = { default: [ '#4e73df', '#1cc88a', '#36b9cc', '#f6c23e', '#e74a3b', '#6f42c1', '#fd7e14', '#20c9a6', '#36b9cc', '#858796' ], pastel: [ '#FFB6C1', '#FFD700', '#98FB98', '#87CEFA', '#FFA07A', '#DDA0DD', '#FFDAB9', '#B0E0E6', '#F0E68C', '#E6E6FA' ], bright: [ '#FF1E1E', '#FFFF00', '#00FF00', '#00FFFF', '#0000FF', '#FF00FF', '#FF7F00', '#FF1493', '#00FA9A', '#7B68EE' ], cool: [ '#5F4B8B', '#42BFDD', '#00A7E1', '#00344B', '#143642', '#0F8B8D', '#4CB5F5', '#1D3557', '#A8DADC', '#457B9D' ], warm: [ '#FF7700', '#FF9E00', '#FFCF00', '#FFF400', '#E20000', '#D91A1A', '#A60000', '#FF5252', '#FF7B7B', '#FFBF69' ], corporate: [ '#003F5C', '#2F4B7C', '#665191', '#A05195', '#D45087', '#F95D6A', '#FF7C43', '#FFA600', '#004D40', '#00695C' ], contrast: [ '#000000', '#E63946', '#457B9D', '#F1C40F', '#2ECC71', '#9B59B6', '#1ABC9C', '#F39C12', '#D35400', '#7F8C8D' ], rainbow: [ '#FF0000', '#FF7F00', '#FFFF00', '#00FF00', '#0000FF', '#4B0082', '#9400D3', '#FF1493', '#00FFFF', '#FF00FF' ], earth: [ '#5D4037', '#795548', '#A1887F', '#4E342E', '#3E2723', '#33691E', '#558B2F', '#7CB342', '#8D6E63', '#6D4C41' ], ocean: [ '#006064', '#00838F', '#0097A7', '#00ACC1', '#00BCD4', '#26C6DA', '#4DD0E1', '#80DEEA', '#01579B', '#0277BD' ], vintage: [ '#8D8741', '#659DBD', '#DAAD86', '#BC986A', '#FBEEC1', '#605B56', '#837A75', '#9E8B8B', '#D8C3A5', '#E8DDCD' ] }; // 获取选定的颜色方案 const colors = colorSchemes[colorScheme] || colorSchemes.default; const actualChartType = chartType.replace(" (首系列)", ""); // 获取基础类型 // 为每个数据集应用颜色 data.datasets.forEach((dataset, index) => { const color = colors[index % colors.length]; // 设置不同图表类型的颜色 if (['pie', 'doughnut', 'polarArea', 'halfPie', 'nestedPie', 'gauge'].includes(actualChartType)) { // 这些图表类型需要为每个数据点设置不同颜色 // 对于gauge特殊处理,不使用这种方式 if (actualChartType === 'gauge' && dataset.backgroundColor) { // 保留gauge的特殊颜色设置 } else { dataset.backgroundColor = dataset.data.map((_, i) => colors[i % colors.length]); dataset.borderColor = 'white'; dataset.borderWidth = 1; // 为每个扇区添加对应的前景色(用于数据标签) dataset.datalabels = { color: dataset.backgroundColor.map(bgColor => isColorDark(bgColor) ? '#ffffff' : '#000000') }; } } else if (['line', 'area', 'stackedArea', 'curvedLine', 'stepLine', 'timeline', 'streamgraph'].includes(actualChartType)) { // 折线图和面积图样式 dataset.borderColor = color; // 根据图表类型调整透明度 let alpha = 0.1; // 默认折线图半透明 if (['area', 'stackedArea', 'streamgraph'].includes(actualChartType)) { alpha = 0.3; // 面积图相对更不透明 } dataset.backgroundColor = hexToRgba(color, alpha); dataset.pointBackgroundColor = color; dataset.pointBorderColor = '#fff'; dataset.pointHoverBackgroundColor = '#fff'; dataset.pointHoverBorderColor = color; // 特殊线型 if (actualChartType === 'curvedLine') { dataset.tension = 0.4; } else if (actualChartType === 'stepLine') { dataset.stepped = true; } else if (actualChartType === 'timeline') { dataset.stepped = 'before'; dataset.borderDash = [5, 5]; } else { dataset.tension = 0.3; } // 设置数据标签颜色 // 对于线图和面积图,标签通常放在点的上方,使用与线条相同的颜色 dataset.datalabels = { color: isColorDark(color) ? color : '#333333', align: 'top' }; } else if (actualChartType === 'radar') { // 雷达图样式 dataset.borderColor = color; dataset.backgroundColor = hexToRgba(color, 0.2); dataset.pointBackgroundColor = color; dataset.pointBorderColor = '#fff'; // 雷达图数据标签颜色 - 使用与边框相同的颜色 dataset.datalabels = { color: isColorDark(color) ? color : '#333333' }; } else if (['scatter', 'bubble', 'scatterSmooth'].includes(actualChartType)) { // 散点图样式 dataset.backgroundColor = color; dataset.borderColor = hexToRgba(color, 0.8); // 散点平滑图特殊处理 if (actualChartType === 'scatterSmooth' && dataset.type === 'line') { dataset.borderColor = color; dataset.backgroundColor = 'transparent'; } // 散点图数据标签颜色 dataset.datalabels = { color: isColorDark(color) ? '#ffffff' : '#333333', align: 'top' }; } else if (actualChartType === 'gradientBar') { // 渐变柱状图 const ctx = document.createElement('canvas').getContext('2d'); const gradient = ctx.createLinearGradient(0, 0, 0, 300); gradient.addColorStop(0, color); gradient.addColorStop(1, hexToRgba(color, 0.3)); dataset.backgroundColor = gradient; dataset.borderColor = color; dataset.borderWidth = 1; dataset.hoverBackgroundColor = color; // 渐变柱状图标签颜色 - 使用顶部颜色判断 dataset.datalabels = { color: isColorDark(color) ? '#ffffff' : '#333333' }; } else if (actualChartType === 'waterfall') { // 瀑布图特殊处理 if (dataset.label === '起点') { // 这是为瀑布图添加的起点数据集,保持透明 } else { const values = dataset.data; // 根据值的正负设置不同颜色 const positiveColor = '#36b9cc'; const negativeColor = '#e74a3b'; dataset.backgroundColor = values.map(value => value >= 0 ? hexToRgba(positiveColor, 0.7) : hexToRgba(negativeColor, 0.7) ); dataset.borderColor = values.map(value => value >= 0 ? positiveColor : negativeColor ); dataset.borderWidth = 1; // 瀑布图数据标签颜色 - 根据每个柱子的背景色决定 dataset.datalabels = { color: values.map(value => value >= 0 ? (isColorDark(positiveColor) ? '#ffffff' : '#333333') : (isColorDark(negativeColor) ? '#ffffff' : '#333333') ) }; } } else if (actualChartType === 'funnel') { // 漏斗图特殊处理 - 使用渐变颜色 const data = dataset.data; if (data.length) { dataset.backgroundColor = data.map((_, i) => { const ratio = 1 - (i / data.length); // 1 到 0 return hexToRgba(color, 0.5 + ratio * 0.5); // 透明度从1到0.5 }); dataset.borderColor = color; dataset.borderWidth = 1; // 漏斗图数据标签颜色 - 根据每个部分的背景色决定 dataset.datalabels = { color: dataset.backgroundColor.map(bgColor => isColorDark(bgColor) ? '#ffffff' : '#333333') }; } } else { // 默认样式(用于柱状图等) dataset.backgroundColor = hexToRgba(color, 0.7); dataset.borderColor = color; dataset.borderWidth = 1; dataset.hoverBackgroundColor = color; // 默认数据标签颜色 - 根据背景色决定 dataset.datalabels = { color: isColorDark(dataset.backgroundColor) ? '#ffffff' : '#333333' }; } }); } // 将十六进制颜色转换为rgba格式 function hexToRgba(hex, alpha) { // 移除井号 hex = hex.replace('#', ''); // 解析RGB值 const r = parseInt(hex.length === 3 ? hex.substring(0, 1).repeat(2) : hex.substring(0, 2), 16); const g = parseInt(hex.length === 3 ? hex.substring(1, 2).repeat(2) : hex.substring(2, 4), 16); const b = parseInt(hex.length === 3 ? hex.substring(2, 3).repeat(2) : hex.substring(4, 6), 16); // 返回rgba字符串 return `rgba(${r}, ${g}, ${b}, ${alpha})`; } // 渲染热力图(自定义实现) function renderHeatmap(ctx, data, options) { // 清除Canvas ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // 设置尺寸和边距 const margin = { top: 50, right: 30, bottom: 50, left: 60 }; const width = ctx.canvas.width - margin.left - margin.right; const height = ctx.canvas.height - margin.top - margin.bottom; // 获取数据 const rows = data.labels; const columns = data.datasets.map(dataset => dataset.label); // 创建值矩阵 const matrix = []; rows.forEach((_, rowIndex) => { const row = []; data.datasets.forEach(dataset => { row.push(dataset.data[rowIndex]); }); matrix.push(row); }); // 找出最大值和最小值 const allValues = matrix.flat(); const min = Math.min(...allValues); const max = Math.max(...allValues); // 绘制标题 if (options.plugins && options.plugins.title && options.plugins.title.display) { ctx.textAlign = 'center'; ctx.font = '18px Arial'; ctx.fillStyle = '#333'; ctx.fillText(options.plugins.title.text, ctx.canvas.width / 2, 25); } // 绘制单元格和标签 const cellWidth = width / columns.length; const cellHeight = height / rows.length; // 行标签(Y轴) ctx.textAlign = 'right'; ctx.textBaseline = 'middle'; ctx.font = '12px Arial'; ctx.fillStyle = '#666'; rows.forEach((label, i) => { const y = margin.top + i * cellHeight + cellHeight / 2; ctx.fillText(label, margin.left - 10, y); }); // 列标签(X轴) ctx.textAlign = 'center'; ctx.textBaseline = 'top'; columns.forEach((label, i) => { const x = margin.left + i * cellWidth + cellWidth / 2; ctx.fillText(label, x, margin.top + height + 10); }); // 绘制热力图单元格 matrix.forEach((row, i) => { row.forEach((value, j) => { // 归一化值 (0-1) const normalizedValue = (value - min) / (max - min || 1); // 计算颜色(红-黄-绿渐变) const color = getHeatmapColor(normalizedValue); // 绘制单元格 const x = margin.left + j * cellWidth; const y = margin.top + i * cellHeight; ctx.fillStyle = color; ctx.fillRect(x, y, cellWidth, cellHeight); // 添加值标签,根据背景色的亮度自动选择标签颜色 const brightness = getColorBrightness(color); ctx.fillStyle = brightness < 0.7 ? 'white' : 'black'; // 亮度阈值为0.7 ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.font = 'bold 12px Arial'; ctx.fillText(value, x + cellWidth / 2, y + cellHeight / 2); }); }); // 绘制坐标轴 ctx.strokeStyle = '#ddd'; ctx.lineWidth = 1; // X轴 ctx.beginPath(); ctx.moveTo(margin.left, margin.top + height); ctx.lineTo(margin.left + width, margin.top + height); ctx.stroke(); // Y轴 ctx.beginPath(); ctx.moveTo(margin.left, margin.top); ctx.lineTo(margin.left, margin.top + height); ctx.stroke(); } // 获取颜色亮度 function getColorBrightness(color) { // 处理rgb格式 if (color.startsWith('rgb(')) { const parts = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/); if (parts) { const r = parseInt(parts[1]); const g = parseInt(parts[2]); const b = parseInt(parts[3]); // 计算亮度 (根据人眼对RGB的敏感度加权) return (r * 0.299 + g * 0.587 + b * 0.114) / 255; } } // 默认返回0.5 return 0.5; } // 获取热力图颜色 function getHeatmapColor(value) { // 红-黄-绿渐变 const r = value < 0.5 ? 255 : Math.round(255 * (1 - 2 * (value - 0.5))); const g = value < 0.5 ? Math.round(255 * (2 * value)) : 255; const b = 0; return `rgb(${r}, ${g}, ${b})`; } // 注册Chart.js插件以支持数据标签 Chart.register({ id: 'datalabels', beforeDraw: (chart) => { const ctx = chart.ctx; const options = chart.options.plugins.datalabels; if (!options || !options.display) { return; } chart.data.datasets.forEach((dataset, datasetIndex) => { const meta = chart.getDatasetMeta(datasetIndex); meta.data.forEach((element, index) => { // 获取值 let value = dataset.data[index]; if (typeof value === 'object' && value !== null) { // 散点图等复杂数据结构 value = value.y; } // 获取位置 const { x, y } = element.getCenterPoint(); // 确定文本颜色 let fillColor; if (typeof options.color === 'function') { fillColor = options.color({ datasetIndex, index, dataset, dataIndex: index, chart: chart }); } else { fillColor = options.color || '#666'; } ctx.fillStyle = fillColor; // 设置字体 ctx.font = options.font.weight + ' 12px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; // 格式化值 let text = typeof options.formatter === 'function' ? options.formatter(value, { datasetIndex, index, dataset, dataIndex: index, chart: chart }) : value; // 绘制文本 ctx.fillText(text, x, y - 15); }); }); } }); // 辅助函数:转换散点图数据 function transformScatterData(datasets) { return datasets.map(dataset => { if (!dataset.data || !Array.isArray(dataset.data)) { return { ...dataset, data: [] }; } return { ...dataset, data: dataset.data.map((value, index) => { // 确保value是一个有效的数值 const y = parseFloat(value); if (isNaN(y)) { return { x: index + 1, y: 0 }; } return { x: index + 1, y: y }; }) }; }); } // 辅助函数:转换气泡图数据 function transformBubbleData(datasets) { return datasets.map(dataset => { if (!dataset.data || !Array.isArray(dataset.data)) { return { ...dataset, data: [] }; } return { ...dataset, data: dataset.data.map((value, index) => { // 确保value是一个有效的数值 const y = parseFloat(value); if (isNaN(y)) { return { x: index + 1, y: 0, r: 5 }; } // 气泡大小与值成比例 const r = Math.max(5, Math.min(20, y / 10)); return { x: index + 1, y: y, r: r }; }) }; }); } /** * 初始化图表类型预览画廊 */ function initChartTypeGallery() { // 获取所有图表类型预览项 const chartTypeItems = document.querySelectorAll('.chart-type-item'); // 获取图表类型选择下拉框 const chartTypeSelect = document.getElementById('chart-type'); // 为每个预览项添加点击事件 chartTypeItems.forEach(item => { item.addEventListener('click', function() { // 获取图表类型值 const chartType = this.getAttribute('data-chart-type'); // 设置下拉框的值 chartTypeSelect.value = chartType; // 触发change事件以更新图表 const event = new Event('change'); chartTypeSelect.dispatchEvent(event); // 更新活动状态 chartTypeItems.forEach(item => item.classList.remove('active')); this.classList.add('active'); // 如果已经有图表实例,立即生成图表 if (window.chartInstance) { // 假设generateChart是全局函数 if (typeof window.generateChart === 'function') { window.generateChart(); } } }); }); // 初始化时设置当前选中的图表类型为活动状态 const currentChartType = chartTypeSelect.value; const activeItem = document.querySelector(`.chart-type-item[data-chart-type="${currentChartType}"]`); if (activeItem) { activeItem.classList.add('active'); } // 当下拉框选择变化时,同步更新活动预览项 chartTypeSelect.addEventListener('change', function() { const selectedType = this.value; chartTypeItems.forEach(item => { if (item.getAttribute('data-chart-type') === selectedType) { item.classList.add('active'); } else { item.classList.remove('active'); } }); }); }