Files
DB-GPT/skills/csv-data-analysis/templates/report_template.html
Aries-ckt ef83851b31 🎉 DB-GPT V0.8.0 - Beta Testing (#2988)
Co-authored-by: lusain <lusain1990@gmail.com>
Co-authored-by: alan.cl <1165243776@qq.com>
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-03-16 11:55:42 +08:00

1180 lines
62 KiB
HTML
Raw Permalink 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>{{REPORT_TITLE}}</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
<style>
:root {
--ink-900: #0f172a;
--ink-700: #334155;
--ink-500: #64748b;
--line: #dbe3ef;
--panel: rgba(255, 255, 255, 0.92);
--panel-strong: #ffffff;
--page-top: #eef4fb;
--page-bottom: #f8fafc;
--blue: #1d4ed8;
--blue-soft: #dbeafe;
--teal: #0f766e;
--amber: #b45309;
--rose: #be123c;
--violet: #6d28d9;
--emerald: #047857;
--shadow: 0 20px 45px rgba(15, 23, 42, 0.06);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", system-ui, sans-serif;
background:
radial-gradient(circle at top left, rgba(37, 99, 235, 0.08), transparent 26%),
radial-gradient(circle at top right, rgba(15, 118, 110, 0.06), transparent 22%),
linear-gradient(180deg, var(--page-top) 0%, var(--page-bottom) 28%, #f8fafc 100%);
color: var(--ink-900);
line-height: 1.65;
-webkit-font-smoothing: antialiased;
}
.page {
max-width: 1220px;
margin: 0 auto;
padding: 20px 18px 32px;
}
.hero {
background: linear-gradient(135deg, rgba(255,255,255,0.94) 0%, rgba(248,250,252,0.96) 100%);
border: 1px solid rgba(219, 227, 239, 0.95);
border-radius: 20px;
padding: 22px 24px;
box-shadow: var(--shadow);
margin-bottom: 16px;
}
.hero-grid {
display: grid;
grid-template-columns: minmax(0, 1.8fr) minmax(320px, 1fr);
gap: 18px;
align-items: stretch;
}
.eyebrow {
display: inline-flex;
align-items: center;
gap: 8px;
color: var(--blue);
font-size: 12px;
font-weight: 700;
letter-spacing: 0.14em;
text-transform: uppercase;
margin-bottom: 10px;
}
.eyebrow::before {
content: "";
width: 26px;
height: 2px;
border-radius: 999px;
background: var(--blue);
}
.hero h1 {
font-size: clamp(28px, 4vw, 38px);
line-height: 1.14;
letter-spacing: -0.03em;
margin-bottom: 10px;
}
.subtitle {
color: var(--ink-700);
font-size: 15px;
max-width: 760px;
margin-bottom: 14px;
}
.hero-note {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 12px;
border-radius: 12px;
background: var(--blue-soft);
color: var(--blue);
font-size: 13px;
font-weight: 600;
}
.summary-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
.summary-card {
border: 1px solid var(--line);
border-radius: 16px;
background: var(--panel-strong);
padding: 14px 14px 12px;
min-height: 92px;
}
.summary-card .label {
color: var(--ink-500);
font-size: 11px;
letter-spacing: 0.08em;
text-transform: uppercase;
font-weight: 700;
margin-bottom: 8px;
}
.summary-card .value {
font-size: 22px;
font-weight: 800;
line-height: 1.1;
margin-bottom: 4px;
}
.summary-card .desc {
font-size: 12px;
color: var(--ink-500);
}
.section {
margin-bottom: 14px;
}
.section-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 10px;
padding: 0 2px;
}
.section-title-wrap {
display: flex;
align-items: center;
gap: 12px;
}
.section-no {
width: 34px;
height: 34px;
border-radius: 12px;
display: inline-flex;
align-items: center;
justify-content: center;
font-weight: 800;
color: #fff;
font-size: 14px;
box-shadow: 0 8px 16px rgba(37, 99, 235, 0.16);
}
.section-title {
font-size: 21px;
font-weight: 800;
letter-spacing: -0.02em;
}
.section-desc {
color: var(--ink-500);
font-size: 13px;
}
.board,
.panel {
background: var(--panel);
border: 1px solid rgba(219, 227, 239, 0.95);
border-radius: 18px;
box-shadow: var(--shadow);
}
.board {
padding: 18px;
}
.panel {
padding: 16px;
}
.panel-title {
font-size: 15px;
font-weight: 700;
color: var(--ink-900);
margin-bottom: 12px;
}
.muted {
color: var(--ink-500);
}
.grid-2 {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
.grid-3 {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
.metric-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 10px;
}
.metric-item {
border: 1px solid var(--line);
border-radius: 14px;
background: #fff;
padding: 14px;
}
.metric-item .label {
font-size: 11px;
color: var(--ink-500);
text-transform: uppercase;
letter-spacing: 0.06em;
font-weight: 700;
margin-bottom: 6px;
}
.metric-item .value {
font-size: 24px;
line-height: 1.1;
font-weight: 800;
}
.metric-item .hint {
margin-top: 5px;
font-size: 12px;
color: var(--ink-500);
}
.chart-box {
border: 1px solid rgba(226, 232, 240, 0.9);
background: #fff;
border-radius: 16px;
padding: 6px;
}
.prose {
color: var(--ink-700);
font-size: 14px;
line-height: 1.78;
}
.prose p { margin-bottom: 0.65em; }
.prose ul, .prose ol { margin-bottom: 0.65em; padding-left: 1.4em; }
.prose li { margin-bottom: 0.28em; }
.prose h3 {
color: var(--ink-900);
font-size: 16px;
font-weight: 800;
margin: 0.9em 0 0.4em;
}
.prose strong { color: var(--blue); }
.insight-box {
margin-top: 12px;
border-left: 4px solid var(--blue);
background: linear-gradient(180deg, #eff6ff 0%, #f8fbff 100%);
border-radius: 0 14px 14px 0;
padding: 14px 16px;
}
.insight-label {
font-size: 11px;
color: var(--blue);
letter-spacing: 0.1em;
text-transform: uppercase;
font-weight: 800;
margin-bottom: 8px;
}
.signal-list {
display: grid;
gap: 10px;
}
.signal-item {
display: flex;
justify-content: space-between;
gap: 12px;
border: 1px solid var(--line);
border-radius: 14px;
background: #fff;
padding: 12px 14px;
}
.signal-item .name {
font-weight: 700;
color: var(--ink-900);
margin-bottom: 4px;
}
.signal-item .meta {
font-size: 12px;
color: var(--ink-500);
}
.badge {
min-width: 64px;
align-self: center;
text-align: center;
padding: 8px 10px;
border-radius: 999px;
background: #eff6ff;
color: var(--blue);
font-size: 12px;
font-weight: 800;
}
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 12px;
}
.data-table th {
background: var(--blue);
color: #fff;
text-align: left;
padding: 10px 11px;
font-size: 11px;
font-weight: 700;
}
.data-table td {
padding: 9px 11px;
border-bottom: 1px solid #edf2f7;
}
.data-table tbody tr:nth-child(even) td { background: #f8fafc; }
.data-table .num {
color: var(--blue);
font-weight: 700;
font-variant-numeric: tabular-nums;
}
.outlier-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
gap: 10px;
}
.outlier-item {
border-radius: 14px;
padding: 12px 14px;
border-left: 4px solid;
background: #fff;
}
.outlier-item .col-name {
font-size: 12px;
color: var(--ink-500);
margin-bottom: 6px;
}
.outlier-item .count {
font-size: 20px;
line-height: 1.1;
font-weight: 800;
}
.footer {
text-align: center;
color: var(--ink-500);
font-size: 12px;
padding: 14px 10px 4px;
}
@media (max-width: 900px) {
.hero-grid, .grid-2, .grid-3 { grid-template-columns: 1fr; }
.summary-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
.page { padding: 14px 12px 24px; }
.hero, .board, .panel { padding: 14px; }
.summary-grid { grid-template-columns: 1fr; }
.metric-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media print {
body { background: #fff; }
.hero, .board, .panel { box-shadow: none; }
.section, .board { break-inside: avoid; }
}
</style>
<meta id="report-lang" data-lang="{{LANG}}">
</head>
<body>
<div class="page">
<section class="hero">
<div class="hero-grid">
<div>
<h1>{{REPORT_TITLE}}</h1>
<p class="subtitle">{{REPORT_SUBTITLE}}</p>
</div>
<div class="summary-grid" id="hero-summary-grid">
<div class="summary-card">
<div class="label">数据规模</div>
<div class="value" id="hero-rows">--</div>
<div class="desc">样本量与字段规模</div>
</div>
<div class="summary-card">
<div class="label">完整度</div>
<div class="value" id="hero-completeness">--</div>
<div class="desc">缺失与重复情况</div>
</div>
<div class="summary-card">
<div class="label">核心指标</div>
<div class="value" id="hero-metric">--</div>
<div class="desc">自动识别的主分析指标</div>
</div>
<div class="summary-card">
<div class="label">异动差距</div>
<div class="value" id="hero-gap">--</div>
<div class="desc">高低分组差异概览</div>
</div>
</div>
</div>
</section>
<section class="section">
<div class="section-head">
<div class="section-title-wrap">
<div class="section-no" style="background:#1d4ed8;">01</div>
<div>
<div class="section-title">报告摘要</div>
<div class="section-desc">快速概览数据规模、重点发现和关键判断</div>
</div>
</div>
</div>
<div class="board">
<div class="prose">{{EXEC_SUMMARY}}</div>
<div class="metric-row" id="overview-metrics" style="margin-top:14px;"></div>
</div>
</section>
<section class="section" id="quality-section">
<div class="section-head">
<div class="section-title-wrap">
<div class="section-no" style="background:#0f766e;">02</div>
<div>
<div class="section-title">数据概览与质量检查</div>
<div class="section-desc">检查数据结构、字段类型、缺失情况与整体质量</div>
</div>
</div>
</div>
<div class="board grid-2">
<div class="panel">
<div class="panel-title">字段缺失率分布</div>
<div class="chart-box" id="missing-rate-chart" style="height: 320px;"></div>
</div>
<div class="panel">
<div class="panel-title">字段类型结构</div>
<div class="chart-box" id="dtype-chart" style="height: 320px;"></div>
</div>
</div>
</section>
<section class="section" id="distribution-section">
<div class="section-head">
<div class="section-title-wrap">
<div class="section-no" style="background:#2563eb;">03</div>
<div>
<div class="section-title">数值指标分布特征</div>
<div class="section-desc">观察核心数值列的分布形态、离散程度和偏态特征</div>
</div>
</div>
</div>
<div class="board">
<div id="distribution-charts" class="grid-2"></div>
<div class="insight-box">
<div class="insight-label">Distribution Insights</div>
<div class="prose">{{DISTRIBUTION_INSIGHTS}}</div>
</div>
</div>
</section>
<section class="section" id="feature-section">
<div class="section-head">
<div class="section-title-wrap">
<div class="section-no" style="background:#d97706;">04</div>
<div>
<div class="section-title">特征分析与结构分析</div>
<div class="section-desc">从分类结构、排名对比和统计画像理解数据特征</div>
</div>
</div>
</div>
<div class="board grid-2">
<div class="panel">
<div class="panel-title">分类维度结构</div>
<div id="categorical-charts" class="grid-2"></div>
<div class="insight-box">
<div class="insight-label">Categorical Insights</div>
<div class="prose">{{CATEGORICAL_INSIGHTS}}</div>
</div>
</div>
<div class="panel">
<div class="panel-title">Top / Bottom 排名特征</div>
<div class="grid-2">
<div class="chart-box" id="top5-chart" style="height: 300px;"></div>
<div class="chart-box" id="bottom5-chart" style="height: 300px;"></div>
</div>
<div class="chart-box" id="segment-comparison-chart" style="height: 310px; margin-top: 12px;"></div>
</div>
</div>
</section>
<section class="section" id="relation-section">
<div class="section-head">
<div class="section-title-wrap">
<div class="section-no" style="background:#be123c;">05</div>
<div>
<div class="section-title">关系分析与异常识别</div>
<div class="section-desc">关注变量联动、异常点分布以及结构性关系特征</div>
</div>
</div>
</div>
<div class="board">
<div class="grid-2">
<div class="panel">
<div class="panel-title">相关性与散点关系</div>
<div class="grid-2">
<div class="chart-box" id="correlation-chart" style="height: 420px;"></div>
<div class="chart-box" id="scatter-chart" style="height: 420px;"></div>
</div>
<div class="insight-box">
<div class="insight-label">Correlation Insights</div>
<div class="prose">{{CORRELATION_INSIGHTS}}</div>
</div>
</div>
<div class="panel" id="boxplot-section">
<div class="panel-title">异常值与箱线分布</div>
<div id="outlier-summary" style="margin-bottom: 12px;"></div>
<div class="chart-box" id="boxplot-chart" style="height: 420px;"></div>
</div>
</div>
</div>
</section>
<section class="section" id="anomaly-section">
<div class="section-head">
<div class="section-title-wrap">
<div class="section-no" style="background:#6d28d9;">06</div>
<div>
<div class="section-title">数据异动概述</div>
<div class="section-desc">围绕核心指标概括波动、分层差距、异常比例和主要异动特征</div>
</div>
</div>
</div>
<div class="board grid-2">
<div class="panel">
<div class="panel-title">异动概览指标</div>
<div class="metric-row" id="anomaly-metrics"></div>
<div class="chart-box" id="anomaly-band-chart" style="height: 300px; margin-top: 12px;"></div>
</div>
<div class="panel">
<div class="panel-title">时间/序列视角(如适用)</div>
<div class="chart-box" id="time-series-chart" style="height: 300px;"></div>
<div class="insight-box">
<div class="insight-label">Time Series / Anomaly Insights</div>
<div class="prose">{{TIME_SERIES_INSIGHTS}}</div>
</div>
</div>
</div>
</section>
<section class="section" id="driver-section">
<div class="section-head">
<div class="section-title-wrap">
<div class="section-no" style="background:#0ea5e9;">07</div>
<div>
<div class="section-title">归因分析模块</div>
<div class="section-desc">在基础分析之上,补充关键驱动维度、组间差异和可能原因线索</div>
</div>
</div>
</div>
<div class="board grid-2">
<div class="panel">
<div class="panel-title">主要驱动维度排序</div>
<div class="chart-box" id="driver-chart" style="height: 360px;"></div>
</div>
<div class="panel">
<div class="panel-title">归因线索摘要</div>
<div class="signal-list" id="driver-signal-list"></div>
</div>
</div>
</section>
<section class="section" id="stats-section">
<div class="section-head">
<div class="section-title-wrap">
<div class="section-no" style="background:#047857;">08</div>
<div>
<div class="section-title">分析结果与统计明细</div>
<div class="section-desc">通过统计表和全局画像沉淀分析结果,作为原因推测的证据基础</div>
</div>
</div>
</div>
<div class="board grid-2" id="stats-layout">
<div class="panel">
<div class="panel-title">特征轮廓雷达</div>
<div class="chart-box" id="radar-chart" style="height: 340px;"></div>
</div>
<div class="panel">
<div class="panel-title">统计摘要表</div>
<div id="stats-table-container" style="overflow-x:auto;"></div>
</div>
</div>
</section>
<section class="section">
<div class="section-head">
<div class="section-title-wrap">
<div class="section-no" style="background:#1e40af;">09</div>
<div>
<div class="section-title">原因推测、总结与建议</div>
<div class="section-desc">对前述现象进行收束,区分证据与推测,并给出行动建议</div>
</div>
</div>
</div>
<div class="board">
<div class="prose">{{CONCLUSIONS}}</div>
</div>
</section>
<div class="footer">Powered by AI Data Analysis Engine · Professional interactive report for CSV exploration</div>
</div>
<script type="application/json" id="echart-binddata">
{{CHART_DATA_JSON}}
</script>
<script>
(function () {
var C = {};
function parseEmbeddedJson(raw) {
var text = (raw || "").trim();
if (!text) return {};
var candidates = [text];
if (text.indexOf('\\"') >= 0) {
candidates.push(
text
.replace(/\\"/g, '"')
.replace(/\\n/g, "\n")
.replace(/\\t/g, "\t")
);
}
if ((text.charAt(0) === '"' && text.charAt(text.length - 1) === '"') || (text.charAt(0) === "'" && text.charAt(text.length - 1) === "'")) {
candidates.push(text.slice(1, -1));
}
for (var i = 0; i < candidates.length; i++) {
try {
var parsed = JSON.parse(candidates[i]);
if (typeof parsed === "string") parsed = JSON.parse(parsed);
if (parsed && typeof parsed === "object") return parsed;
} catch (err) {}
}
return {};
}
try {
var el = document.getElementById("echart-binddata");
if (el) {
C = parseEmbeddedJson(el.textContent || "");
}
} catch (e) {
console.error("Chart data parse error:", e);
}
var charts = [];
var COLORS = ["#2563eb", "#7c3aed", "#059669", "#d97706", "#e11d48", "#0d9488", "#4f46e5", "#0891b2"];
var TT = { backgroundColor: "rgba(15,23,42,0.92)", borderWidth: 0, textStyle: { color: "#fff", fontSize: 12 } };
function byId(id) { return document.getElementById(id); }
function hide(id) { var el = byId(id); if (el) el.style.display = "none"; }
function initChart(id) {
var el = byId(id);
if (!el) return null;
var chart = echarts.init(el);
charts.push(chart);
return chart;
}
function formatNum(value) {
if (value == null || value === "") return "--";
if (typeof value === "number") {
if (Math.abs(value) >= 1000) return value.toLocaleString();
return (Math.round(value * 100) / 100).toString();
}
return String(value);
}
function setText(id, value) {
var el = byId(id);
if (el) el.textContent = value;
}
window.onload = function () {
/* ── i18n: translate hardcoded titles/labels based on LANG ── */
var langMeta = document.getElementById("report-lang");
var lang = (langMeta && langMeta.dataset.lang) ? langMeta.dataset.lang.toLowerCase() : "zh";
if (lang !== "zh" && lang !== "en") lang = "zh";
document.documentElement.lang = lang === "en" ? "en" : "zh-CN";
var I18N = {
/* ── hero summary cards ── */
"数据规模": "Data Scale",
"样本量与字段规模": "Sample size & field count",
"完整度": "Completeness",
"缺失与重复情况": "Missing & duplicate status",
"核心指标": "Key Metric",
"自动识别的主分析指标": "Auto-detected primary metric",
"异动差距": "Anomaly Gap",
"高低分组差异概览": "High/low group difference overview",
/* ── section titles ── */
"报告摘要": "Executive Summary",
"快速概览数据规模、重点发现和关键判断": "Quick overview of data scale, key findings, and critical insights",
"数据概览与质量检查": "Data Overview & Quality Check",
"检查数据结构、字段类型、缺失情况与整体质量": "Inspect data structure, field types, missing values, and overall quality",
"数值指标分布特征": "Numerical Distribution Features",
"观察核心数值列的分布形态、离散程度和偏态特征": "Observe distribution shape, dispersion, and skewness of key numeric columns",
"特征分析与结构分析": "Feature & Structural Analysis",
"从分类结构、排名对比和统计画像理解数据特征": "Understand data characteristics via categorical structure, rankings, and statistical profiles",
"关系分析与异常识别": "Relationship Analysis & Anomaly Detection",
"关注变量联动、异常点分布以及结构性关系特征": "Focus on variable interactions, outlier distribution, and structural relationships",
"数据异动概述": "Data Anomaly Overview",
"围绕核心指标概括波动、分层差距、异常比例和主要异动特征": "Summarize fluctuations, stratification gaps, anomaly ratios, and key anomaly features around the primary metric",
"归因分析模块": "Attribution Analysis Module",
"在基础分析之上,补充关键驱动维度、组间差异和可能原因线索": "Beyond foundational analysis, supplement with key drivers, inter-group differences, and possible causal clues",
"分析结果与统计明细": "Analysis Results & Statistical Details",
"通过统计表和全局画像沉淀分析结果,作为原因推测的证据基础": "Consolidate analysis via statistical tables and global profiles as evidence for root cause inference",
"原因推测、总结与建议": "Root Cause Inference, Conclusions & Recommendations",
"对前述现象进行收束,区分证据与推测,并给出行动建议": "Converge prior findings, distinguish evidence from inference, and provide action recommendations",
/* ── panel titles ── */
"字段缺失率分布": "Field Missing Rate Distribution",
"字段类型结构": "Field Type Structure",
"分类维度结构": "Categorical Dimension Structure",
"Top / Bottom 排名特征": "Top / Bottom Ranking Features",
"相关性与散点关系": "Correlation & Scatter Relationships",
"异常值与箱线分布": "Outliers & Box Plot Distribution",
"异动概览指标": "Anomaly Overview Metrics",
"时间/序列视角(如适用)": "Time / Sequence Perspective (if applicable)",
"主要驱动维度排序": "Key Driver Dimension Ranking",
"归因线索摘要": "Attribution Clue Summary",
"特征轮廓雷达": "Feature Profile Radar",
"统计摘要表": "Statistical Summary Table",
/* ── chart titles (rendered in JS) ── */
"字段缺失率": "Field Missing Rate",
"字段类型分布": "Field Type Distribution",
"相关性热力图": "Correlation Heatmap",
"箱线图对比": "Box Plot Comparison",
"全局特征轮廓": "Global Feature Profile",
/* ── overview metric labels ── */
"样本行数": "Sample Rows",
"字段数量": "Field Count",
"完整率": "Completeness",
"重复记录": "Duplicate Records",
"内存占用": "Memory Usage",
"主分析指标": "Primary Metric",
"总记录数": "Total records",
"总列数": "Total columns",
"缺失越低越好": "Lower missing is better",
"重复行检查": "Duplicate row check",
"数据量级": "Data magnitude",
"后续异动分析基准": "Baseline for anomaly analysis",
/* ── anomaly metric labels ── */
"自动识别": "Auto-detected",
"均值 / 中位数": "Mean / Median",
"中心位置": "Central tendency",
"P10 / P90": "P10 / P90",
"分位带": "Percentile band",
"高低组差距": "High/Low Group Gap",
"高位组均值 - 低位组均值": "High group mean Low group mean",
"异常值占比": "Outlier Ratio",
"主指标异常比例": "Primary metric anomaly ratio",
"组别规模": "Group Size",
"高位组 / 低位组": "High group / Low group",
/* ── misc JS strings ── */
"行": " rows",
"当前数据未识别到可用时间列,已保留文字分析位用于说明分层特征与异动概况。": "No usable time column detected. Text analysis is retained for stratification features and anomaly overview.",
"峰值频次": "Peak Frequency",
"缺失率": "Missing rate",
/* ── driver analysis ── */
"驱动分": "Driver score",
"相关系数": "Correlation",
"组间差异强度": "Inter-group gap ratio",
"高位组均值": "High group mean",
"低位组均值": "Low group mean",
"的驱动线索排序": " driver clue ranking",
/* ── segment comparison ── */
"分组均值对比": "Group mean comparison",
/* ── footer ── */
"Powered by AI Data Analysis Engine · Professional interactive report for CSV exploration": "Powered by AI Data Analysis Engine · Professional interactive report for CSV exploration"
};
function t(zh) {
if (lang === "zh") return zh;
return I18N[zh] !== undefined ? I18N[zh] : zh;
}
/* Translate static DOM text nodes */
if (lang === "en") {
/* summary cards */
var summaryCards = document.querySelectorAll("#hero-summary-grid .summary-card");
var cardTexts = [
["数据规模", "样本量与字段规模"],
["完整度", "缺失与重复情况"],
["核心指标", "自动识别的主分析指标"],
["异动差距", "高低分组差异概览"]
];
summaryCards.forEach(function (card, idx) {
if (cardTexts[idx]) {
var labelEl = card.querySelector(".label");
var descEl = card.querySelector(".desc");
if (labelEl) labelEl.textContent = t(cardTexts[idx][0]);
if (descEl) descEl.textContent = t(cardTexts[idx][1]);
}
});
/* section titles + descriptions */
var sections = document.querySelectorAll(".section-title-wrap");
var sectionTexts = [
["报告摘要", "快速概览数据规模、重点发现和关键判断"],
["数据概览与质量检查", "检查数据结构、字段类型、缺失情况与整体质量"],
["数值指标分布特征", "观察核心数值列的分布形态、离散程度和偏态特征"],
["特征分析与结构分析", "从分类结构、排名对比和统计画像理解数据特征"],
["关系分析与异常识别", "关注变量联动、异常点分布以及结构性关系特征"],
["数据异动概述", "围绕核心指标概括波动、分层差距、异常比例和主要异动特征"],
["归因分析模块", "在基础分析之上,补充关键驱动维度、组间差异和可能原因线索"],
["分析结果与统计明细", "通过统计表和全局画像沉淀分析结果,作为原因推测的证据基础"],
["原因推测、总结与建议", "对前述现象进行收束,区分证据与推测,并给出行动建议"]
];
sections.forEach(function (wrap, idx) {
if (sectionTexts[idx]) {
var titleEl = wrap.querySelector(".section-title");
var descEl = wrap.querySelector(".section-desc");
if (titleEl) titleEl.textContent = t(sectionTexts[idx][0]);
if (descEl) descEl.textContent = t(sectionTexts[idx][1]);
}
});
/* panel titles */
document.querySelectorAll(".panel-title").forEach(function (el) {
var zh = el.textContent.trim();
if (I18N[zh]) el.textContent = I18N[zh];
});
/* footer */
var footer = document.querySelector(".footer");
if (footer) footer.textContent = "Powered by AI Data Analysis Engine · Professional interactive report for CSV exploration";
}
/* ── end i18n ── */
var overview = C.overview || {};
var anomaly = C.anomaly_overview || {};
var primaryMetric = C.primary_metric || anomaly.metric || "--";
setText("hero-rows", overview.rows != null ? overview.rows.toLocaleString() + t("行") : "--");
setText("hero-completeness", overview.missing_pct != null ? (100 - overview.missing_pct).toFixed(1) + "%" : "--");
setText("hero-metric", primaryMetric || "--");
setText("hero-gap", anomaly.gap != null ? formatNum(anomaly.gap) : "--");
if (overview && Object.keys(overview).length > 0) {
var metricWrap = byId("overview-metrics");
if (metricWrap) {
var cards = [
{ label: t("样本行数"), value: overview.rows != null ? overview.rows.toLocaleString() : "--", hint: t("总记录数") },
{ label: t("字段数量"), value: overview.cols != null ? overview.cols : "--", hint: t("总列数") },
{ label: t("完整率"), value: overview.missing_pct != null ? (100 - overview.missing_pct).toFixed(1) + "%" : "--", hint: t("缺失越低越好") },
{ label: t("重复记录"), value: overview.duplicate_rows != null ? overview.duplicate_rows : "--", hint: t("重复行检查") },
{ label: t("内存占用"), value: overview.memory_kb != null ? overview.memory_kb + " KB" : "--", hint: t("数据量级") },
{ label: t("主分析指标"), value: primaryMetric || "--", hint: t("后续异动分析基准") }
];
metricWrap.innerHTML = cards.map(function (item) {
return '<div class="metric-item"><div class="label">' + item.label + '</div><div class="value">' + item.value + '</div><div class="hint">' + item.hint + '</div></div>';
}).join("");
}
}
var dq = C.data_quality;
if (dq && dq.columns && dq.columns.length > 0) {
var missingChart = initChart("missing-rate-chart");
if (missingChart) {
var cols = dq.columns.slice(0, 20);
var rates = dq.missing_rates.slice(0, 20);
missingChart.setOption({
title: { text: t("字段缺失率"), left: "center", top: 6, textStyle: { fontSize: 12, fontWeight: 700, color: "#334155" } },
tooltip: Object.assign({ trigger: "axis", axisPointer: { type: "shadow" }, formatter: function (p) { return p[0].name + '<br/>' + t("缺失率") + ' <b>' + p[0].value + '%</b>'; } }, TT),
grid: { left: "4%", right: "8%", bottom: "6%", top: "16%", containLabel: true },
xAxis: { type: "value", max: 100, axisLabel: { formatter: "{value}%", color: "#64748b" }, splitLine: { lineStyle: { color: "#e2e8f0", type: "dashed" } } },
yAxis: { type: "category", data: cols.slice().reverse(), axisLabel: { color: "#334155", width: 120, overflow: "truncate" } },
series: [{ type: "bar", data: rates.slice().reverse(), barWidth: "56%", itemStyle: { borderRadius: [0, 6, 6, 0], color: function (p) { return p.value > 20 ? "#be123c" : p.value > 5 ? "#b45309" : "#0f766e"; } }, label: { show: true, position: "right", formatter: "{c}%", color: "#64748b" } }]
});
}
var dtypeChart = initChart("dtype-chart");
if (dtypeChart && dq.dtype_summary) {
var dtypeData = [];
Object.keys(dq.dtype_summary).forEach(function (key) { dtypeData.push({ name: key, value: dq.dtype_summary[key] }); });
dtypeChart.setOption({
title: { text: t("字段类型分布"), left: "center", top: 6, textStyle: { fontSize: 12, fontWeight: 700, color: "#334155" } },
tooltip: Object.assign({ trigger: "item", formatter: "{b}: {c} ({d}%)" }, TT),
legend: { bottom: 8, textStyle: { color: "#64748b", fontSize: 11 } },
color: ["#2563eb", "#7c3aed", "#d97706", "#059669"],
series: [{ type: "pie", radius: ["46%", "72%"], center: ["50%", "46%"], data: dtypeData, label: { show: true, formatter: function (p) { return p.name + ' ' + p.percent + '%'; }, fontSize: 11, fontWeight: 700 }, itemStyle: { borderRadius: 8, borderColor: "#fff", borderWidth: 2 } }]
});
}
} else {
hide("quality-section");
}
var dists = C.distributions || {};
var dKeys = Object.keys(dists);
if (dKeys.length > 0) {
var dc = byId("distribution-charts");
if (dc) {
dKeys.forEach(function (col, idx) {
var holder = document.createElement("div");
holder.className = "chart-box";
holder.style.height = "290px";
dc.appendChild(holder);
var chart = echarts.init(holder);
charts.push(chart);
var item = dists[col];
chart.setOption({
title: { text: col, left: "center", top: 6, textStyle: { fontSize: 12, fontWeight: 700, color: "#334155" } },
tooltip: Object.assign({ trigger: "axis", axisPointer: { type: "shadow" } }, TT),
grid: { left: "4%", right: "4%", bottom: "14%", top: "16%", containLabel: true },
xAxis: { type: "category", data: item.bins, axisLabel: { rotate: 35, fontSize: 9, color: "#64748b" }, axisLine: { lineStyle: { color: "#e2e8f0" } } },
yAxis: { type: "value", axisLabel: { color: "#64748b" }, splitLine: { lineStyle: { color: "#e2e8f0", type: "dashed" } } },
series: [{ type: "bar", data: item.counts, barWidth: "58%", itemStyle: { borderRadius: [6, 6, 0, 0], color: COLORS[idx % COLORS.length] } }]
});
});
}
} else {
hide("distribution-section");
}
var catKeys = C.categories ? Object.keys(C.categories) : [];
if (catKeys.length > 0) {
var catWrap = byId("categorical-charts");
if (catWrap) {
catKeys.forEach(function (col) {
var holder = document.createElement("div");
holder.className = "chart-box";
holder.style.height = "300px";
catWrap.appendChild(holder);
var chart = echarts.init(holder);
charts.push(chart);
var values = C.categories[col].labels.map(function (label, i) {
return { name: label, value: C.categories[col].values[i] };
});
chart.setOption({
title: { text: col, left: "center", top: 6, textStyle: { fontSize: 12, fontWeight: 700, color: "#334155" } },
tooltip: Object.assign({ trigger: "item", formatter: "{b}<br/>{c} ({d}%)" }, TT),
legend: { bottom: 0, type: "scroll", textStyle: { fontSize: 10, color: "#64748b" } },
color: COLORS,
series: [{ type: "pie", radius: ["40%", "66%"], center: ["50%", "45%"], data: values, label: { show: true, formatter: "{d}%", fontSize: 10, fontWeight: 700 }, itemStyle: { borderRadius: 6, borderColor: "#fff", borderWidth: 2 } }]
});
});
}
}
var tb = C.top_bottom;
if (tb && tb.top5 && tb.top5.labels && tb.top5.labels.length > 0) {
var topChart = initChart("top5-chart");
if (topChart) {
topChart.setOption({
title: { text: (lang === "en" ? "Top 5 (by " + tb.rank_col + ")" : "Top 5按 " + tb.rank_col + ""), left: "center", top: 6, textStyle: { fontSize: 12, fontWeight: 700, color: "#334155" } },
tooltip: Object.assign({ trigger: "axis", axisPointer: { type: "shadow" } }, TT),
grid: { left: "4%", right: "14%", bottom: "5%", top: "15%", containLabel: true },
xAxis: { type: "value", axisLabel: { color: "#64748b" }, splitLine: { lineStyle: { color: "#e2e8f0", type: "dashed" } } },
yAxis: { type: "category", data: tb.top5.labels.slice().reverse(), axisLabel: { color: "#334155", width: 100, overflow: "truncate" } },
series: [{ type: "bar", data: tb.top5.values.slice().reverse(), barWidth: "52%", itemStyle: { borderRadius: [0, 6, 6, 0], color: "#2563eb" }, label: { show: true, position: "right", color: "#1d4ed8" } }]
});
}
var bottomChart = initChart("bottom5-chart");
if (bottomChart) {
bottomChart.setOption({
title: { text: (lang === "en" ? "Bottom 5 (by " + tb.rank_col + ")" : "Bottom 5按 " + tb.rank_col + ""), left: "center", top: 6, textStyle: { fontSize: 12, fontWeight: 700, color: "#334155" } },
tooltip: Object.assign({ trigger: "axis", axisPointer: { type: "shadow" } }, TT),
grid: { left: "4%", right: "14%", bottom: "5%", top: "15%", containLabel: true },
xAxis: { type: "value", axisLabel: { color: "#64748b" }, splitLine: { lineStyle: { color: "#e2e8f0", type: "dashed" } } },
yAxis: { type: "category", data: tb.bottom5.labels.slice().reverse(), axisLabel: { color: "#334155", width: 100, overflow: "truncate" } },
series: [{ type: "bar", data: tb.bottom5.values.slice().reverse(), barWidth: "52%", itemStyle: { borderRadius: [0, 6, 6, 0], color: "#e11d48" }, label: { show: true, position: "right", color: "#be123c" } }]
});
}
}
var seg = C.segment_comparison || {};
if (seg.labels && seg.labels.length > 0) {
var segmentChart = initChart("segment-comparison-chart");
if (segmentChart) {
segmentChart.setOption({
title: { text: seg.dimension + (lang === "en" ? " group mean comparison (" + seg.metric + ")" : " 分组均值对比(" + seg.metric + ""), left: "center", top: 6, textStyle: { fontSize: 12, fontWeight: 700, color: "#334155" } },
tooltip: Object.assign({ trigger: "axis", axisPointer: { type: "shadow" } }, TT),
grid: { left: "4%", right: "6%", bottom: "15%", top: "18%", containLabel: true },
xAxis: { type: "category", data: seg.labels, axisLabel: { rotate: 25, color: "#64748b" } },
yAxis: { type: "value", axisLabel: { color: "#64748b" }, splitLine: { lineStyle: { color: "#e2e8f0", type: "dashed" } } },
series: [{ type: "bar", data: seg.values, barWidth: "48%", itemStyle: { borderRadius: [6, 6, 0, 0], color: "#0f766e" }, label: { show: true, position: "top", color: "#0f766e" } }]
});
}
} else {
var segEl = byId("segment-comparison-chart");
if (segEl) segEl.style.display = "none";
}
var corr = C.correlations;
if (corr && corr.cols && corr.cols.length > 1) {
var corrChart = initChart("correlation-chart");
if (corrChart) {
corrChart.setOption({
title: { text: t("相关性热力图"), left: "center", top: 6, textStyle: { fontSize: 12, fontWeight: 700, color: "#334155" } },
tooltip: Object.assign({ position: "top", formatter: function (p) { return corr.cols[p.data[0]] + ' / ' + corr.cols[p.data[1]] + '<br/>r = <b>' + p.data[2] + '</b>'; } }, TT),
grid: { height: "60%", top: "10%", bottom: "22%", left: "18%", right: "5%" },
xAxis: { type: "category", data: corr.cols, splitArea: { show: true }, axisLabel: { rotate: 35, color: "#64748b", fontSize: 9 } },
yAxis: { type: "category", data: corr.cols, splitArea: { show: true }, axisLabel: { color: "#64748b", fontSize: 9 } },
visualMap: { min: -1, max: 1, calculable: true, orient: "horizontal", left: "center", bottom: "2%", itemWidth: 16, itemHeight: 100, textStyle: { color: "#64748b", fontSize: 10 }, inRange: { color: ["#1e3a8a", "#60a5fa", "#f8fafc", "#f59e0b", "#991b1b"] } },
series: [{ type: "heatmap", data: corr.data, label: { show: corr.cols.length <= 10, fontSize: 9, fontWeight: 700, formatter: function (p) { return p.data[2].toFixed(2); } } }]
});
}
var scatter = C.scatter;
var scatterChart = initChart("scatter-chart");
if (scatterChart && scatter && scatter.x && scatter.x.length > 0) {
scatterChart.setOption({
title: { text: scatter.x_name + ' vs ' + scatter.y_name, left: "center", top: 6, textStyle: { fontSize: 12, fontWeight: 700, color: "#334155" } },
tooltip: Object.assign({ formatter: function (p) { return scatter.x_name + ': ' + p.data[0] + '<br/>' + scatter.y_name + ': ' + p.data[1]; } }, TT),
grid: { left: "12%", right: "5%", bottom: "12%", top: "16%" },
xAxis: { type: "value", name: scatter.x_name, axisLabel: { color: "#64748b" }, splitLine: { lineStyle: { color: "#e2e8f0", type: "dashed" } }, scale: true },
yAxis: { type: "value", name: scatter.y_name, axisLabel: { color: "#64748b" }, splitLine: { lineStyle: { color: "#e2e8f0", type: "dashed" } }, scale: true },
series: [{ type: "scatter", data: scatter.x.map(function (x, i) { return [x, scatter.y[i]]; }), symbolSize: 7, itemStyle: { color: "#2563eb", opacity: 0.65 } }]
});
} else {
hide("scatter-chart");
}
} else {
hide("relation-section");
}
var bpKeys = C.box_plots ? Object.keys(C.box_plots) : [];
var outlierData = C.outliers || {};
if (bpKeys.length > 0) {
var outlierWrap = byId("outlier-summary");
if (outlierWrap) {
outlierWrap.innerHTML = '<div class="outlier-grid">' + bpKeys.map(function (col) {
var o = outlierData[col] || { count: 0, pct: 0 };
var bg = o.count > 0 ? '#fef2f2' : '#ecfdf5';
var bc = o.count > 0 ? '#be123c' : '#047857';
var tc = o.count > 0 ? '#9f1239' : '#065f46';
return '<div class="outlier-item" style="background:' + bg + ';border-left-color:' + bc + '"><div class="col-name">' + col + '</div><div class="count" style="color:' + tc + '">' + o.count + ' <span style="font-size:12px;font-weight:600;color:#64748b;">(' + o.pct + '%)</span></div></div>';
}).join('') + '</div>';
}
var boxChart = initChart("boxplot-chart");
if (boxChart) {
var boxData = [];
var outlierPoints = [];
bpKeys.slice(0, 8).forEach(function (col, idx) {
var bp = C.box_plots[col];
boxData.push([bp.lower_fence, bp.q1, bp.median, bp.q3, bp.upper_fence]);
(bp.outliers || []).forEach(function (value) { outlierPoints.push([idx, value]); });
});
boxChart.setOption({
title: { text: t("箱线图对比"), left: "center", top: 6, textStyle: { fontSize: 12, fontWeight: 700, color: "#334155" } },
tooltip: Object.assign({ trigger: "item" }, TT),
grid: { left: "10%", right: "5%", bottom: "12%", top: "16%" },
xAxis: { type: "category", data: bpKeys.slice(0, 8), axisLabel: { rotate: 25, color: "#64748b" } },
yAxis: { type: "value", scale: true, axisLabel: { color: "#64748b" }, splitLine: { lineStyle: { color: "#e2e8f0", type: "dashed" } } },
series: [
{ type: "boxplot", data: boxData, itemStyle: { color: "#dbeafe", borderColor: "#2563eb", borderWidth: 2 } },
{ type: "scatter", data: outlierPoints, symbolSize: 6, itemStyle: { color: "#e11d48", opacity: 0.68 } }
]
});
}
}
if (anomaly && anomaly.metric) {
var anomalyWrap = byId("anomaly-metrics");
if (anomalyWrap) {
var anomalyCards = [
{ label: t("核心指标"), value: anomaly.metric, hint: t("自动识别") },
{ label: t("均值 / 中位数"), value: formatNum(anomaly.mean) + ' / ' + formatNum(anomaly.median), hint: t("中心位置") },
{ label: t("P10 / P90"), value: formatNum(anomaly.q10) + ' / ' + formatNum(anomaly.q90), hint: t("分位带") },
{ label: t("高低组差距"), value: formatNum(anomaly.gap), hint: t("高位组均值 - 低位组均值") },
{ label: t("异常值占比"), value: formatNum(anomaly.outlier_pct) + '%', hint: t("主指标异常比例") },
{ label: t("组别规模"), value: formatNum(anomaly.top_group_size) + ' / ' + formatNum(anomaly.bottom_group_size), hint: t("高位组 / 低位组") }
];
anomalyWrap.innerHTML = anomalyCards.map(function (item) {
return '<div class="metric-item"><div class="label">' + item.label + '</div><div class="value">' + item.value + '</div><div class="hint">' + item.hint + '</div></div>';
}).join("");
}
var anomalyChart = initChart("anomaly-band-chart");
if (anomalyChart) {
anomalyChart.setOption({
title: { text: anomaly.metric + (lang === "en" ? " percentile band distribution" : " 分位带样本分布"), left: "center", top: 6, textStyle: { fontSize: 12, fontWeight: 700, color: "#334155" } },
tooltip: Object.assign({ trigger: "axis", axisPointer: { type: "shadow" } }, TT),
grid: { left: "5%", right: "5%", bottom: "12%", top: "16%", containLabel: true },
xAxis: { type: "category", data: anomaly.band_labels || [], axisLabel: { color: "#64748b" } },
yAxis: { type: "value", axisLabel: { color: "#64748b" }, splitLine: { lineStyle: { color: "#e2e8f0", type: "dashed" } } },
series: [{ type: "bar", data: anomaly.band_values || [], barWidth: "46%", itemStyle: { borderRadius: [6, 6, 0, 0], color: "#6d28d9" }, label: { show: true, position: "top", color: "#6d28d9" } }]
});
}
} else {
hide("anomaly-section");
}
var timeSeries = C.time_series || {};
if (timeSeries.dates && timeSeries.dates.length > 0) {
var tsChart = initChart("time-series-chart");
if (tsChart) {
tsChart.setOption({
title: { text: (timeSeries.name || (lang === "en" ? "Metric" : "指标")) + (lang === "en" ? " Trend" : " 趋势线"), left: "center", top: 6, textStyle: { fontSize: 12, fontWeight: 700, color: "#334155" } },
tooltip: Object.assign({ trigger: "axis" }, TT),
grid: { left: "4%", right: "5%", bottom: "14%", top: "16%", containLabel: true },
xAxis: { type: "category", data: timeSeries.dates, boundaryGap: false, axisLabel: { rotate: 28, color: "#64748b", fontSize: 9 } },
yAxis: { type: "value", scale: true, axisLabel: { color: "#64748b" }, splitLine: { lineStyle: { color: "#e2e8f0", type: "dashed" } } },
series: [{ type: "line", data: timeSeries.values, smooth: true, symbolSize: 5, itemStyle: { color: "#2563eb" }, lineStyle: { width: 2.5, color: "#2563eb" }, areaStyle: { color: "rgba(37,99,235,0.08)" } }]
});
}
} else {
var tsEl = byId("time-series-chart");
if (tsEl) {
tsEl.innerHTML = '<div style="height:100%;display:flex;align-items:center;justify-content:center;color:#64748b;font-size:13px;">' + t("当前数据未识别到可用时间列,已保留文字分析位用于说明分层特征与异动概况。") + '</div>';
}
}
var driver = C.driver_analysis || {};
if (driver.items && driver.items.length > 0) {
var driverChart = initChart("driver-chart");
if (driverChart) {
var items = driver.items.slice(0, 8);
driverChart.setOption({
title: { text: (driver.metric || t('核心指标')) + t('的驱动线索排序'), left: "center", top: 6, textStyle: { fontSize: 12, fontWeight: 700, color: "#334155" } },
tooltip: Object.assign({ trigger: "axis", axisPointer: { type: "shadow" }, formatter: function (p) { var item = items[p[0].dataIndex]; return item.name + '<br/>' + t('驱动分') + ' <b>' + item.score + '</b><br/>' + t('相关系数') + ' ' + item.corr + '<br/>' + t('组间差异强度') + ' ' + item.gap_ratio; } }, TT),
grid: { left: "6%", right: "10%", bottom: "6%", top: "14%", containLabel: true },
xAxis: { type: "value", max: 100, axisLabel: { color: "#64748b" }, splitLine: { lineStyle: { color: "#e2e8f0", type: "dashed" } } },
yAxis: { type: "category", data: items.map(function (item) { return item.name; }).reverse(), axisLabel: { color: "#334155", width: 120, overflow: "truncate" } },
series: [{ type: "bar", data: items.map(function (item) { return item.score; }).reverse(), barWidth: "56%", itemStyle: { borderRadius: [0, 6, 6, 0], color: "#0ea5e9" }, label: { show: true, position: "right", color: "#0369a1" } }]
});
}
var signalList = byId("driver-signal-list");
if (signalList) {
signalList.innerHTML = driver.items.slice(0, 5).map(function (item) {
return '<div class="signal-item"><div><div class="name">' + item.name + '</div><div class="meta">' + t('相关系数') + ' ' + item.corr + ' · ' + t('高位组均值') + ' ' + formatNum(item.top_mean) + ' · ' + t('低位组均值') + ' ' + formatNum(item.bottom_mean) + ' · ' + t('组间差异强度') + ' ' + formatNum(item.gap_ratio) + '</div></div><div class="badge">' + item.score + '</div></div>';
}).join("");
}
} else {
hide("driver-section");
}
var radarChart = initChart("radar-chart");
var radarOk = false;
if (radarChart && C.numeric_cols && C.numeric_cols.length >= 3 && C.distributions) {
var indicators = [];
var values = [];
C.numeric_cols.slice(0, 8).forEach(function (col) {
var dist = C.distributions[col];
if (dist && dist.counts) {
var total = dist.counts.reduce(function (a, b) { return a + b; }, 0);
indicators.push({ name: col.length > 12 ? col.slice(0, 12) + '...' : col, max: total || 1 });
values.push(Math.max.apply(null, dist.counts));
}
});
if (indicators.length >= 3) {
radarOk = true;
radarChart.setOption({
title: { text: t("全局特征轮廓"), left: "center", top: 6, textStyle: { fontSize: 12, fontWeight: 700, color: "#334155" } },
tooltip: Object.assign({}, TT),
radar: { indicator: indicators, axisName: { color: "#64748b", fontSize: 9 }, splitArea: { areaStyle: { color: ["#fff", "#f8fafc", "#eef2ff"] } }, splitLine: { lineStyle: { color: "#dbeafe" } } },
series: [{ type: "radar", data: [{ value: values, name: t("峰值频次"), areaStyle: { color: "rgba(37,99,235,0.12)" }, lineStyle: { color: "#2563eb", width: 2 }, itemStyle: { color: "#2563eb" } }] }]
});
}
}
if (!radarOk) {
hide("radar-chart");
var statsLayout = byId("stats-layout");
if (statsLayout) statsLayout.style.gridTemplateColumns = "1fr";
}
var stats = C.stats_table;
var tableWrap = byId("stats-table-container");
if (tableWrap && stats && stats.headers && stats.rows && stats.rows.length > 0) {
var html = '<table class="data-table"><thead><tr>';
stats.headers.forEach(function (h) { html += '<th>' + h + '</th>'; });
html += '</tr></thead><tbody>';
stats.rows.forEach(function (row) {
html += '<tr>';
row.forEach(function (cell, idx) {
html += '<td' + (idx === 0 ? '' : ' class="num"') + '>' + formatNum(cell) + '</td>';
});
html += '</tr>';
});
html += '</tbody></table>';
tableWrap.innerHTML = html;
} else if (!radarOk) {
hide("stats-section");
}
};
window.addEventListener("resize", function () {
charts.forEach(function (chart) {
if (chart) chart.resize();
});
});
})();
</script>
</body>
</html>