feat: 多图合并支持多行多列网格排布 + 统一'多图合并'文案
- 合并模式新增'多行多列'排布(radio),可选每行列数(gridCols), 子图按最大单元格 contain 居中排列,列数变化实时重排 - 后端 /api/combine 支持 direction=grid + cols 参数 - 页面所有'双图合并'文案统一改为'多图合并'
This commit is contained in:
@@ -1016,7 +1016,7 @@ function quickSwitchTheme(theme) {
|
||||
document.getElementById('tableTheme').value = theme;
|
||||
updateTable();
|
||||
}
|
||||
// 双图合并模式:仅切换按钮高亮,不联动(两图各自独立主题)
|
||||
// 多图合并模式:仅切换按钮高亮,不联动(各图独立主题)
|
||||
}
|
||||
|
||||
// ===== 同步风格按钮状态(下拉框变更时) =====
|
||||
@@ -1039,7 +1039,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
initThemeButtons();
|
||||
});
|
||||
|
||||
// ===== 双图合并 =====
|
||||
// ===== 多图合并 =====
|
||||
let combinedCanvas = null;
|
||||
|
||||
// 构建单个子图的 echarts option(不依赖全局状态,双图模式专用)
|
||||
@@ -1245,6 +1245,13 @@ function removeCombineChart(i) {
|
||||
generateCombine();
|
||||
}
|
||||
|
||||
// 多行多列切换:显示/隐藏列数输入
|
||||
function onCombineGridToggle() {
|
||||
const grid = document.querySelector('input[name="combineDirection"]:checked').value === 'grid';
|
||||
document.getElementById('gridColsGroup').style.display = grid ? 'block' : 'none';
|
||||
generateCombine();
|
||||
}
|
||||
|
||||
// 生成合并图(多图)
|
||||
function generateCombine() {
|
||||
const validCharts = combineCharts.filter(c => c.data && c.data.trim());
|
||||
@@ -1284,34 +1291,55 @@ function generateCombine() {
|
||||
const m = measureCombineChart(c.data);
|
||||
return renderOne(c, m.w, m.h);
|
||||
})).then(canvases => {
|
||||
let W, H, scaled;
|
||||
if (direction === 'vertical') {
|
||||
// 竖排:等宽
|
||||
const tw = Math.max(...canvases.map(c => c.width));
|
||||
scaled = canvases.map(c => ({ c, w: tw, h: Math.round(c.height * tw / c.width) }));
|
||||
W = tw;
|
||||
H = scaled.reduce((s, x) => s + x.h, 0) + gap * (scaled.length - 1);
|
||||
} else {
|
||||
// 横排:等高
|
||||
const th = Math.max(...canvases.map(c => c.height));
|
||||
scaled = canvases.map(c => ({ c, w: Math.round(c.width * th / c.height), h: th }));
|
||||
W = scaled.reduce((s, x) => s + x.w, 0) + gap * (scaled.length - 1);
|
||||
H = th;
|
||||
}
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = W;
|
||||
canvas.height = H;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
|
||||
if (direction === 'vertical') {
|
||||
let y = 0;
|
||||
scaled.forEach(x => { ctx.drawImage(x.c, 0, y, x.w, x.h); y += x.h + gap; });
|
||||
if (direction === 'grid') {
|
||||
// 多行多列网格:所有子图统一 contain 到最大单元格,按列填充
|
||||
const cols = Math.max(1, parseInt(document.getElementById('gridCols').value) || 2);
|
||||
const rows = Math.ceil(canvases.length / cols);
|
||||
const cellW = Math.max(...canvases.map(c => c.width));
|
||||
const cellH = Math.max(...canvases.map(c => c.height));
|
||||
canvas.width = cols * cellW + (cols - 1) * gap;
|
||||
canvas.height = rows * cellH + (rows - 1) * gap;
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
canvases.forEach((c, i) => {
|
||||
const r = Math.floor(i / cols), col = i % cols;
|
||||
const scale = Math.min(cellW / c.width, cellH / c.height);
|
||||
const dw = Math.round(c.width * scale), dh = Math.round(c.height * scale);
|
||||
const x = col * (cellW + gap) + Math.round((cellW - dw) / 2);
|
||||
const y = r * (cellH + gap) + Math.round((cellH - dh) / 2);
|
||||
ctx.drawImage(c, x, y, dw, dh);
|
||||
});
|
||||
} else {
|
||||
let x = 0;
|
||||
scaled.forEach(s => { ctx.drawImage(s.c, x, 0, s.w, s.h); x += s.w + gap; });
|
||||
let W, H, scaled;
|
||||
if (direction === 'vertical') {
|
||||
// 竖排:等宽
|
||||
const tw = Math.max(...canvases.map(c => c.width));
|
||||
scaled = canvases.map(c => ({ c, w: tw, h: Math.round(c.height * tw / c.width) }));
|
||||
W = tw;
|
||||
H = scaled.reduce((s, x) => s + x.h, 0) + gap * (scaled.length - 1);
|
||||
} else {
|
||||
// 横排:等高
|
||||
const th = Math.max(...canvases.map(c => c.height));
|
||||
scaled = canvases.map(c => ({ c, w: Math.round(c.width * th / c.height), h: th }));
|
||||
W = scaled.reduce((s, x) => s + x.w, 0) + gap * (scaled.length - 1);
|
||||
H = th;
|
||||
}
|
||||
|
||||
canvas.width = W;
|
||||
canvas.height = H;
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fillRect(0, 0, W, H);
|
||||
|
||||
if (direction === 'vertical') {
|
||||
let y = 0;
|
||||
scaled.forEach(x => { ctx.drawImage(x.c, 0, y, x.w, x.h); y += x.h + gap; });
|
||||
} else {
|
||||
let x = 0;
|
||||
scaled.forEach(s => { ctx.drawImage(s.c, x, 0, s.w, s.h); x += s.w + gap; });
|
||||
}
|
||||
}
|
||||
|
||||
combinedCanvas = canvas;
|
||||
|
||||
Reference in New Issue
Block a user