main.js 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712
  1. document.addEventListener('DOMContentLoaded', function() {
  2. // 获取DOM元素
  3. const simpleDataContainer = document.getElementById('simple-data-container');
  4. const seriesDataContainer = document.getElementById('series-data-container');
  5. const csvDataContainer = document.getElementById('csv-data-container');
  6. const generateBtn = document.getElementById('generate-btn');
  7. const sampleDataBtn = document.getElementById('sample-data-btn');
  8. const exportPngBtn = document.getElementById('export-png-btn');
  9. const exportJpgBtn = document.getElementById('export-jpg-btn');
  10. const copyImgBtn = document.getElementById('copy-img-btn');
  11. const chartTypeSelect = document.getElementById('chart-type');
  12. const manualFormatSelect = document.getElementById('manual-format');
  13. const fileUploadInput = document.getElementById('file-upload');
  14. const manualFormatContainer = document.getElementById('manual-format-container');
  15. const manualInputContainers = [simpleDataContainer, seriesDataContainer, csvDataContainer];
  16. // 初始化显示状态
  17. const initialMethod = document.querySelector('input[name="data-input-method"]:checked').value;
  18. toggleManualInputs(initialMethod === 'manual');
  19. fileUploadInput.parentElement.style.display = initialMethod === 'upload-csv' ? 'block' : 'none';
  20. // 初始化图表类型画廊
  21. initChartTypeGallery();
  22. function toggleManualInputs(show) {
  23. manualFormatContainer.style.display = show ? 'block' : 'none';
  24. const selectedFormat = manualFormatSelect.value;
  25. manualInputContainers.forEach(container => {
  26. const containerId = container.id.split('-')[0]; // 'simple', 'series', 'csv'
  27. container.style.display = (show && containerId === selectedFormat) ? 'block' : 'none';
  28. });
  29. }
  30. // 初始化时调用updateChartTypeOptions函数
  31. // 无论当前选择的是什么输入方式,都初始化图表类型
  32. // 默认使用'series'格式以显示最多的图表类型选项
  33. updateChartTypeOptions('series');
  34. // 监听数据输入方式切换
  35. document.querySelectorAll('input[name="data-input-method"]').forEach(radio => {
  36. radio.addEventListener('change', function() {
  37. const method = this.value;
  38. toggleManualInputs(method === 'manual');
  39. fileUploadInput.parentElement.style.display = method === 'upload-csv' ? 'block' : 'none';
  40. // 在切换到"上传Excel/CSV"时,更新图表类型选项为多系列数据
  41. if (method === 'upload-csv') {
  42. updateChartTypeOptions('series');
  43. } else if (method === 'manual') {
  44. // 切换回"手动录入"时,根据当前选择的格式更新图表类型选项
  45. updateChartTypeOptions(manualFormatSelect.value);
  46. uploadedData = null;
  47. fileUploadInput.value = ''; // 清空文件选择
  48. }
  49. });
  50. });
  51. // 监听手动格式选择变化
  52. manualFormatSelect.addEventListener('change', function() {
  53. const format = this.value;
  54. manualInputContainers.forEach(container => {
  55. const containerId = container.id.split('-')[0];
  56. container.style.display = (containerId === format) ? 'block' : 'none';
  57. });
  58. // 更新图表类型选项
  59. updateChartTypeOptions(format);
  60. });
  61. // 文件上传处理
  62. fileUploadInput.addEventListener('change', function(event) {
  63. const file = event.target.files[0];
  64. if (!file) {
  65. uploadedData = null;
  66. return;
  67. }
  68. const reader = new FileReader();
  69. reader.onload = function(e) {
  70. try {
  71. const data = new Uint8Array(e.target.result);
  72. const workbook = XLSX.read(data, { type: 'array' });
  73. const firstSheetName = workbook.SheetNames[0];
  74. const worksheet = workbook.Sheets[firstSheetName];
  75. const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
  76. uploadedData = parseExcelData(jsonData);
  77. showNotification('文件上传成功,可以点击"生成图表"');
  78. // 上传Excel文件后,更新图表类型选项为多系列数据类型
  79. updateChartTypeOptions('series');
  80. } catch (error) {
  81. showNotification('文件解析失败: ' + error.message, true);
  82. uploadedData = null;
  83. fileUploadInput.value = ''; // 清空文件选择
  84. }
  85. };
  86. reader.onerror = function() {
  87. showNotification('文件读取失败', true);
  88. uploadedData = null;
  89. fileUploadInput.value = ''; // 清空文件选择
  90. };
  91. reader.readAsArrayBuffer(file);
  92. });
  93. // 生成图表按钮点击事件 (修改为独立的函数)
  94. function generateChart() {
  95. try {
  96. console.log('开始生成图表...');
  97. let parsedData;
  98. const method = document.querySelector('input[name="data-input-method"]:checked').value;
  99. console.log('数据输入方式:', method);
  100. if (method === 'upload-csv' && uploadedData) {
  101. parsedData = uploadedData;
  102. console.log('使用上传的数据');
  103. } else if (method === 'manual') {
  104. parsedData = parseInputData(); // 使用现有的手动数据解析函数
  105. console.log('使用手动输入的数据');
  106. } else if (method === 'upload-csv' && !uploadedData) {
  107. throw new Error('请先上传文件');
  108. } else {
  109. throw new Error('请选择有效的数据输入方式并提供数据');
  110. }
  111. console.log('解析后的数据:', parsedData);
  112. if (!parsedData ||
  113. (parsedData.labels && parsedData.labels.length === 0) ||
  114. (parsedData.datasets && parsedData.datasets.length === 0)) {
  115. throw new Error('无法解析数据或数据为空');
  116. }
  117. // 保存数据到全局变量,方便其他函数访问
  118. window.chartData = parsedData;
  119. const chartSettings = getChartSettings();
  120. // 将简单数据标记添加到设置中
  121. if (parsedData.isSimpleData) {
  122. chartSettings.isSimpleData = true;
  123. }
  124. console.log('图表设置:', chartSettings);
  125. // 调用chart-generator.js中的createChart函数
  126. if (typeof createChart !== 'function') {
  127. throw new Error('createChart函数未定义,请确保chart-generator.js正确加载');
  128. }
  129. createChart(parsedData, chartSettings);
  130. console.log('图表生成成功');
  131. exportPngBtn.disabled = false;
  132. exportJpgBtn.disabled = false;
  133. copyImgBtn.disabled = false;
  134. } catch (error) {
  135. console.error('生成图表时出错:', error);
  136. showNotification(error.message, true);
  137. }
  138. }
  139. // 将generateChart函数暴露为全局函数
  140. window.generateChart = generateChart;
  141. generateBtn.addEventListener('click', generateChart);
  142. // 监听图表设置的变化事件,实时更新图表 (仅在有图表时)
  143. ['chart-title', 'x-axis-label', 'y-axis-label', 'color-scheme', 'legend-position'].forEach(id => {
  144. document.getElementById(id).addEventListener('input', function() {
  145. const instance = getChartInstance();
  146. if (instance) { // 检查是否有图表实例
  147. generateChart(); // 重新生成图表以应用设置
  148. }
  149. });
  150. });
  151. // 初始化图表类型画廊
  152. function initChartTypeGallery() {
  153. console.log('初始化图表类型预览画廊...');
  154. // 获取所有图表类型预览项
  155. const chartTypeItems = document.querySelectorAll('.chart-type-item');
  156. console.log(`找到${chartTypeItems.length}个图表类型预览项`);
  157. // 为每个预览项添加点击事件
  158. chartTypeItems.forEach(item => {
  159. item.addEventListener('click', function() {
  160. const chartType = this.getAttribute('data-chart-type');
  161. console.log('选择了图表类型:', chartType);
  162. // 更新活动状态
  163. chartTypeItems.forEach(item => item.classList.remove('active'));
  164. this.classList.add('active');
  165. // 无论是否有图表实例都应该重新生成图表
  166. // 删除之前的检查条件,始终调用generateChart
  167. generateChart();
  168. });
  169. });
  170. // 初始设置默认图表类型为活动状态
  171. const defaultChartType = "bar"; // 默认为柱状图
  172. const activeItem = document.querySelector(`.chart-type-item[data-chart-type="${defaultChartType}"]`);
  173. if (activeItem) {
  174. activeItem.classList.add('active');
  175. }
  176. }
  177. // 加载样例数据
  178. sampleDataBtn.addEventListener('click', function() {
  179. // 确保选中"手动录入"选项
  180. const manualRadio = document.querySelector('input[name="data-input-method"][value="manual"]');
  181. if (manualRadio && !manualRadio.checked) {
  182. manualRadio.checked = true;
  183. // 触发change事件以显示相关的输入控件
  184. manualRadio.dispatchEvent(new Event('change'));
  185. }
  186. const currentFormat = manualFormatSelect.value;
  187. switch(currentFormat) {
  188. case 'simple':
  189. document.getElementById('data-input').value =
  190. '智能手机,2458\n平板电脑,1678\n笔记本电脑,1892\n智能手表,986\n耳机,1342';
  191. document.getElementById('chart-title').value = '2023年电子产品销量(万台)';
  192. document.getElementById('x-axis-label').value = '产品类别';
  193. document.getElementById('y-axis-label').value = '销量(万台)';
  194. break;
  195. case 'series':
  196. document.getElementById('series-data-input').value =
  197. '第一季度,2458,1678,1892,986,1342\n第二季度,2612,1524,1953,1104,1587\n第三季度,2845,1701,2135,1287,1643\n第四季度,3256,1835,2278,1452,1821';
  198. document.getElementById('series-labels').value =
  199. '智能手机,平板电脑,笔记本电脑,智能手表,耳机';
  200. document.getElementById('chart-title').value = '2023年电子产品季度销量(万台)';
  201. document.getElementById('x-axis-label').value = '产品类别';
  202. document.getElementById('y-axis-label').value = '销量(万台)';
  203. break;
  204. case 'csv':
  205. document.getElementById('csv-data-input').value =
  206. '品牌,2021年,2022年,2023年\n华为,786.5,845.2,921.6\n小米,651.2,712.8,768.3\n苹果,598.7,642.1,724.5\n三星,542.3,575.8,612.4\nOPPO,487.6,524.3,547.8\nvivo,452.8,501.7,532.9';
  207. document.getElementById('chart-title').value = '国内智能手机品牌销量趋势(万台)';
  208. document.getElementById('x-axis-label').value = '品牌';
  209. document.getElementById('y-axis-label').value = '销量(万台)';
  210. break;
  211. }
  212. // 提示用户下一步操作
  213. showNotification('已加载样例数据,点击"生成图表"查看效果');
  214. });
  215. // 显示通知
  216. function showNotification(message, isError = false) {
  217. // 移除现有通知
  218. const existingNotification = document.querySelector('.notification');
  219. if (existingNotification) {
  220. existingNotification.remove();
  221. }
  222. // 创建新通知
  223. const notification = document.createElement('div');
  224. notification.className = 'notification' + (isError ? ' error' : '');
  225. notification.textContent = message;
  226. // 添加到文档
  227. document.body.appendChild(notification);
  228. // 显示通知
  229. setTimeout(() => notification.classList.add('show'), 10);
  230. // 自动隐藏
  231. setTimeout(() => {
  232. notification.classList.remove('show');
  233. setTimeout(() => notification.remove(), 300);
  234. }, 3000);
  235. }
  236. // 解析输入数据
  237. function parseInputData() {
  238. const currentFormat = manualFormatSelect.value;
  239. switch(currentFormat) {
  240. case 'simple':
  241. return parseSimpleData();
  242. case 'series':
  243. return parseSeriesData();
  244. case 'csv':
  245. return parseCsvData();
  246. default:
  247. throw new Error('未知的数据格式');
  248. }
  249. }
  250. // 解析简单数据
  251. function parseSimpleData() {
  252. const input = document.getElementById('data-input').value.trim();
  253. if (!input) {
  254. throw new Error('请输入数据');
  255. }
  256. const lines = input.split('\n').filter(line => line.trim());
  257. const labels = [];
  258. const data = [];
  259. lines.forEach(line => {
  260. const parts = line.split(',').map(part => part.trim());
  261. if (parts.length >= 2) {
  262. labels.push(parts[0]);
  263. const value = parseFloat(parts[1]);
  264. if (isNaN(value)) {
  265. throw new Error(`"${parts[1]}"不是有效的数值`);
  266. }
  267. data.push(value);
  268. }
  269. });
  270. if (labels.length === 0 || data.length === 0) {
  271. throw new Error('无法解析数据,请检查格式是否正确');
  272. }
  273. return {
  274. labels: labels,
  275. datasets: [{
  276. data: data,
  277. label: '数值'
  278. }],
  279. isSimpleData: true // 添加标记,表示这是简单数据格式
  280. };
  281. }
  282. // 解析系列数据
  283. function parseSeriesData() {
  284. const input = document.getElementById('series-data-input').value.trim();
  285. const labelsInput = document.getElementById('series-labels').value.trim();
  286. if (!input) {
  287. throw new Error('请输入系列数据');
  288. }
  289. if (!labelsInput) {
  290. throw new Error('请输入标签数据');
  291. }
  292. const lines = input.split('\n').filter(line => line.trim());
  293. const labels = labelsInput.split(',').map(label => label.trim());
  294. const datasets = [];
  295. lines.forEach(line => {
  296. const parts = line.split(',').map(part => part.trim());
  297. if (parts.length >= 2) {
  298. const seriesName = parts[0];
  299. const seriesData = parts.slice(1).map(val => {
  300. const value = parseFloat(val);
  301. if (isNaN(value)) {
  302. throw new Error(`"${val}"不是有效的数值`);
  303. }
  304. return value;
  305. });
  306. datasets.push({
  307. label: seriesName,
  308. data: seriesData
  309. });
  310. }
  311. });
  312. if (labels.length === 0 || datasets.length === 0) {
  313. throw new Error('无法解析数据,请检查格式是否正确');
  314. }
  315. return {
  316. labels: labels,
  317. datasets: datasets
  318. };
  319. }
  320. // 解析CSV数据
  321. function parseCsvData() {
  322. const input = document.getElementById('csv-data-input').value.trim();
  323. const firstRowHeader = document.getElementById('first-row-header').checked;
  324. const firstColLabels = document.getElementById('first-col-labels').checked;
  325. if (!input) {
  326. throw new Error('请输入CSV数据');
  327. }
  328. const lines = input.split('\n').filter(line => line.trim());
  329. if (lines.length < 2) {
  330. throw new Error('CSV数据至少需要两行');
  331. }
  332. const rows = lines.map(line => line.split(',').map(cell => cell.trim()));
  333. let labels = [];
  334. let datasets = [];
  335. if (firstRowHeader && firstColLabels) {
  336. // 第一行是标题,第一列是标签
  337. labels = rows.slice(1).map(row => row[0]);
  338. const headers = rows[0].slice(1);
  339. headers.forEach((header, i) => {
  340. const data = rows.slice(1).map(row => {
  341. const value = parseFloat(row[i+1]);
  342. if (isNaN(value)) {
  343. throw new Error(`"${row[i+1]}"不是有效的数值`);
  344. }
  345. return value;
  346. });
  347. datasets.push({
  348. label: header,
  349. data: data
  350. });
  351. });
  352. } else if (firstRowHeader && !firstColLabels) {
  353. // 第一行是标题,但第一列不是标签
  354. labels = Array.from({length: rows[0].length}, (_, i) => `数据${i+1}`);
  355. const headers = rows[0];
  356. headers.forEach((header, i) => {
  357. const data = rows.slice(1).map(row => {
  358. const value = parseFloat(row[i]);
  359. if (isNaN(value)) {
  360. throw new Error(`"${row[i]}"不是有效的数值`);
  361. }
  362. return value;
  363. });
  364. datasets.push({
  365. label: header,
  366. data: data
  367. });
  368. });
  369. } else if (!firstRowHeader && firstColLabels) {
  370. // 第一行不是标题,第一列是标签
  371. labels = rows.map(row => row[0]);
  372. for (let i = 1; i < rows[0].length; i++) {
  373. const data = rows.map(row => {
  374. const value = parseFloat(row[i]);
  375. if (isNaN(value)) {
  376. throw new Error(`"${row[i]}"不是有效的数值`);
  377. }
  378. return value;
  379. });
  380. datasets.push({
  381. label: `系列${i}`,
  382. data: data
  383. });
  384. }
  385. } else {
  386. // 第一行不是标题,第一列也不是标签
  387. labels = Array.from({length: rows.length}, (_, i) => `标签${i+1}`);
  388. for (let i = 0; i < rows[0].length; i++) {
  389. const data = rows.map(row => {
  390. const value = parseFloat(row[i]);
  391. if (isNaN(value)) {
  392. throw new Error(`"${row[i]}"不是有效的数值`);
  393. }
  394. return value;
  395. });
  396. datasets.push({
  397. label: `系列${i+1}`,
  398. data: data
  399. });
  400. }
  401. }
  402. if (labels.length === 0 || datasets.length === 0) {
  403. throw new Error('无法解析数据,请检查格式是否正确');
  404. }
  405. return {
  406. labels: labels,
  407. datasets: datasets
  408. };
  409. }
  410. // 获取图表设置
  411. function getChartSettings() {
  412. // 从活跃的图表类型项获取图表类型,而不是从下拉框
  413. let chartType = 'bar'; // 默认值
  414. const activeChartTypeItem = document.querySelector('.chart-type-item.active');
  415. if (activeChartTypeItem) {
  416. chartType = activeChartTypeItem.getAttribute('data-chart-type');
  417. }
  418. return {
  419. type: chartType,
  420. title: document.getElementById('chart-title').value,
  421. xAxisLabel: document.getElementById('x-axis-label').value,
  422. yAxisLabel: document.getElementById('y-axis-label').value,
  423. colorScheme: document.getElementById('color-scheme').value,
  424. legendPosition: document.getElementById('legend-position').value,
  425. showGridLines: document.getElementById('show-grid-lines').checked,
  426. animateChart: document.getElementById('animate-chart').checked
  427. };
  428. }
  429. // 导出PNG图像
  430. exportPngBtn.addEventListener('click', function() {
  431. exportChart('png');
  432. });
  433. // 导出JPG图像
  434. exportJpgBtn.addEventListener('click', function() {
  435. exportChart('jpg');
  436. });
  437. // 复制图像到剪贴板
  438. copyImgBtn.addEventListener('click', function() {
  439. copyChartToClipboard();
  440. });
  441. // 导出图表为图像
  442. function exportChart(format) {
  443. const chartWrapper = document.getElementById('chart-wrapper');
  444. // 创建加载指示器
  445. const loadingOverlay = document.createElement('div');
  446. loadingOverlay.className = 'loading-overlay';
  447. loadingOverlay.innerHTML = '<div class="loading-spinner"></div>';
  448. chartWrapper.appendChild(loadingOverlay);
  449. setTimeout(() => {
  450. // 获取原始canvas的尺寸
  451. const originalCanvas = document.getElementById('chart-canvas');
  452. const width = originalCanvas.width;
  453. const height = originalCanvas.height;
  454. // 创建一个临时的高分辨率canvas
  455. const tempCanvas = document.createElement('canvas');
  456. const tempCtx = tempCanvas.getContext('2d');
  457. // 设置更高的分辨率
  458. const scale = 8; // 提升到8倍分辨率
  459. tempCanvas.width = width * scale;
  460. tempCanvas.height = height * scale;
  461. // 优化渲染质量
  462. tempCtx.imageSmoothingEnabled = true;
  463. tempCtx.imageSmoothingQuality = 'high';
  464. html2canvas(originalCanvas, {
  465. backgroundColor: '#ffffff',
  466. scale: scale, // 使用8倍缩放
  467. width: width,
  468. height: height,
  469. useCORS: true,
  470. allowTaint: true,
  471. logging: false,
  472. imageTimeout: 0,
  473. onclone: (document) => {
  474. const clonedCanvas = document.getElementById('chart-canvas');
  475. if(clonedCanvas) {
  476. clonedCanvas.style.width = width + 'px';
  477. clonedCanvas.style.height = height + 'px';
  478. }
  479. },
  480. // 添加高级渲染选项
  481. canvas: tempCanvas,
  482. renderCallback: (canvas) => {
  483. // 应用锐化效果
  484. const ctx = canvas.getContext('2d');
  485. ctx.filter = 'contrast(1.1) saturate(1.2)';
  486. }
  487. }).then(canvas => {
  488. // 移除加载指示器
  489. loadingOverlay.remove();
  490. // 导出图像时使用更高的质量设置
  491. let imgUrl;
  492. if (format === 'jpg') {
  493. // JPEG使用最高质量
  494. imgUrl = canvas.toDataURL('image/jpeg', 1.0);
  495. } else {
  496. // PNG使用无损压缩
  497. imgUrl = canvas.toDataURL('image/png');
  498. }
  499. // 创建下载链接
  500. const link = document.createElement('a');
  501. const chartTitle = document.getElementById('chart-title').value || '图表';
  502. const fileName = `${chartTitle.replace(/[^\w\u4e00-\u9fa5]/g, '_')}_Ultra_HD.${format}`;
  503. link.download = fileName;
  504. link.href = imgUrl;
  505. link.click();
  506. showNotification(`已成功导出超高清${format.toUpperCase()}图像`);
  507. }).catch(error => {
  508. // 移除加载指示器
  509. loadingOverlay.remove();
  510. showNotification('导出图像失败,请重试', true);
  511. console.error('导出图像出错:', error);
  512. });
  513. }, 100);
  514. }
  515. // 复制图表到剪贴板
  516. function copyChartToClipboard() {
  517. const chartWrapper = document.getElementById('chart-wrapper');
  518. // 创建加载指示器
  519. const loadingOverlay = document.createElement('div');
  520. loadingOverlay.className = 'loading-overlay';
  521. loadingOverlay.innerHTML = '<div class="loading-spinner"></div>';
  522. chartWrapper.appendChild(loadingOverlay);
  523. setTimeout(() => {
  524. html2canvas(document.getElementById('chart-canvas'), {
  525. backgroundColor: '#ffffff',
  526. scale: 2
  527. }).then(canvas => {
  528. // 移除加载指示器
  529. loadingOverlay.remove();
  530. canvas.toBlob(blob => {
  531. try {
  532. // 尝试使用现代API复制到剪贴板
  533. if (navigator.clipboard && navigator.clipboard.write) {
  534. const clipboardItem = new ClipboardItem({'image/png': blob});
  535. navigator.clipboard.write([clipboardItem])
  536. .then(() => {
  537. showNotification('图表已复制到剪贴板');
  538. })
  539. .catch(err => {
  540. console.error('剪贴板API错误:', err);
  541. legacyCopyToClipboard(canvas);
  542. });
  543. } else {
  544. legacyCopyToClipboard(canvas);
  545. }
  546. } catch (e) {
  547. console.error('复制到剪贴板出错:', e);
  548. legacyCopyToClipboard(canvas);
  549. }
  550. });
  551. }).catch(error => {
  552. // 移除加载指示器
  553. loadingOverlay.remove();
  554. showNotification('复制图像失败,请重试', true);
  555. console.error('复制图像出错:', error);
  556. });
  557. }, 100);
  558. }
  559. // 兼容性较好的复制方法(通过创建临时链接)
  560. function legacyCopyToClipboard(canvas) {
  561. const imgUrl = canvas.toDataURL('image/png');
  562. // 创建临时链接
  563. const link = document.createElement('a');
  564. link.download = '图表.png';
  565. link.href = imgUrl;
  566. showNotification('已准备下载图表,无法直接复制到剪贴板');
  567. link.click();
  568. }
  569. // 解析Excel数据
  570. function parseExcelData(jsonData) {
  571. if (!jsonData || jsonData.length < 2 || !jsonData[0] || jsonData[0].length < 2) {
  572. throw new Error('Excel数据格式不正确,至少需要表头行和数据行');
  573. }
  574. // 假设第一行为标题,第一列为标签
  575. const labels = jsonData.slice(1).map(row => row && row[0] ? row[0].toString() : '');
  576. const datasets = [];
  577. const headers = jsonData[0].slice(1);
  578. headers.forEach((header, i) => {
  579. const data = jsonData.slice(1).map(row => {
  580. // 确保每个单元格数据都是数值类型
  581. if (!row || !row[i + 1]) return 0;
  582. const value = parseFloat(row[i + 1]);
  583. return isNaN(value) ? 0 : value;
  584. });
  585. datasets.push({
  586. label: header ? header.toString() : `系列${i+1}`,
  587. data: data
  588. });
  589. });
  590. return {
  591. labels: labels,
  592. datasets: datasets
  593. };
  594. }
  595. // 从chart-generator.js中导入图表生成函数
  596. function getChartInstance() {
  597. return window.chartInstance;
  598. }
  599. function setChartInstance(instance) {
  600. window.chartInstance = instance;
  601. }
  602. // 根据数据格式更新图表类型选项
  603. function updateChartTypeOptions(dataFormat) {
  604. // 由于移除了图表类型下拉框,这个函数现在仅记录当前数据格式,不再修改任何选项
  605. console.log('当前数据格式:', dataFormat);
  606. // 未来可以根据数据格式来调整图表类型画廊的可见性或提示,但现在不需要操作
  607. }
  608. });