为SPC验证页增加ECharts图表

This commit is contained in:
zhangshun
2026-06-16 10:11:36 +08:00
parent 6f45353b21
commit e9c8bde14a

View File

@@ -268,6 +268,49 @@
border-radius: 8px; border-radius: 8px;
} }
.chart-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
margin-bottom: 16px;
}
.chart-card {
border: 1px solid var(--line);
border-radius: 8px;
background: #ffffff;
overflow: hidden;
}
.chart-card.wide {
grid-column: 1 / -1;
}
.chart-title {
display: flex;
align-items: center;
justify-content: space-between;
min-height: 42px;
padding: 10px 12px;
border-bottom: 1px solid var(--line);
background: #f7faf8;
color: var(--muted);
font-size: 13px;
font-weight: 750;
}
.chart {
width: 100%;
height: 300px;
}
.chart.empty {
display: grid;
place-items: center;
color: var(--muted);
font-size: 13px;
}
table { table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
@@ -385,6 +428,10 @@
.summary { .summary {
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
} }
.chart-grid {
grid-template-columns: 1fr;
}
} }
@media (max-width: 560px) { @media (max-width: 560px) {
@@ -480,6 +527,37 @@
</div> </div>
</div> </div>
<div class="chart-grid" aria-label="SPC 图形结果">
<div class="chart-card wide">
<div class="chart-title">
<span>Xbar-R 控制图</span>
<span>XR</span>
</div>
<div class="chart empty" id="xrChart">等待执行</div>
</div>
<div class="chart-card wide">
<div class="chart-title">
<span>Xbar-S 控制图</span>
<span>XS</span>
</div>
<div class="chart empty" id="xsChart">等待执行</div>
</div>
<div class="chart-card">
<div class="chart-title">
<span>过程能力指标</span>
<span>Cp / Cpk / Pp / Ppk</span>
</div>
<div class="chart empty" id="capabilityChart">等待执行</div>
</div>
<div class="chart-card">
<div class="chart-title">
<span>直方图与正态曲线</span>
<span>Histogram</span>
</div>
<div class="chart empty" id="histogramChart">等待执行</div>
</div>
</div>
<div class="table-wrap"> <div class="table-wrap">
<table> <table>
<thead> <thead>
@@ -513,6 +591,7 @@
</section> </section>
</main> </main>
<script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js"></script>
<script src="./wasm/smart_math.js"></script> <script src="./wasm/smart_math.js"></script>
<script> <script>
const sampleData = [ const sampleData = [
@@ -606,8 +685,15 @@
const localCpk = document.getElementById("localCpk"); const localCpk = document.getElementById("localCpk");
const responseTab = document.getElementById("responseTab"); const responseTab = document.getElementById("responseTab");
const localTab = document.getElementById("localTab"); const localTab = document.getElementById("localTab");
const chartElements = {
xr: document.getElementById("xrChart"),
xs: document.getElementById("xsChart"),
capability: document.getElementById("capabilityChart"),
histogram: document.getElementById("histogramChart"),
};
let wasmModule = null; let wasmModule = null;
let charts = null;
// 设置提示信息,统一处理错误和成功状态。 // 设置提示信息,统一处理错误和成功状态。
function setMessage(text, type = "") { function setMessage(text, type = "") {
@@ -652,6 +738,33 @@
return Math.sqrt(variance); return Math.sqrt(variance);
} }
// 懒初始化 ECharts 实例,避免页面初始加载时做多余工作。
function ensureCharts() {
if (!window.echarts) {
throw new Error("ECharts 加载失败,请检查网络或 CDN 可用性");
}
if (charts) return charts;
Object.values(chartElements).forEach((element) => {
element.classList.remove("empty");
element.textContent = "";
});
charts = {
xr: echarts.init(chartElements.xr),
xs: echarts.init(chartElements.xs),
capability: echarts.init(chartElements.capability),
histogram: echarts.init(chartElements.histogram),
};
window.addEventListener("resize", () => {
Object.values(charts).forEach((chart) => chart.resize());
});
return charts;
}
// 从文本框解析测量数据。 // 从文本框解析测量数据。
function parseDataInput() { function parseDataInput() {
const values = dataInput.value const values = dataInput.value
@@ -836,6 +949,256 @@
}, source); }, source);
} }
// 生成序号数组作为控制图横轴。
function sequenceLabels(length) {
return Array.from({ length }, (_, index) => String(index + 1));
}
// 生成固定值数组,用于绘制中心线和控制限。
function constantSeries(length, value) {
return Array.from({ length }, () => value);
}
// 创建控制图通用配置。
function createControlChartOption(title, xValues, yValues, limits, yName) {
const labels = sequenceLabels(xValues.length);
return {
color: ["#126b5c", "#b45f06", "#8a5a00", "#b42318"],
tooltip: { trigger: "axis" },
legend: {
top: 0,
right: 0,
itemWidth: 12,
itemHeight: 8,
},
grid: [
{ left: 48, right: 24, top: 42, height: 82 },
{ left: 48, right: 24, top: 188, height: 72 },
],
xAxis: [
{
type: "category",
gridIndex: 0,
data: labels,
axisLabel: { color: "#65736e" },
},
{
type: "category",
gridIndex: 1,
data: labels,
axisLabel: { color: "#65736e" },
},
],
yAxis: [
{
type: "value",
name: "Xbar",
gridIndex: 0,
scale: true,
axisLabel: { color: "#65736e" },
splitLine: { lineStyle: { color: "#e5ebe8" } },
},
{
type: "value",
name: yName,
gridIndex: 1,
scale: true,
axisLabel: { color: "#65736e" },
splitLine: { lineStyle: { color: "#e5ebe8" } },
},
],
series: [
{
name: `${title} Xbar`,
type: "line",
data: xValues,
xAxisIndex: 0,
yAxisIndex: 0,
symbolSize: 6,
smooth: false,
},
{
name: "CL_X",
type: "line",
data: constantSeries(xValues.length, limits.CL_X),
xAxisIndex: 0,
yAxisIndex: 0,
symbol: "none",
lineStyle: { type: "dashed", width: 1.5 },
},
{
name: "UCL/LCL_X",
type: "line",
data: constantSeries(xValues.length, limits.UCL_X),
xAxisIndex: 0,
yAxisIndex: 0,
symbol: "none",
lineStyle: { type: "dotted", width: 1.5 },
},
{
name: "UCL/LCL_X",
type: "line",
data: constantSeries(xValues.length, limits.LCL_X),
xAxisIndex: 0,
yAxisIndex: 0,
symbol: "none",
lineStyle: { type: "dotted", width: 1.5 },
},
{
name: yName,
type: "bar",
data: yValues,
xAxisIndex: 1,
yAxisIndex: 1,
barMaxWidth: 18,
},
{
name: `CL_${yName}`,
type: "line",
data: constantSeries(yValues.length, limits[`CL_${yName}`]),
xAxisIndex: 1,
yAxisIndex: 1,
symbol: "none",
lineStyle: { type: "dashed", width: 1.5 },
},
{
name: `UCL/LCL_${yName}`,
type: "line",
data: constantSeries(yValues.length, limits[`UCL_${yName}`]),
xAxisIndex: 1,
yAxisIndex: 1,
symbol: "none",
lineStyle: { type: "dotted", width: 1.5 },
},
{
name: `UCL/LCL_${yName}`,
type: "line",
data: constantSeries(yValues.length, limits[`LCL_${yName}`]),
xAxisIndex: 1,
yAxisIndex: 1,
symbol: "none",
lineStyle: { type: "dotted", width: 1.5 },
},
],
};
}
// 刷新能力指标柱状图。
function createCapabilityOption(cpk) {
const labels = ["Cp", "Cpk", "Pp", "Ppk", "CPU", "CPL"];
return {
color: ["#126b5c"],
tooltip: { trigger: "axis" },
grid: { left: 46, right: 20, top: 24, bottom: 36 },
xAxis: {
type: "category",
data: labels,
axisLabel: { color: "#65736e" },
},
yAxis: {
type: "value",
axisLabel: { color: "#65736e" },
splitLine: { lineStyle: { color: "#e5ebe8" } },
},
series: [
{
name: "能力指标",
type: "bar",
data: labels.map((label) => cpk[label]),
barMaxWidth: 34,
itemStyle: {
color: (params) =>
params.value >= 1.33 ? "#157347" : params.value >= 1 ? "#b45f06" : "#b42318",
},
markLine: {
symbol: "none",
data: [
{ yAxis: 1, name: "1.00" },
{ yAxis: 1.33, name: "1.33" },
],
label: { color: "#65736e" },
lineStyle: { color: "#8a5a00", type: "dashed" },
},
},
],
};
}
// 刷新直方图和正态曲线。
function createHistogramOption(cpk) {
return {
color: ["#126b5c", "#b45f06"],
tooltip: { trigger: "axis" },
legend: { top: 0, right: 0 },
grid: { left: 48, right: 24, top: 42, bottom: 42 },
xAxis: {
type: "value",
name: "X",
axisLabel: { color: "#65736e" },
},
yAxis: [
{
type: "value",
name: "频数",
axisLabel: { color: "#65736e" },
splitLine: { lineStyle: { color: "#e5ebe8" } },
},
{
type: "value",
name: "曲线",
axisLabel: { color: "#65736e" },
splitLine: { show: false },
},
],
series: [
{
name: "频数",
type: "bar",
data: cpk.Xk.map((value, index) => [value, cpk.YkCount[index]]),
barMaxWidth: 32,
},
{
name: "正态曲线",
type: "line",
yAxisIndex: 1,
data: cpk.NormalDistributionX.map((value, index) => [
value,
cpk.NormalDistributionY[index],
]),
smooth: true,
symbolSize: 5,
},
],
};
}
// 使用 WASM 返回结果刷新全部 ECharts 图形。
function renderCharts(spcResult) {
const chartMap = ensureCharts();
chartMap.xr.setOption(
createControlChartOption(
"XR",
spcResult.XR.CL_Xk,
spcResult.XR.CL_Rk,
spcResult.XR,
"R",
),
true,
);
chartMap.xs.setOption(
createControlChartOption(
"XS",
spcResult.XS.CL_Xk,
spcResult.XS.CL_Sk,
spcResult.XS,
"S",
),
true,
);
chartMap.capability.setOption(createCapabilityOption(spcResult.Cpk), true);
chartMap.histogram.setOption(createHistogramOption(spcResult.Cpk), true);
}
// 更新输入数据数量显示。 // 更新输入数据数量显示。
function updateInputCount() { function updateInputCount() {
try { try {
@@ -966,6 +1329,7 @@
renderJsonOutputs(response, localResult); renderJsonOutputs(response, localResult);
renderComparison(response.res_data, localResult); renderComparison(response.res_data, localResult);
renderCharts(response.res_data);
setMessage("验证完成", "is-ok"); setMessage("验证完成", "is-ok");
} catch (error) { } catch (error) {
verifyStatus.textContent = "失败"; verifyStatus.textContent = "失败";