Files
data-chart-tool/index.html
T
hz4th_coder 2cc6491440 feat: 数据方向(行=系列)+每系列类型/双轴 + 多图大标题/共享图例 v1.16.0
- 图表模式新增'数据方向':列=系列(默认)/行=系列(第一行是横坐标,每行一个系列),每行可独立设柱状/折线 + 左右轴量度
- 后端 buildChartOption/GET /api/chart 支持 rowsAsSeries + seriesAxis(按系列下标指定左右轴)
- 多图合并每张小图新增:行=系列、双Y轴(左右量度不同)、每系列独立图表类型与坐标轴配置
- 多图合并新增图片大标题(bigTitle),居中显示在顶部
- 多图合并新增图例方式(legendMode):own 各自图例(默认)/shared-top 共用图例顶部/shared-bottom 共用图例底部,共用时各子图隐藏自己的图例、系列合并去重、超宽自动换行
- /api/combine 后端同步支持 bigTitle/legendMode(含深色主题文字适配)
- 收藏功能完整支持新字段(combine 大标题/图例方式/每系列配置,chart 数据方向),编辑恢复正常
- 文档/API.md/README 更新,版本 1.16.0
2026-08-22 12:03:05 +08:00

467 lines
28 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数据可视化图表生成器</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="app-container">
<!-- 顶部标题 -->
<header class="app-header">
<div class="header-title">
<h1>📊 数据可视化图表生成器</h1>
<p class="subtitle">粘贴数据,生成精美图表或表格</p>
</div>
<div class="header-actions">
<button class="btn btn-fav-outline" onclick="openFavorites()">⭐ 收藏区</button>
</div>
</header>
<div class="main-content">
<!-- 左侧面板:数据输入和配置 -->
<div class="left-panel">
<!-- 模式切换 -->
<div class="panel-section">
<div class="mode-switch">
<button class="mode-btn active" id="btnChartMode" onclick="switchMode('chart')">📈 图表模式</button>
<button class="mode-btn" id="btnTableMode" onclick="switchMode('table')">📋 表格模式</button>
<button class="mode-btn" id="btnCombineMode" onclick="switchMode('combine')">🖼️ 多图合并</button>
</div>
</div>
<!-- 数据输入区 -->
<div class="panel-section" id="dataInputSection">
<h2>📝 数据输入</h2>
<div class="input-format-hint">
<p><strong>格式说明:</strong></p>
<p>第一行为表头,后续行为数据</p>
<pre>类别, 系列1, 系列2, 系列3
A, 10, 20, 30
B, 15, 25, 35
C, 20, 30, 40</pre>
</div>
<textarea id="dataInput" placeholder="在此粘贴数据...&#10;支持逗号、制表符分隔&#10;&#10;示例:&#10;产品, 2023年, 2024年, 2025年&#10;Q1, 120, 150, 180&#10;Q2, 200, 220, 260&#10;Q3, 180, 210, 240&#10;Q4, 250, 280, 320"></textarea>
<div class="btn-group">
<button class="btn btn-primary" id="btnGenerate" onclick="generate()">🎨 生成图表</button>
<button class="btn btn-secondary" onclick="loadSampleData()">📋 加载示例</button>
<button class="btn btn-secondary" onclick="clearData()">🗑️ 清空</button>
</div>
</div>
<!-- 图表配置区 -->
<div class="panel-section" id="chartConfigSection">
<h2>⚙️ 图表配置</h2>
<div class="config-group">
<label>图表类型</label>
<select id="chartType" onchange="updateChart()">
<option value="bar">柱状图</option>
<option value="line">折线图</option>
<option value="bar-line">柱状图+折线图混合</option>
<option value="pie">饼图</option>
<option value="radar">雷达图</option>
</select>
</div>
<div class="config-group">
<label>数据方向</label>
<select id="dataOrientation" onchange="onDataOrientationChange()">
<option value="columns">列=系列(第一列是横坐标,每列一个系列)</option>
<option value="rows">行=系列(第一行是横坐标,每行一个系列)</option>
</select>
<p class="hint-text">「行=系列」示例:<br>指标, 2023, 2024, 2025<br>营收(元), 1200, 1800, 2100<br>销量(个), 100, 150, 130<br>—— 每行可单独设柱状/折线 + 左右轴</p>
</div>
<div class="config-group">
<label>标题</label>
<input type="text" id="chartTitle" placeholder="图表标题" oninput="updateChart()">
</div>
<div class="config-group">
<label>主题风格</label>
<select id="themeStyle" onchange="syncThemeButtons(this.value); updateChart()">
<option value="default">默认</option>
<option value="dark">深色</option>
<option value="macarons">马卡龙</option>
<option value="gradient">渐变</option>
<option value="retro">复古</option>
<option value="ocean">海洋</option>
<option value="forest">森林</option>
<option value="sunset">日落</option>
<option value="lavender">薰衣草</option>
<option value="minimal">极简</option>
<option value="cherry">樱花</option>
<option value="midnight">午夜</option>
<option value="gold">金色</option>
<option value="coral">珊瑚</option>
<option value="mint">薄荷</option>
<option value="slate">石板灰</option>
<option value="sky">天空蓝</option>
<option value="rose">玫瑰红</option>
<option value="amber">琥珀黄</option>
<option value="emerald">翡翠绿</option>
<option value="indigo">靛蓝色</option>
<option value="stone">石灰白</option>
</select>
</div>
<div class="config-group">
<label>显示选项</label>
<div class="checkbox-group">
<label><input type="checkbox" id="showLegend" checked onchange="updateChart()"> 图例</label>
<label><input type="checkbox" id="showGrid" checked onchange="updateChart()"> 网格线</label>
<label><input type="checkbox" id="showLabel" onchange="updateChart()"> 数据标签</label>
<label><input type="checkbox" id="stackMode" onchange="updateChart()"> 堆叠模式</label>
<label><input type="checkbox" id="smoothLine" checked onchange="updateChart()"> 平滑曲线</label>
<label><input type="checkbox" id="dualAxis" onchange="onDualAxisChange()"> 双Y轴</label>
</div>
</div>
<div class="config-group" id="rightAxisGroup" style="display:none;">
<label>左右轴名称(量度可不同)</label>
<div class="res-input-row">
<input type="text" id="leftAxisName" placeholder="左轴,如:销售额" oninput="updateChart()">
<span class="res-times">/</span>
<input type="text" id="rightAxisName" placeholder="右轴,如:增长率(%)" oninput="updateChart()">
</div>
<p class="hint-text">在下方「系列配置」中可指定每个系列用左轴/右轴及图表类型(柱状/折线)</p>
</div>
</div>
<!-- 表格配置区 -->
<div class="panel-section" id="tableConfigSection" style="display:none;">
<h2>⚙️ 表格配置</h2>
<div class="config-group">
<label>标题</label>
<input type="text" id="tableTitle" placeholder="表格标题" oninput="updateTable()">
</div>
<div class="config-group">
<label>主题风格</label>
<select id="tableTheme" onchange="syncThemeButtons(this.value); updateTable()">
<option value="default">默认</option>
<option value="dark">深色</option>
<option value="macarons">马卡龙</option>
<option value="gradient">渐变</option>
<option value="retro">复古</option>
<option value="ocean">海洋</option>
<option value="forest">森林</option>
<option value="sunset">日落</option>
<option value="lavender">薰衣草</option>
<option value="minimal">极简</option>
<option value="cherry">樱花</option>
<option value="midnight">午夜</option>
<option value="gold">金色</option>
<option value="coral">珊瑚</option>
<option value="mint">薄荷</option>
<option value="slate">石板灰</option>
<option value="sky">天空蓝</option>
<option value="rose">玫瑰红</option>
<option value="amber">琥珀黄</option>
<option value="emerald">翡翠绿</option>
<option value="indigo">靛蓝色</option>
<option value="stone">石灰白</option>
</select>
</div>
<div class="config-group">
<label>字体大小: <span id="fontSizeValue">14</span>px</label>
<input type="range" id="tableFontSize" min="10" max="24" value="14" oninput="updateFontSize(); updateTable()">
</div>
<div class="config-group">
<label>显示选项</label>
<div class="checkbox-group">
<label><input type="checkbox" id="stripeRows" checked onchange="updateTable()"> 斑马纹</label>
</div>
</div>
<div class="config-group">
<label>边框样式</label>
<select id="borderStyle" onchange="updateTable()">
<option value="all">全部边框</option>
<option value="none">无边框</option>
<option value="no-outer">无外边线</option>
<option value="no-horizontal">无横线</option>
<option value="no-vertical">无竖线</option>
<option value="no-inner">无内部线</option>
<option value="no-inner-horizontal">无内部横线</option>
<option value="no-inner-vertical">无内部竖线</option>
<option value="outer-only">仅外边框</option>
<option value="header-only">仅表头分隔线</option>
</select>
</div>
</div>
<!-- 系列配置区 -->
<div class="panel-section" id="seriesConfigSection">
<h2>🎨 系列配置</h2>
<div id="seriesConfig" class="series-config">
<p class="hint-text">生成图表后可在此调整各系列的顺序和颜色</p>
</div>
</div>
<!-- 区域分割配置 -->
<div class="panel-section" id="splitConfigSection">
<h2>📐 区域分割</h2>
<div class="config-group">
<label><input type="checkbox" id="enableSplit" onchange="updateChart()"> 启用区域分割</label>
</div>
<div id="splitConfig" class="split-config" style="display:none;">
<div class="config-group">
<label>分割位置(横坐标索引,如:3 表示在第3个数据后分割)</label>
<input type="number" id="splitIndex" value="3" min="1" oninput="updateChart()">
</div>
<div class="config-group">
<label>左侧区域标签</label>
<input type="text" id="leftLabel" placeholder="如:2023年" oninput="updateChart()">
</div>
<div class="config-group">
<label>右侧区域标签</label>
<input type="text" id="rightLabel" placeholder="如:2024年" oninput="updateChart()">
</div>
<div class="config-group">
<label>分割线样式</label>
<select id="splitStyle" onchange="updateChart()">
<option value="solid">实线</option>
<option value="dashed">虚线</option>
<option value="dotted">点线</option>
</select>
</div>
</div>
</div>
<!-- 导出设置 + 按钮(默认折叠) -->
<div class="panel-section" id="exportSection">
<div class="export-header">
<div class="export-header-main" onclick="toggleExportSettings()" title="点击展开/折叠">
<h2>📤 导出设置</h2>
<span class="export-summary" id="exportSummary">原始大小</span>
<span class="export-toggle" id="exportToggle"></span>
</div>
<button class="btn btn-success btn-sm" onclick="exportImage('png')">📥 下载</button>
<button class="btn btn-fav btn-sm" id="btnFavorite" onclick="saveFavorite()">⭐ 收藏</button>
</div>
<div id="exportSettingsBody" class="export-settings-body" style="display:none;">
<div class="config-group">
<label>分辨率</label>
<select id="exportPreset" onchange="onExportPresetChange()">
<option value="original">原始大小</option>
<option value="1920x1080">1920 × 1080</option>
<option value="1280x720">1280 × 720</option>
<option value="1024x768">1024 × 768</option>
<option value="800x600">800 × 600</option>
<option value="custom">自定义</option>
</select>
</div>
<div class="config-group" id="customResGroup" style="display:none;">
<label>自定义分辨率(宽 × 高)</label>
<div class="res-input-row">
<input type="number" id="customWidth" min="1" max="8192" placeholder="宽" oninput="onCustomResInput()">
<span class="res-times">×</span>
<input type="number" id="customHeight" min="1" max="8192" placeholder="高" oninput="onCustomResInput()">
</div>
</div>
<div class="config-group">
<label>历史分辨率(最近5个)</label>
<select id="exportHistory" onchange="onHistorySelect()">
<option value="">— 暂无历史 —</option>
</select>
</div>
<div class="config-group">
<label>文件名</label>
<input type="text" id="exportFilename" placeholder="留空则按日期时间自动命名">
<p class="hint-text" id="exportResHint"></p>
</div>
<div class="btn-group">
<button class="btn btn-secondary" onclick="resetExportSettings()">🔄 恢复默认</button>
<button class="btn btn-success" id="btnExportSvg" onclick="exportImage('svg')">📥 导出SVG</button>
</div>
</div>
</div>
<!-- 多图合并配置区 -->
<div class="panel-section" id="combineConfigSection" style="display:none;">
<h2>🖼️ 多图合并配置</h2>
<div class="config-group">
<label>图片大标题(可选)</label>
<input type="text" id="combineBigTitle" placeholder="整张大图的大标题,留空则不显示" oninput="generateCombine()">
</div>
<div class="config-group">
<label>图例方式</label>
<select id="combineLegendMode" onchange="generateCombine()">
<option value="own">每个小图各自图例</option>
<option value="shared-top">共用一套图例(顶部)</option>
<option value="shared-bottom">共用一套图例(底部)</option>
</select>
<p class="hint-text">共用图例时:所有小图的系列合并去重,放在整张图片顶部/底部,各小图隐藏自己的图例;同名系列只显示一次</p>
</div>
<div class="config-group">
<label>排列方式</label>
<div class="combine-direction">
<label class="dir-option"><input type="radio" name="combineDirection" value="horizontal" checked onchange="generateCombine()"> ⇄ 横排(单行)</label>
<label class="dir-option"><input type="radio" name="combineDirection" value="vertical" onchange="generateCombine()"> ⇅ 竖排(单列)</label>
<label class="dir-option"><input type="radio" name="combineDirection" value="grid" onchange="onCombineGridToggle()"> ▦ 多行多列</label>
</div>
</div>
<div class="config-group" id="gridColsGroup" style="display:none;">
<label>每行列数</label>
<input type="number" id="gridCols" value="2" min="1" max="6" oninput="generateCombine()">
</div>
<div id="combineChartsContainer">
<!-- 图表卡片由 JS 动态渲染 -->
</div>
<div class="btn-group">
<button class="btn btn-secondary" onclick="addCombineChart()"> 添加图表</button>
<button class="btn btn-primary" onclick="generateCombine()">🖼️ 生成合并图</button>
<button class="btn btn-success" onclick="exportImage('png')">📥 下载图片</button>
</div>
</div>
</div>
<!-- 右侧面板:图表预览 + 导出预览 + 风格切换 -->
<div class="right-panel">
<div class="right-main">
<div class="chart-container">
<div id="chartArea" class="chart-area">
<div class="placeholder">
<p>📊</p>
<p>输入数据后点击"生成图表"</p>
</div>
</div>
</div>
<!-- 导出效果预览(按分辨率居中展示保存后的图) -->
<div class="export-preview-box" id="exportPreviewBox" style="display:none;">
<div class="export-preview-title" id="exportPreviewTitle">📐 导出预览</div>
<div class="export-preview-inner" id="exportPreviewInner"></div>
</div>
</div>
<!-- 风格快速切换按钮(放在图表右侧) -->
<div class="theme-buttons-section">
<h3>🎨 风格</h3>
<div class="theme-buttons-grid" id="themeButtonsGrid">
<button class="theme-btn" data-theme="default" onclick="quickSwitchTheme('default')">
<span class="theme-preview" style="background: linear-gradient(135deg, #5470c6, #91cc75)"></span>
<span class="theme-name">默认</span>
</button>
<button class="theme-btn" data-theme="dark" onclick="quickSwitchTheme('dark')">
<span class="theme-preview" style="background: linear-gradient(135deg, #4fc3f7, #81c784)"></span>
<span class="theme-name">深色</span>
</button>
<button class="theme-btn" data-theme="macarons" onclick="quickSwitchTheme('macarons')">
<span class="theme-preview" style="background: linear-gradient(135deg, #2ec7c9, #b6a2de)"></span>
<span class="theme-name">马卡龙</span>
</button>
<button class="theme-btn" data-theme="gradient" onclick="quickSwitchTheme('gradient')">
<span class="theme-preview" style="background: linear-gradient(135deg, #7f7fd5, #91eae4)"></span>
<span class="theme-name">渐变</span>
</button>
<button class="theme-btn" data-theme="retro" onclick="quickSwitchTheme('retro')">
<span class="theme-preview" style="background: linear-gradient(135deg, #d4a5a5, #95b9c7)"></span>
<span class="theme-name">复古</span>
</button>
<button class="theme-btn" data-theme="ocean" onclick="quickSwitchTheme('ocean')">
<span class="theme-preview" style="background: linear-gradient(135deg, #0077b6, #90e0ef)"></span>
<span class="theme-name">海洋</span>
</button>
<button class="theme-btn" data-theme="forest" onclick="quickSwitchTheme('forest')">
<span class="theme-preview" style="background: linear-gradient(135deg, #2d6a4f, #95d5b2)"></span>
<span class="theme-name">森林</span>
</button>
<button class="theme-btn" data-theme="sunset" onclick="quickSwitchTheme('sunset')">
<span class="theme-preview" style="background: linear-gradient(135deg, #e85d04, #ffba08)"></span>
<span class="theme-name">日落</span>
</button>
<button class="theme-btn" data-theme="lavender" onclick="quickSwitchTheme('lavender')">
<span class="theme-preview" style="background: linear-gradient(135deg, #7c3aed, #c4b5fd)"></span>
<span class="theme-name">薰衣草</span>
</button>
<button class="theme-btn" data-theme="minimal" onclick="quickSwitchTheme('minimal')">
<span class="theme-preview" style="background: linear-gradient(135deg, #6b7280, #d1d5db)"></span>
<span class="theme-name">极简</span>
</button>
<button class="theme-btn" data-theme="cherry" onclick="quickSwitchTheme('cherry')">
<span class="theme-preview" style="background: linear-gradient(135deg, #e91e63, #f48fb1)"></span>
<span class="theme-name">樱花</span>
</button>
<button class="theme-btn" data-theme="midnight" onclick="quickSwitchTheme('midnight')">
<span class="theme-preview" style="background: linear-gradient(135deg, #6c63ff, #3d3d5c)"></span>
<span class="theme-name">午夜</span>
</button>
<button class="theme-btn" data-theme="gold" onclick="quickSwitchTheme('gold')">
<span class="theme-preview" style="background: linear-gradient(135deg, #b8860b, #daa520)"></span>
<span class="theme-name">金色</span>
</button>
<button class="theme-btn" data-theme="coral" onclick="quickSwitchTheme('coral')">
<span class="theme-preview" style="background: linear-gradient(135deg, #ff6f61, #ffab91)"></span>
<span class="theme-name">珊瑚</span>
</button>
<button class="theme-btn" data-theme="mint" onclick="quickSwitchTheme('mint')">
<span class="theme-preview" style="background: linear-gradient(135deg, #00bfa5, #80cbc4)"></span>
<span class="theme-name">薄荷</span>
</button>
<button class="theme-btn" data-theme="slate" onclick="quickSwitchTheme('slate')">
<span class="theme-preview" style="background: #475569"></span>
<span class="theme-name">石板灰</span>
</button>
<button class="theme-btn" data-theme="sky" onclick="quickSwitchTheme('sky')">
<span class="theme-preview" style="background: #0ea5e9"></span>
<span class="theme-name">天空蓝</span>
</button>
<button class="theme-btn" data-theme="rose" onclick="quickSwitchTheme('rose')">
<span class="theme-preview" style="background: #e11d48"></span>
<span class="theme-name">玫瑰红</span>
</button>
<button class="theme-btn" data-theme="amber" onclick="quickSwitchTheme('amber')">
<span class="theme-preview" style="background: #f59e0b"></span>
<span class="theme-name">琥珀黄</span>
</button>
<button class="theme-btn" data-theme="emerald" onclick="quickSwitchTheme('emerald')">
<span class="theme-preview" style="background: #10b981"></span>
<span class="theme-name">翡翠绿</span>
</button>
<button class="theme-btn" data-theme="indigo" onclick="quickSwitchTheme('indigo')">
<span class="theme-preview" style="background: #6366f1"></span>
<span class="theme-name">靛蓝色</span>
</button>
<button class="theme-btn" data-theme="stone" onclick="quickSwitchTheme('stone')">
<span class="theme-preview" style="background: #78716c"></span>
<span class="theme-name">石灰白</span>
</button>
</div>
</div>
</div>
</div>
</div>
<script src="app.js"></script>
<!-- 收藏区弹窗 -->
<div class="fav-modal" id="favoritesModal" onclick="if(event.target===this)closeFavorites()">
<div class="fav-modal-box">
<div class="fav-modal-head">
<h2>⭐ 收藏区</h2>
<button class="btn btn-remove" onclick="closeFavorites()" title="关闭"></button>
</div>
<div class="fav-modal-tip"><span id="favCount">0</span> 条 · 点击图片看原图 · 「编辑」在新标签页再次制作</div>
<div class="fav-list" id="favoritesList"></div>
</div>
</div>
</body>
</html>