// ===== 全局状态 ===== let chartInstance = null; let parsedData = null; let seriesColors = []; let seriesOrder = []; // ===== 预设颜色方案 ===== const colorPalettes = { default: ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc'], dark: ['#4fc3f7', '#81c784', '#fff176', '#ff8a65', '#ba68c8', '#4dd0e1', '#ffab91', '#aed581', '#f48fb1'], macarons: ['#2ec7c9', '#b6a2de', '#5ab1ef', '#ffb980', '#d87a80', '#8d98b3', '#e5cf0d', '#97b552', '#95706d'], gradient: ['#7f7fd5', '#86a8e7', '#91eae4', '#ff6b6b', '#feca57', '#48dbfb', '#ff9ff3', '#54a0ff', '#5f27cd'], retro: ['#d4a5a5', '#95b9c7', '#f6e8c3', '#dfceb4', '#a4c3b5', '#c9b1ff', '#f5c7b8', '#b8d4e3', '#e8c8a0'] }; // ===== 示例数据 ===== const sampleDataSets = [ { name: '季度销售对比', data: `产品, Q1, Q2, Q3, Q4 手机, 1200, 1800, 2100, 2500 平板, 800, 950, 1100, 1300 笔记本, 600, 750, 900, 1050 耳机, 400, 520, 680, 800` }, { name: '年度增长趋势', data: `指标, 2020年, 2021年, 2022年, 2023年, 2024年 营收(万), 500, 680, 820, 1050, 1380 利润(万), 80, 120, 160, 230, 350 用户(千), 50, 85, 130, 200, 320` }, { name: '多指标对比(适合区域分割)', data: `月份, 方案A-效率, 方案A-成本, 方案B-效率, 方案B-成本 1月, 85, 120, 78, 135 2月, 88, 115, 82, 128 3月, 92, 108, 88, 120 4月, 90, 112, 95, 110 5月, 95, 105, 98, 105 6月, 98, 98, 102, 95` } ]; // ===== 初始化 ===== document.addEventListener('DOMContentLoaded', () => { // 默认加载第一个示例 document.getElementById('dataInput').value = sampleDataSets[0].data; generateChart(); }); // ===== 数据解析 ===== function parseData(rawText) { const lines = rawText.trim().split('\n').filter(l => l.trim()); if (lines.length < 2) { alert('数据至少需要包含表头和一行数据'); return null; } // 自动检测分隔符 let delimiter = ','; if (lines[0].includes('\t')) { delimiter = '\t'; } else if (lines[0].split('|').length > lines[0].split(',').length) { delimiter = '|'; } const rows = lines.map(line => { return line.split(delimiter).map(cell => cell.trim()); }); const headers = rows[0]; const categories = []; const seriesData = {}; // 第一列是横坐标,其余列是系列 for (let i = 1; i < headers.length; i++) { seriesData[headers[i]] = []; } for (let i = 1; i < rows.length; i++) { categories.push(rows[i][0]); for (let j = 1; j < rows[i].length && j < headers.length; j++) { const val = parseFloat(rows[i][j]); seriesData[headers[j]].push(isNaN(val) ? 0 : val); } } return { categories, seriesNames: headers.slice(1), seriesData }; } // ===== 生成图表 ===== function generateChart() { const rawText = document.getElementById('dataInput').value; if (!rawText.trim()) { alert('请输入数据'); return; } parsedData = parseData(rawText); if (!parsedData) return; // 初始化系列顺序和颜色 seriesOrder = parsedData.seriesNames.map((_, i) => i); const palette = colorPalettes[document.getElementById('themeStyle').value] || colorPalettes.default; seriesColors = parsedData.seriesNames.map((_, i) => palette[i % palette.length]); // 渲染系列配置 renderSeriesConfig(); // 初始化图表 initChart(); updateChart(); } // ===== 初始化图表实例 ===== function initChart() { const chartDom = document.getElementById('chartArea'); chartDom.innerHTML = ''; if (chartInstance) { chartInstance.dispose(); } chartInstance = echarts.init(chartDom, null, { renderer: 'canvas' }); // 响应式 window.addEventListener('resize', () => { chartInstance && chartInstance.resize(); }); } // ===== 更新图表 ===== function updateChart() { if (!parsedData || !chartInstance) return; const chartType = document.getElementById('chartType').value; const title = document.getElementById('chartTitle').value; const theme = document.getElementById('themeStyle').value; const showLegend = document.getElementById('showLegend').checked; const showGrid = document.getElementById('showGrid').checked; const showLabel = document.getElementById('showLabel').checked; const stackMode = document.getElementById('stackMode').checked; const smoothLine = document.getElementById('smoothLine').checked; const enableSplit = document.getElementById('enableSplit').checked; // 显示/隐藏分割配置 document.getElementById('splitConfig').style.display = enableSplit ? 'block' : 'none'; // 背景色 const bgColor = theme === 'dark' ? '#1a1a2e' : '#ffffff'; const textColor = theme === 'dark' ? '#e0e0e0' : '#333333'; const axisLineColor = theme === 'dark' ? '#444' : '#ddd'; // 按当前顺序构建系列 const palette = colorPalettes[theme] || colorPalettes.default; const series = seriesOrder.map((origIdx, displayIdx) => { const name = parsedData.seriesNames[origIdx]; const data = parsedData.seriesData[name]; const color = seriesColors[origIdx] || palette[origIdx % palette.length]; let type = chartType === 'bar-line' ? (displayIdx % 2 === 0 ? 'bar' : 'line') : chartType; const seriesItem = { name: name, type: type, data: [...data], itemStyle: { color: color }, emphasis: { focus: 'series', itemStyle: { shadowBlur: 10, shadowColor: 'rgba(0,0,0,0.3)' } } }; // 堆叠 if (stackMode) { seriesItem.stack = 'total'; } // 折线图特有 if (type === 'line') { seriesItem.smooth = smoothLine; seriesItem.lineStyle = { width: 3 }; seriesItem.symbolSize = 8; seriesItem.areaStyle = theme === 'gradient' ? { opacity: 0.15 } : undefined; } // 柱状图特有 if (type === 'bar') { seriesItem.barMaxWidth = 40; seriesItem.itemStyle.borderRadius = stackMode ? [0, 0, 0, 0] : [4, 4, 0, 0]; if (theme === 'gradient') { seriesItem.itemStyle.color = new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: color }, { offset: 1, color: adjustColor(color, 40) } ]); } } // 数据标签 if (showLabel) { seriesItem.label = { show: true, position: type === 'bar' ? 'top' : 'top', fontSize: 11, color: textColor, formatter: (params) => { if (params.value >= 10000) return (params.value / 10000).toFixed(1) + 'w'; if (params.value >= 1000) return (params.value / 1000).toFixed(1) + 'k'; return params.value; } }; } return seriesItem; }); // 区域分割 - 使用 graphic 组件精确画分割线在柱子间隙中 const graphicElements = []; if (enableSplit) { const splitIdx = parseInt(document.getElementById('splitIndex').value) || 3; const leftLabel = document.getElementById('leftLabel').value || '左侧'; const rightLabel = document.getElementById('rightLabel').value || '右侧'; const splitStyle = document.getElementById('splitStyle').value; if (splitIdx < parsedData.categories.length) { // 区域背景色用 markArea const splitLineColor = theme === 'dark' ? '#ff6b6b' : '#e74c3c'; if (series.length > 0) { series[0].markArea = { silent: true, data: [ [ { name: leftLabel, xAxis: parsedData.categories[0], itemStyle: { color: theme === 'dark' ? 'rgba(79,195,247,0.06)' : 'rgba(79,70,229,0.05)', borderWidth: 0 }, label: { show: true, position: 'insideTop', fontSize: 13, fontWeight: 'bold', color: theme === 'dark' ? '#4fc3f7' : '#4f46e5', offset: [0, 10] } }, { xAxis: parsedData.categories[splitIdx - 1] } ], [ { name: rightLabel, xAxis: parsedData.categories[splitIdx], itemStyle: { color: theme === 'dark' ? 'rgba(255,107,107,0.06)' : 'rgba(239,68,68,0.05)', borderWidth: 0 }, label: { show: true, position: 'insideTop', fontSize: 13, fontWeight: 'bold', color: theme === 'dark' ? '#ff6b6b' : '#ef4444', offset: [0, 10] } }, { xAxis: parsedData.categories[parsedData.categories.length - 1] } ] ] }; } // 用 graphic 组件在渲染后画分割线(在柱子间隙中) // 先存到全局,在 setOption 后通过 rendered 事件绘制 window._splitConfig = { splitIdx, splitLineColor, splitStyle, theme }; } else { window._splitConfig = null; } } else { window._splitConfig = null; } // 图表配置 const option = { backgroundColor: bgColor, title: title ? { text: title, left: 'center', top: 15, textStyle: { color: textColor, fontSize: 18, fontWeight: 600 } } : undefined, tooltip: { trigger: 'axis', backgroundColor: theme === 'dark' ? 'rgba(30,30,50,0.95)' : 'rgba(255,255,255,0.95)', borderColor: theme === 'dark' ? '#555' : '#eee', textStyle: { color: textColor }, axisPointer: { type: chartType === 'bar' ? 'shadow' : 'cross', crossStyle: { color: '#999' } }, formatter: function(params) { let html = `
📊
输入数据后点击"生成图表"
生成图表后可在此调整各系列的顺序和颜色
'; } // ===== 导出图表 ===== function exportChart(format) { if (!chartInstance) { alert('请先生成图表'); return; } if (format === 'png') { const url = chartInstance.getDataURL({ type: 'png', pixelRatio: 2, backgroundColor: document.getElementById('themeStyle').value === 'dark' ? '#1a1a2e' : '#fff' }); const a = document.createElement('a'); a.href = url; a.download = 'chart.png'; a.click(); } else if (format === 'svg') { // 重新用 SVG 渲染 const svgChart = echarts.init(document.createElement('div'), null, { renderer: 'svg' }); svgChart.setOption(chartInstance.getOption()); const url = svgChart.getDataURL({ type: 'svg', pixelRatio: 2 }); const a = document.createElement('a'); a.href = url; a.download = 'chart.svg'; a.click(); svgChart.dispose(); } }