179 lines
6.4 KiB
HTML
179 lines
6.4 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Multi Chart</title>
|
|
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; padding: 24px; }
|
|
h1 { font-size: 22px; font-weight: 600; margin-bottom: 20px; text-align: center; }
|
|
.grid { display: grid; gap: 16px; }
|
|
.chart-card { border-radius: 12px; padding: 16px; border: 1px solid #e2e8f0; }
|
|
@media (max-width: 768px) { .grid { grid-template-columns: 1fr !important; } }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1 id="title"></h1>
|
|
<div class="grid" id="grid"></div>
|
|
<script>
|
|
(function () {
|
|
var charts = [];
|
|
var COLOR_PALETTE = ['#5470c6','#91cc75','#fac858','#ee6666','#73c0de','#3ba272','#fc8452','#9a60b4'];
|
|
|
|
function baseOption(title) {
|
|
return {
|
|
title: { text: title, left: 'center', top: 8, textStyle: { fontSize: 16, fontWeight: 600 } },
|
|
tooltip: { trigger: 'axis' },
|
|
legend: { top: 36 },
|
|
grid: { top: 80, left: 60, right: 30, bottom: 40 },
|
|
color: COLOR_PALETTE
|
|
};
|
|
}
|
|
function labelCfg(show) { return { show: show, position: 'top', fontSize: 11 }; }
|
|
|
|
var builders = {
|
|
line: function (d) {
|
|
var opt = baseOption(d.title);
|
|
opt.xAxis = { type: 'category', data: d.data.categories || [], boundaryGap: false };
|
|
opt.yAxis = { type: 'value' };
|
|
opt.series = (d.data.series || []).map(function (s) {
|
|
return { name: s.name, type: 'line', data: s.data, smooth: !!d.smooth,
|
|
stack: d.stacked ? 'total' : null, areaStyle: d.stacked ? {} : null,
|
|
label: labelCfg(!!d.show_label) };
|
|
});
|
|
return opt;
|
|
},
|
|
bar: function (d) {
|
|
var opt = baseOption(d.title);
|
|
opt.xAxis = { type: 'category', data: d.data.categories || [] };
|
|
opt.yAxis = { type: 'value' };
|
|
opt.series = (d.data.series || []).map(function (s) {
|
|
return { name: s.name, type: 'bar', data: s.data, stack: d.stacked ? 'total' : null,
|
|
label: labelCfg(!!d.show_label), barMaxWidth: 50 };
|
|
});
|
|
return opt;
|
|
},
|
|
pie: function (d) {
|
|
var opt = baseOption(d.title);
|
|
delete opt.grid;
|
|
opt.tooltip = { trigger: 'item', formatter: '{b}: {c} ({d}%)' };
|
|
var s0 = (d.data.series || [])[0] || {};
|
|
opt.series = [{
|
|
name: s0.name || d.title, type: 'pie', radius: ['40%','70%'], center: ['50%','55%'],
|
|
data: s0.data || [],
|
|
emphasis: { itemStyle: { shadowBlur: 10, shadowOffsetX: 0, shadowColor: 'rgba(0,0,0,0.5)' } },
|
|
label: { show: true, formatter: '{b}: {d}%' },
|
|
itemStyle: { borderRadius: 6, borderColor: '#fff', borderWidth: 2 }
|
|
}];
|
|
return opt;
|
|
},
|
|
radar: function (d) {
|
|
var opt = baseOption(d.title);
|
|
delete opt.grid;
|
|
opt.tooltip = { trigger: 'item' };
|
|
var cats = d.data.categories || [];
|
|
var series = d.data.series || [];
|
|
var maxVals = cats.map(function () { return 0; });
|
|
series.forEach(function (s) {
|
|
s.data.forEach(function (v, i) { if (i < maxVals.length && v > maxVals[i]) maxVals[i] = v; });
|
|
});
|
|
opt.radar = {
|
|
indicator: cats.map(function (c, i) { return { name: c, max: Math.round(maxVals[i] * 1.2) || 100 }; }),
|
|
center: ['50%','55%']
|
|
};
|
|
opt.series = [{
|
|
type: 'radar',
|
|
data: series.map(function (s) { return { value: s.data, name: s.name }; }),
|
|
areaStyle: { opacity: 0.15 }
|
|
}];
|
|
return opt;
|
|
},
|
|
scatter: function (d) {
|
|
var opt = baseOption(d.title);
|
|
opt.tooltip = { trigger: 'item', formatter: '{a}: ({c})' };
|
|
opt.xAxis = { type: 'value', scale: true };
|
|
opt.yAxis = { type: 'value', scale: true };
|
|
opt.series = (d.data.series || []).map(function (s) {
|
|
return { name: s.name, type: 'scatter', data: s.data, symbolSize: 10, label: labelCfg(!!d.show_label) };
|
|
});
|
|
return opt;
|
|
},
|
|
gauge: function (d) {
|
|
var opt = baseOption(d.title);
|
|
delete opt.grid;
|
|
opt.tooltip = { trigger: 'item' };
|
|
var s0 = (d.data.series || [])[0] || {};
|
|
var val = (s0.data && s0.data[0]) || 0;
|
|
if (typeof val !== 'number') val = 0;
|
|
opt.series = [{
|
|
type: 'gauge', center: ['50%','60%'], startAngle: 200, endAngle: -20,
|
|
min: 0, max: 100,
|
|
detail: { formatter: '{value}%', fontSize: 24, offsetCenter: [0,'60%'] },
|
|
data: [{ value: val, name: s0.name || d.title }],
|
|
axisLine: { lineStyle: { width: 20 } },
|
|
progress: { show: true, width: 20 },
|
|
pointer: { show: true }
|
|
}];
|
|
return opt;
|
|
}
|
|
};
|
|
|
|
function render(payload) {
|
|
var isDark = payload.theme === 'dark';
|
|
document.body.style.background = isDark ? '#1a1a2e' : '#f8fafc';
|
|
document.body.style.color = isDark ? '#e0e0e0' : '#0f172a';
|
|
|
|
document.getElementById('title').textContent = payload.title || 'Dashboard';
|
|
|
|
var grid = document.getElementById('grid');
|
|
var columns = payload.columns || 2;
|
|
grid.style.gridTemplateColumns = 'repeat(' + columns + ', 1fr)';
|
|
grid.innerHTML = '';
|
|
|
|
// Dispose previous charts
|
|
charts.forEach(function (c) { c.dispose(); });
|
|
charts = [];
|
|
|
|
var echartsTheme = isDark ? 'dark' : null;
|
|
var cardBg = isDark ? '#252547' : '#ffffff';
|
|
var borderColor = isDark ? '#3a3a5c' : '#e2e8f0';
|
|
|
|
(payload.charts || []).forEach(function (chartDef, idx) {
|
|
var card = document.createElement('div');
|
|
card.className = 'chart-card';
|
|
card.style.background = cardBg;
|
|
card.style.borderColor = borderColor;
|
|
|
|
var el = document.createElement('div');
|
|
el.id = 'chart_' + idx;
|
|
el.style.width = '100%';
|
|
el.style.height = chartDef.height || '350px';
|
|
card.appendChild(el);
|
|
grid.appendChild(card);
|
|
|
|
var c = echarts.init(el, echartsTheme);
|
|
var builder = builders[chartDef.chart_type] || builders.line;
|
|
c.setOption(builder(chartDef));
|
|
charts.push(c);
|
|
});
|
|
|
|
window.addEventListener('resize', function () {
|
|
charts.forEach(function (c) { c.resize(); });
|
|
});
|
|
}
|
|
|
|
window.addEventListener('message', function (event) {
|
|
var msg = event.data;
|
|
if (msg && msg.type === 'mcp-app-data') {
|
|
render(msg.payload);
|
|
}
|
|
});
|
|
|
|
window.parent.postMessage({ type: 'mcp-app-ready' }, '*');
|
|
})();
|
|
</script>
|
|
</body>
|
|
</html>
|