123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712 |
- document.addEventListener('DOMContentLoaded', function() {
- // 获取DOM元素
- const simpleDataContainer = document.getElementById('simple-data-container');
- const seriesDataContainer = document.getElementById('series-data-container');
- const csvDataContainer = document.getElementById('csv-data-container');
- const generateBtn = document.getElementById('generate-btn');
- const sampleDataBtn = document.getElementById('sample-data-btn');
- const exportPngBtn = document.getElementById('export-png-btn');
- const exportJpgBtn = document.getElementById('export-jpg-btn');
- const copyImgBtn = document.getElementById('copy-img-btn');
- const chartTypeSelect = document.getElementById('chart-type');
- const manualFormatSelect = document.getElementById('manual-format');
- const fileUploadInput = document.getElementById('file-upload');
- const manualFormatContainer = document.getElementById('manual-format-container');
- const manualInputContainers = [simpleDataContainer, seriesDataContainer, csvDataContainer];
-
- // 初始化显示状态
- const initialMethod = document.querySelector('input[name="data-input-method"]:checked').value;
- toggleManualInputs(initialMethod === 'manual');
- fileUploadInput.parentElement.style.display = initialMethod === 'upload-csv' ? 'block' : 'none';
- // 初始化图表类型画廊
- initChartTypeGallery();
- function toggleManualInputs(show) {
- manualFormatContainer.style.display = show ? 'block' : 'none';
- const selectedFormat = manualFormatSelect.value;
- manualInputContainers.forEach(container => {
- const containerId = container.id.split('-')[0]; // 'simple', 'series', 'csv'
- container.style.display = (show && containerId === selectedFormat) ? 'block' : 'none';
- });
- }
- // 初始化时调用updateChartTypeOptions函数
- // 无论当前选择的是什么输入方式,都初始化图表类型
- // 默认使用'series'格式以显示最多的图表类型选项
- updateChartTypeOptions('series');
- // 监听数据输入方式切换
- document.querySelectorAll('input[name="data-input-method"]').forEach(radio => {
- radio.addEventListener('change', function() {
- const method = this.value;
- toggleManualInputs(method === 'manual');
- fileUploadInput.parentElement.style.display = method === 'upload-csv' ? 'block' : 'none';
-
- // 在切换到"上传Excel/CSV"时,更新图表类型选项为多系列数据
- if (method === 'upload-csv') {
- updateChartTypeOptions('series');
- } else if (method === 'manual') {
- // 切换回"手动录入"时,根据当前选择的格式更新图表类型选项
- updateChartTypeOptions(manualFormatSelect.value);
- uploadedData = null;
- fileUploadInput.value = ''; // 清空文件选择
- }
- });
- });
- // 监听手动格式选择变化
- manualFormatSelect.addEventListener('change', function() {
- const format = this.value;
- manualInputContainers.forEach(container => {
- const containerId = container.id.split('-')[0];
- container.style.display = (containerId === format) ? 'block' : 'none';
- });
-
- // 更新图表类型选项
- updateChartTypeOptions(format);
- });
- // 文件上传处理
- fileUploadInput.addEventListener('change', function(event) {
- const file = event.target.files[0];
- if (!file) {
- uploadedData = null;
- return;
- }
- const reader = new FileReader();
- reader.onload = function(e) {
- try {
- const data = new Uint8Array(e.target.result);
- const workbook = XLSX.read(data, { type: 'array' });
- const firstSheetName = workbook.SheetNames[0];
- const worksheet = workbook.Sheets[firstSheetName];
- const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
- uploadedData = parseExcelData(jsonData);
- showNotification('文件上传成功,可以点击"生成图表"');
-
- // 上传Excel文件后,更新图表类型选项为多系列数据类型
- updateChartTypeOptions('series');
- } catch (error) {
- showNotification('文件解析失败: ' + error.message, true);
- uploadedData = null;
- fileUploadInput.value = ''; // 清空文件选择
- }
- };
- reader.onerror = function() {
- showNotification('文件读取失败', true);
- uploadedData = null;
- fileUploadInput.value = ''; // 清空文件选择
- };
- reader.readAsArrayBuffer(file);
- });
- // 生成图表按钮点击事件 (修改为独立的函数)
- function generateChart() {
- try {
- console.log('开始生成图表...');
- let parsedData;
- const method = document.querySelector('input[name="data-input-method"]:checked').value;
- console.log('数据输入方式:', method);
- if (method === 'upload-csv' && uploadedData) {
- parsedData = uploadedData;
- console.log('使用上传的数据');
- } else if (method === 'manual') {
- parsedData = parseInputData(); // 使用现有的手动数据解析函数
- console.log('使用手动输入的数据');
- } else if (method === 'upload-csv' && !uploadedData) {
- throw new Error('请先上传文件');
- } else {
- throw new Error('请选择有效的数据输入方式并提供数据');
- }
-
- console.log('解析后的数据:', parsedData);
-
- if (!parsedData ||
- (parsedData.labels && parsedData.labels.length === 0) ||
- (parsedData.datasets && parsedData.datasets.length === 0)) {
- throw new Error('无法解析数据或数据为空');
- }
-
- // 保存数据到全局变量,方便其他函数访问
- window.chartData = parsedData;
-
- const chartSettings = getChartSettings();
-
- // 将简单数据标记添加到设置中
- if (parsedData.isSimpleData) {
- chartSettings.isSimpleData = true;
- }
-
- console.log('图表设置:', chartSettings);
-
- // 调用chart-generator.js中的createChart函数
- if (typeof createChart !== 'function') {
- throw new Error('createChart函数未定义,请确保chart-generator.js正确加载');
- }
-
- createChart(parsedData, chartSettings);
- console.log('图表生成成功');
-
- exportPngBtn.disabled = false;
- exportJpgBtn.disabled = false;
- copyImgBtn.disabled = false;
- } catch (error) {
- console.error('生成图表时出错:', error);
- showNotification(error.message, true);
- }
- }
- // 将generateChart函数暴露为全局函数
- window.generateChart = generateChart;
- generateBtn.addEventListener('click', generateChart);
- // 监听图表设置的变化事件,实时更新图表 (仅在有图表时)
- ['chart-title', 'x-axis-label', 'y-axis-label', 'color-scheme', 'legend-position'].forEach(id => {
- document.getElementById(id).addEventListener('input', function() {
- const instance = getChartInstance();
- if (instance) { // 检查是否有图表实例
- generateChart(); // 重新生成图表以应用设置
- }
- });
- });
-
- // 初始化图表类型画廊
- function initChartTypeGallery() {
- console.log('初始化图表类型预览画廊...');
-
- // 获取所有图表类型预览项
- const chartTypeItems = document.querySelectorAll('.chart-type-item');
- console.log(`找到${chartTypeItems.length}个图表类型预览项`);
-
- // 为每个预览项添加点击事件
- chartTypeItems.forEach(item => {
- item.addEventListener('click', function() {
- const chartType = this.getAttribute('data-chart-type');
- console.log('选择了图表类型:', chartType);
-
- // 更新活动状态
- chartTypeItems.forEach(item => item.classList.remove('active'));
- this.classList.add('active');
-
- // 无论是否有图表实例都应该重新生成图表
- // 删除之前的检查条件,始终调用generateChart
- generateChart();
- });
- });
-
- // 初始设置默认图表类型为活动状态
- const defaultChartType = "bar"; // 默认为柱状图
- const activeItem = document.querySelector(`.chart-type-item[data-chart-type="${defaultChartType}"]`);
- if (activeItem) {
- activeItem.classList.add('active');
- }
- }
-
- // 加载样例数据
- sampleDataBtn.addEventListener('click', function() {
- // 确保选中"手动录入"选项
- const manualRadio = document.querySelector('input[name="data-input-method"][value="manual"]');
- if (manualRadio && !manualRadio.checked) {
- manualRadio.checked = true;
- // 触发change事件以显示相关的输入控件
- manualRadio.dispatchEvent(new Event('change'));
- }
-
- const currentFormat = manualFormatSelect.value;
-
- switch(currentFormat) {
- case 'simple':
- document.getElementById('data-input').value =
- '智能手机,2458\n平板电脑,1678\n笔记本电脑,1892\n智能手表,986\n耳机,1342';
- document.getElementById('chart-title').value = '2023年电子产品销量(万台)';
- document.getElementById('x-axis-label').value = '产品类别';
- document.getElementById('y-axis-label').value = '销量(万台)';
- break;
- case 'series':
- document.getElementById('series-data-input').value =
- '第一季度,2458,1678,1892,986,1342\n第二季度,2612,1524,1953,1104,1587\n第三季度,2845,1701,2135,1287,1643\n第四季度,3256,1835,2278,1452,1821';
- document.getElementById('series-labels').value =
- '智能手机,平板电脑,笔记本电脑,智能手表,耳机';
- document.getElementById('chart-title').value = '2023年电子产品季度销量(万台)';
- document.getElementById('x-axis-label').value = '产品类别';
- document.getElementById('y-axis-label').value = '销量(万台)';
- break;
- case 'csv':
- document.getElementById('csv-data-input').value =
- '品牌,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';
- document.getElementById('chart-title').value = '国内智能手机品牌销量趋势(万台)';
- document.getElementById('x-axis-label').value = '品牌';
- document.getElementById('y-axis-label').value = '销量(万台)';
- break;
- }
-
- // 提示用户下一步操作
- showNotification('已加载样例数据,点击"生成图表"查看效果');
- });
-
- // 显示通知
- function showNotification(message, isError = false) {
- // 移除现有通知
- const existingNotification = document.querySelector('.notification');
- if (existingNotification) {
- existingNotification.remove();
- }
-
- // 创建新通知
- const notification = document.createElement('div');
- notification.className = 'notification' + (isError ? ' error' : '');
- notification.textContent = message;
-
- // 添加到文档
- document.body.appendChild(notification);
-
- // 显示通知
- setTimeout(() => notification.classList.add('show'), 10);
-
- // 自动隐藏
- setTimeout(() => {
- notification.classList.remove('show');
- setTimeout(() => notification.remove(), 300);
- }, 3000);
- }
-
- // 解析输入数据
- function parseInputData() {
- const currentFormat = manualFormatSelect.value;
-
- switch(currentFormat) {
- case 'simple':
- return parseSimpleData();
- case 'series':
- return parseSeriesData();
- case 'csv':
- return parseCsvData();
- default:
- throw new Error('未知的数据格式');
- }
- }
-
- // 解析简单数据
- function parseSimpleData() {
- const input = document.getElementById('data-input').value.trim();
- if (!input) {
- throw new Error('请输入数据');
- }
-
- const lines = input.split('\n').filter(line => line.trim());
- const labels = [];
- const data = [];
-
- lines.forEach(line => {
- const parts = line.split(',').map(part => part.trim());
- if (parts.length >= 2) {
- labels.push(parts[0]);
- const value = parseFloat(parts[1]);
- if (isNaN(value)) {
- throw new Error(`"${parts[1]}"不是有效的数值`);
- }
- data.push(value);
- }
- });
-
- if (labels.length === 0 || data.length === 0) {
- throw new Error('无法解析数据,请检查格式是否正确');
- }
-
- return {
- labels: labels,
- datasets: [{
- data: data,
- label: '数值'
- }],
- isSimpleData: true // 添加标记,表示这是简单数据格式
- };
- }
-
- // 解析系列数据
- function parseSeriesData() {
- const input = document.getElementById('series-data-input').value.trim();
- const labelsInput = document.getElementById('series-labels').value.trim();
-
- if (!input) {
- throw new Error('请输入系列数据');
- }
-
- if (!labelsInput) {
- throw new Error('请输入标签数据');
- }
-
- const lines = input.split('\n').filter(line => line.trim());
- const labels = labelsInput.split(',').map(label => label.trim());
- const datasets = [];
-
- lines.forEach(line => {
- const parts = line.split(',').map(part => part.trim());
- if (parts.length >= 2) {
- const seriesName = parts[0];
- const seriesData = parts.slice(1).map(val => {
- const value = parseFloat(val);
- if (isNaN(value)) {
- throw new Error(`"${val}"不是有效的数值`);
- }
- return value;
- });
-
- datasets.push({
- label: seriesName,
- data: seriesData
- });
- }
- });
-
- if (labels.length === 0 || datasets.length === 0) {
- throw new Error('无法解析数据,请检查格式是否正确');
- }
-
- return {
- labels: labels,
- datasets: datasets
- };
- }
-
- // 解析CSV数据
- function parseCsvData() {
- const input = document.getElementById('csv-data-input').value.trim();
- const firstRowHeader = document.getElementById('first-row-header').checked;
- const firstColLabels = document.getElementById('first-col-labels').checked;
-
- if (!input) {
- throw new Error('请输入CSV数据');
- }
-
- const lines = input.split('\n').filter(line => line.trim());
- if (lines.length < 2) {
- throw new Error('CSV数据至少需要两行');
- }
-
- const rows = lines.map(line => line.split(',').map(cell => cell.trim()));
-
- let labels = [];
- let datasets = [];
-
- if (firstRowHeader && firstColLabels) {
- // 第一行是标题,第一列是标签
- labels = rows.slice(1).map(row => row[0]);
-
- const headers = rows[0].slice(1);
- headers.forEach((header, i) => {
- const data = rows.slice(1).map(row => {
- const value = parseFloat(row[i+1]);
- if (isNaN(value)) {
- throw new Error(`"${row[i+1]}"不是有效的数值`);
- }
- return value;
- });
-
- datasets.push({
- label: header,
- data: data
- });
- });
- } else if (firstRowHeader && !firstColLabels) {
- // 第一行是标题,但第一列不是标签
- labels = Array.from({length: rows[0].length}, (_, i) => `数据${i+1}`);
-
- const headers = rows[0];
- headers.forEach((header, i) => {
- const data = rows.slice(1).map(row => {
- const value = parseFloat(row[i]);
- if (isNaN(value)) {
- throw new Error(`"${row[i]}"不是有效的数值`);
- }
- return value;
- });
-
- datasets.push({
- label: header,
- data: data
- });
- });
- } else if (!firstRowHeader && firstColLabels) {
- // 第一行不是标题,第一列是标签
- labels = rows.map(row => row[0]);
-
- for (let i = 1; i < rows[0].length; i++) {
- const data = rows.map(row => {
- const value = parseFloat(row[i]);
- if (isNaN(value)) {
- throw new Error(`"${row[i]}"不是有效的数值`);
- }
- return value;
- });
-
- datasets.push({
- label: `系列${i}`,
- data: data
- });
- }
- } else {
- // 第一行不是标题,第一列也不是标签
- labels = Array.from({length: rows.length}, (_, i) => `标签${i+1}`);
-
- for (let i = 0; i < rows[0].length; i++) {
- const data = rows.map(row => {
- const value = parseFloat(row[i]);
- if (isNaN(value)) {
- throw new Error(`"${row[i]}"不是有效的数值`);
- }
- return value;
- });
-
- datasets.push({
- label: `系列${i+1}`,
- data: data
- });
- }
- }
-
- if (labels.length === 0 || datasets.length === 0) {
- throw new Error('无法解析数据,请检查格式是否正确');
- }
-
- return {
- labels: labels,
- datasets: datasets
- };
- }
-
- // 获取图表设置
- function getChartSettings() {
- // 从活跃的图表类型项获取图表类型,而不是从下拉框
- let chartType = 'bar'; // 默认值
- const activeChartTypeItem = document.querySelector('.chart-type-item.active');
- if (activeChartTypeItem) {
- chartType = activeChartTypeItem.getAttribute('data-chart-type');
- }
-
- return {
- type: chartType,
- title: document.getElementById('chart-title').value,
- xAxisLabel: document.getElementById('x-axis-label').value,
- yAxisLabel: document.getElementById('y-axis-label').value,
- colorScheme: document.getElementById('color-scheme').value,
- legendPosition: document.getElementById('legend-position').value,
- showGridLines: document.getElementById('show-grid-lines').checked,
- animateChart: document.getElementById('animate-chart').checked
- };
- }
-
- // 导出PNG图像
- exportPngBtn.addEventListener('click', function() {
- exportChart('png');
- });
-
- // 导出JPG图像
- exportJpgBtn.addEventListener('click', function() {
- exportChart('jpg');
- });
-
- // 复制图像到剪贴板
- copyImgBtn.addEventListener('click', function() {
- copyChartToClipboard();
- });
-
- // 导出图表为图像
- function exportChart(format) {
- const chartWrapper = document.getElementById('chart-wrapper');
-
- // 创建加载指示器
- const loadingOverlay = document.createElement('div');
- loadingOverlay.className = 'loading-overlay';
- loadingOverlay.innerHTML = '<div class="loading-spinner"></div>';
- chartWrapper.appendChild(loadingOverlay);
-
- setTimeout(() => {
- // 获取原始canvas的尺寸
- const originalCanvas = document.getElementById('chart-canvas');
- const width = originalCanvas.width;
- const height = originalCanvas.height;
-
- // 创建一个临时的高分辨率canvas
- const tempCanvas = document.createElement('canvas');
- const tempCtx = tempCanvas.getContext('2d');
-
- // 设置更高的分辨率
- const scale = 8; // 提升到8倍分辨率
- tempCanvas.width = width * scale;
- tempCanvas.height = height * scale;
-
- // 优化渲染质量
- tempCtx.imageSmoothingEnabled = true;
- tempCtx.imageSmoothingQuality = 'high';
-
- html2canvas(originalCanvas, {
- backgroundColor: '#ffffff',
- scale: scale, // 使用8倍缩放
- width: width,
- height: height,
- useCORS: true,
- allowTaint: true,
- logging: false,
- imageTimeout: 0,
- onclone: (document) => {
- const clonedCanvas = document.getElementById('chart-canvas');
- if(clonedCanvas) {
- clonedCanvas.style.width = width + 'px';
- clonedCanvas.style.height = height + 'px';
- }
- },
- // 添加高级渲染选项
- canvas: tempCanvas,
- renderCallback: (canvas) => {
- // 应用锐化效果
- const ctx = canvas.getContext('2d');
- ctx.filter = 'contrast(1.1) saturate(1.2)';
- }
- }).then(canvas => {
- // 移除加载指示器
- loadingOverlay.remove();
-
- // 导出图像时使用更高的质量设置
- let imgUrl;
- if (format === 'jpg') {
- // JPEG使用最高质量
- imgUrl = canvas.toDataURL('image/jpeg', 1.0);
- } else {
- // PNG使用无损压缩
- imgUrl = canvas.toDataURL('image/png');
- }
-
- // 创建下载链接
- const link = document.createElement('a');
- const chartTitle = document.getElementById('chart-title').value || '图表';
- const fileName = `${chartTitle.replace(/[^\w\u4e00-\u9fa5]/g, '_')}_Ultra_HD.${format}`;
-
- link.download = fileName;
- link.href = imgUrl;
- link.click();
-
- showNotification(`已成功导出超高清${format.toUpperCase()}图像`);
- }).catch(error => {
- // 移除加载指示器
- loadingOverlay.remove();
-
- showNotification('导出图像失败,请重试', true);
- console.error('导出图像出错:', error);
- });
- }, 100);
- }
-
- // 复制图表到剪贴板
- function copyChartToClipboard() {
- const chartWrapper = document.getElementById('chart-wrapper');
-
- // 创建加载指示器
- const loadingOverlay = document.createElement('div');
- loadingOverlay.className = 'loading-overlay';
- loadingOverlay.innerHTML = '<div class="loading-spinner"></div>';
- chartWrapper.appendChild(loadingOverlay);
-
- setTimeout(() => {
- html2canvas(document.getElementById('chart-canvas'), {
- backgroundColor: '#ffffff',
- scale: 2
- }).then(canvas => {
- // 移除加载指示器
- loadingOverlay.remove();
-
- canvas.toBlob(blob => {
- try {
- // 尝试使用现代API复制到剪贴板
- if (navigator.clipboard && navigator.clipboard.write) {
- const clipboardItem = new ClipboardItem({'image/png': blob});
- navigator.clipboard.write([clipboardItem])
- .then(() => {
- showNotification('图表已复制到剪贴板');
- })
- .catch(err => {
- console.error('剪贴板API错误:', err);
- legacyCopyToClipboard(canvas);
- });
- } else {
- legacyCopyToClipboard(canvas);
- }
- } catch (e) {
- console.error('复制到剪贴板出错:', e);
- legacyCopyToClipboard(canvas);
- }
- });
- }).catch(error => {
- // 移除加载指示器
- loadingOverlay.remove();
-
- showNotification('复制图像失败,请重试', true);
- console.error('复制图像出错:', error);
- });
- }, 100);
- }
-
- // 兼容性较好的复制方法(通过创建临时链接)
- function legacyCopyToClipboard(canvas) {
- const imgUrl = canvas.toDataURL('image/png');
-
- // 创建临时链接
- const link = document.createElement('a');
- link.download = '图表.png';
- link.href = imgUrl;
-
- showNotification('已准备下载图表,无法直接复制到剪贴板');
- link.click();
- }
-
- // 解析Excel数据
- function parseExcelData(jsonData) {
- if (!jsonData || jsonData.length < 2 || !jsonData[0] || jsonData[0].length < 2) {
- throw new Error('Excel数据格式不正确,至少需要表头行和数据行');
- }
-
- // 假设第一行为标题,第一列为标签
- const labels = jsonData.slice(1).map(row => row && row[0] ? row[0].toString() : '');
- const datasets = [];
- const headers = jsonData[0].slice(1);
- headers.forEach((header, i) => {
- const data = jsonData.slice(1).map(row => {
- // 确保每个单元格数据都是数值类型
- if (!row || !row[i + 1]) return 0;
- const value = parseFloat(row[i + 1]);
- return isNaN(value) ? 0 : value;
- });
-
- datasets.push({
- label: header ? header.toString() : `系列${i+1}`,
- data: data
- });
- });
- return {
- labels: labels,
- datasets: datasets
- };
- }
- // 从chart-generator.js中导入图表生成函数
- function getChartInstance() {
- return window.chartInstance;
- }
- function setChartInstance(instance) {
- window.chartInstance = instance;
- }
- // 根据数据格式更新图表类型选项
- function updateChartTypeOptions(dataFormat) {
- // 由于移除了图表类型下拉框,这个函数现在仅记录当前数据格式,不再修改任何选项
- console.log('当前数据格式:', dataFormat);
- // 未来可以根据数据格式来调整图表类型画廊的可见性或提示,但现在不需要操作
- }
- });
|