fix: csv data i18n

This commit is contained in:
alan.cl
2026-03-15 15:09:05 +08:00
parent 4d968dad81
commit ded32d2503
2 changed files with 193 additions and 35 deletions

View File

@@ -37,15 +37,15 @@ Read the "Statistical Summary" obtained in Step 1, and reason about the business
**Critical Rules (Must Follow):**
1. **You must set `template_path`** to `csv-data-analysis/templates/report_template.html`. The template has built-in complete ECharts rendering JavaScript code and all section titles and footer text. You only need to fill in 8 content placeholders via the `data` parameter. **Never write or modify any JavaScript chart rendering code yourself.**
1. **You must set `template_path`** to `csv-data-analysis/templates/report_template.html`. The template has built-in complete ECharts rendering JavaScript code and all section titles and footer text. You only need to fill in 9 content placeholders via the `data` parameter. **Never write or modify any JavaScript chart rendering code yourself.**
2. **Marker data blocks are automatically injected by the backend** — you must not pass them in `data`. The backend automatically extracts content from `###KEY_START###...###KEY_END###` markers in the script output and injects it into the template; in this skill, this is primarily `CHART_DATA_JSON`.
3. **`*_INSIGHTS`, `EXEC_SUMMARY`, and `CONCLUSIONS`** must use HTML formatting (e.g., `<p>`, `<ul>`, `<li>`, `<strong>`, `<ol>`) to ensure proper layout. These are deep business insights you write based on the statistical summary.
4. **The output language must match the user's input language.**
4. **The output language must match the user's input language.** You must also pass the `LANG` placeholder (`"en"` or `"zh"`) so that the template's hardcoded section titles, labels, and footer text are displayed in the matching language. Detect language from the user's query: if the user writes in English, set `LANG` to `"en"`; if the user writes in Chinese, set `LANG` to `"zh"`. Default to `"zh"` when uncertain.
5. **Pass exactly 8 placeholders — no more, no less.** Auto-injected marker fields like `CHART_DATA_JSON` are handled by the backend and should not be passed by you. The template already hardcodes all section titles (Distribution Analysis, Correlation Analysis, etc.), insight box titles ("Insights"), and footer text — you do not need to pass these.
5. **Pass exactly 9 placeholders — no more, no less.** Auto-injected marker fields like `CHART_DATA_JSON` are handled by the backend and should not be passed by you. The template already hardcodes all section titles (Distribution Analysis, Correlation Analysis, etc.), insight box titles ("Insights"), and footer text — you do not need to pass these (the template will automatically translate them based on the `LANG` placeholder).
6. **Insight content must be substantive.** Each insight module should cover 4 layers of information: `observation`, `possible causes`, `business impact`, and `action recommendations`. Do not merely restate statistical values or write only a few vague conclusions.
@@ -56,6 +56,7 @@ Read the "Statistical Summary" obtained in Step 1, and reason about the business
{
"template_path": "csv-data-analysis/templates/report_template.html",
"data": {
"LANG": "en",
"REPORT_TITLE": "Sales Dataset Deep Analysis Report",
"REPORT_SUBTITLE": "Multi-dimensional Data Feature & Business Insight Mining",
"EXEC_SUMMARY": "<p>This dataset contains 1,000 rows and 5 columns with good data completeness. Key findings include:</p><ul><li><strong>Audience Distribution:</strong> Primarily concentrated in the 25-35 age group...</li></ul>",
@@ -73,14 +74,15 @@ Read the "Statistical Summary" obtained in Step 1, and reason about the business
> - Do NOT add any JavaScript code in `data`
> - Do NOT omit the `template_path` parameter (omitting template_path will prevent charts from rendering!)
> - Do NOT return static PNG images — this tool has been fully upgraded to ECharts dynamic frontend rendering
> - Do NOT pass non-existent placeholders (the template only has the following 8 text placeholders + 1 auto-injected CHART_DATA_JSON; other names will be ignored)
> - Do NOT pass non-existent placeholders (the template only has the following 9 text placeholders + 1 auto-injected CHART_DATA_JSON; other names will be ignored)
## Placeholder Reference (8 total, passed by LLM via data)
## Placeholder Reference (9 total, passed by LLM via data)
The placeholders you need to fill in the template are as follows:
| Placeholder | Type | Required | Description |
|---|---|---|---|
| `LANG` | Text | Yes | Report language: `"en"` for English, `"zh"` for Chinese. Determines all section titles, labels, and footer text language. Detect from user's input language; default `"zh"` |
| `REPORT_TITLE` | Text | Yes | Report title, e.g., "Sales Dataset Deep Analysis Report" |
| `REPORT_SUBTITLE` | Text | Yes | Report subtitle, e.g., "Multi-dimensional Data Feature & Business Insight Mining" |
| `EXEC_SUMMARY` | HTML | Yes | Executive summary: overview of data scale, key findings, and conclusion preview |
@@ -90,7 +92,7 @@ The placeholders you need to fill in the template are as follows:
| `TIME_SERIES_INSIGHTS` | HTML | Yes | Supplementary interpretation for the data anomaly overview section: discuss trends if time columns exist; discuss stratification differences and anomaly patterns if no time columns |
| `CONCLUSIONS` | HTML | Yes | Root cause inference, conclusions & recommendations body; must distinguish between "data evidence" and "reasonable speculation" |
> **Note:** `csv_analyzer.py` includes `###CHART_DATA_JSON_START###...###CHART_DATA_JSON_END###` marker data blocks in its output. The backend automatically extracts and injects these into the template — they should not be passed in `data`. All section titles in the template (e.g., "Distribution Analysis", "Correlation Analysis", "Conclusions & Recommendations"), insight box titles ("Insights"), and footer text are hardcoded in the HTML and do not need to be passed via placeholders.
> **Note:** `csv_analyzer.py` includes `###CHART_DATA_JSON_START###...###CHART_DATA_JSON_END###` marker data blocks in its output. The backend automatically extracts and injects these into the template — they should not be passed in `data`. All section titles in the template (e.g., "Distribution Analysis", "Correlation Analysis", "Conclusions & Recommendations"), insight box titles ("Insights"), and footer text are hardcoded in the HTML and are automatically translated based on the `LANG` placeholder — they do not need to be passed via placeholders.
## Why Choose This Tool?

View File

@@ -359,6 +359,7 @@
.section, .board { break-inside: avoid; }
}
</style>
<meta id="report-lang" data-lang="{{LANG}}">
</head>
<body>
<div class="page">
@@ -669,10 +670,165 @@
}
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() + " 行" : "--");
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) : "--");
@@ -681,12 +837,12 @@
var metricWrap = byId("overview-metrics");
if (metricWrap) {
var cards = [
{ label: "样本行数", value: overview.rows != null ? overview.rows.toLocaleString() : "--", hint: "总记录数" },
{ label: "字段数量", value: overview.cols != null ? overview.cols : "--", hint: "总列数" },
{ label: "完整率", value: overview.missing_pct != null ? (100 - overview.missing_pct).toFixed(1) + "%" : "--", hint: "缺失越低越好" },
{ label: "重复记录", value: overview.duplicate_rows != null ? overview.duplicate_rows : "--", hint: "重复行检查" },
{ label: "内存占用", value: overview.memory_kb != null ? overview.memory_kb + " KB" : "--", hint: "数据量级" },
{ label: "主分析指标", value: primaryMetric || "--", hint: "后续异动分析基准" }
{ 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>';
@@ -701,8 +857,8 @@
var cols = dq.columns.slice(0, 20);
var rates = dq.missing_rates.slice(0, 20);
missingChart.setOption({
title: { text: "字段缺失率", 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/>缺失率 <b>' + p[0].value + '%</b>'; } }, TT),
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" } },
@@ -714,7 +870,7 @@
var dtypeData = [];
Object.keys(dq.dtype_summary).forEach(function (key) { dtypeData.push({ name: key, value: dq.dtype_summary[key] }); });
dtypeChart.setOption({
title: { text: "字段类型分布", left: "center", top: 6, textStyle: { fontSize: 12, fontWeight: 700, color: "#334155" } },
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"],
@@ -782,7 +938,7 @@
var topChart = initChart("top5-chart");
if (topChart) {
topChart.setOption({
title: { text: "Top 5按 " + tb.rank_col + "", left: "center", top: 6, textStyle: { fontSize: 12, fontWeight: 700, color: "#334155" } },
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" } } },
@@ -793,7 +949,7 @@
var bottomChart = initChart("bottom5-chart");
if (bottomChart) {
bottomChart.setOption({
title: { text: "Bottom 5按 " + tb.rank_col + "", left: "center", top: 6, textStyle: { fontSize: 12, fontWeight: 700, color: "#334155" } },
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" } } },
@@ -808,7 +964,7 @@
var segmentChart = initChart("segment-comparison-chart");
if (segmentChart) {
segmentChart.setOption({
title: { text: seg.dimension + " 分组均值对比(" + seg.metric + "", left: "center", top: 6, textStyle: { fontSize: 12, fontWeight: 700, color: "#334155" } },
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" } },
@@ -826,7 +982,7 @@
var corrChart = initChart("correlation-chart");
if (corrChart) {
corrChart.setOption({
title: { text: "相关性热力图", left: "center", top: 6, textStyle: { fontSize: 12, fontWeight: 700, color: "#334155" } },
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 } },
@@ -876,7 +1032,7 @@
(bp.outliers || []).forEach(function (value) { outlierPoints.push([idx, value]); });
});
boxChart.setOption({
title: { text: "箱线图对比", left: "center", top: 6, textStyle: { fontSize: 12, fontWeight: 700, color: "#334155" } },
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" } },
@@ -893,12 +1049,12 @@
var anomalyWrap = byId("anomaly-metrics");
if (anomalyWrap) {
var anomalyCards = [
{ label: "核心指标", value: anomaly.metric, hint: "自动识别" },
{ label: "均值 / 中位数", value: formatNum(anomaly.mean) + ' / ' + formatNum(anomaly.median), hint: "中心位置" },
{ label: "P10 / P90", value: formatNum(anomaly.q10) + ' / ' + formatNum(anomaly.q90), hint: "分位带" },
{ label: "高低组差距", value: formatNum(anomaly.gap), hint: "高位组均值 - 低位组均值" },
{ label: "异常值占比", value: formatNum(anomaly.outlier_pct) + '%', hint: "主指标异常比例" },
{ label: "组别规模", value: formatNum(anomaly.top_group_size) + ' / ' + formatNum(anomaly.bottom_group_size), hint: "高位组 / 低位组" }
{ 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>';
@@ -907,7 +1063,7 @@
var anomalyChart = initChart("anomaly-band-chart");
if (anomalyChart) {
anomalyChart.setOption({
title: { text: anomaly.metric + ' 分位带样本分布', left: "center", top: 6, textStyle: { fontSize: 12, fontWeight: 700, color: "#334155" } },
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" } },
@@ -924,7 +1080,7 @@
var tsChart = initChart("time-series-chart");
if (tsChart) {
tsChart.setOption({
title: { text: (timeSeries.name || "指标") + ' 趋势线', left: "center", top: 6, textStyle: { fontSize: 12, fontWeight: 700, color: "#334155" } },
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 } },
@@ -935,7 +1091,7 @@
} 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;">当前数据未识别到可用时间列,已保留文字分析位用于说明分层特征与异动概况。</div>';
tsEl.innerHTML = '<div style="height:100%;display:flex;align-items:center;justify-content:center;color:#64748b;font-size:13px;">' + t("当前数据未识别到可用时间列,已保留文字分析位用于说明分层特征与异动概况。") + '</div>';
}
}
@@ -945,8 +1101,8 @@
if (driverChart) {
var items = driver.items.slice(0, 8);
driverChart.setOption({
title: { text: (driver.metric || '核心指标') + ' 的驱动线索排序', 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/>驱动分 <b>' + item.score + '</b><br/>相关系数 ' + item.corr + '<br/>组间差异强度 ' + item.gap_ratio; } }, TT),
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" } },
@@ -956,7 +1112,7 @@
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">相关系数 ' + item.corr + ' · 高位组均值 ' + formatNum(item.top_mean) + ' · 低位组均值 ' + formatNum(item.bottom_mean) + ' · 组间差异强度 ' + formatNum(item.gap_ratio) + '</div></div><div class="badge">' + item.score + '</div></div>';
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 {
@@ -979,10 +1135,10 @@
if (indicators.length >= 3) {
radarOk = true;
radarChart.setOption({
title: { text: "全局特征轮廓", left: "center", top: 6, textStyle: { fontSize: 12, fontWeight: 700, color: "#334155" } },
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: "峰值频次", areaStyle: { color: "rgba(37,99,235,0.12)" }, lineStyle: { color: "#2563eb", width: 2 }, itemStyle: { color: "#2563eb" } }] }]
series: [{ type: "radar", data: [{ value: values, name: t("峰值频次"), areaStyle: { color: "rgba(37,99,235,0.12)" }, lineStyle: { color: "#2563eb", width: 2 }, itemStyle: { color: "#2563eb" } }] }]
});
}
}