From 41381b95778aef0a8001dec89e1a8beecebeae0f Mon Sep 17 00:00:00 2001
From: zhangshun <453905631@qq.com>
Date: Tue, 16 Jun 2026 10:24:33 +0800
Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0SPC=E6=89=A9=E5=B1=95?=
=?UTF-8?q?=E6=8E=A7=E5=88=B6=E5=9B=BE=E9=AA=8C=E8=AF=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
public/spc_test.html | 279 ++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 277 insertions(+), 2 deletions(-)
diff --git a/public/spc_test.html b/public/spc_test.html
index 8cb2c7a..fcefbe1 100644
--- a/public/spc_test.html
+++ b/public/spc_test.html
@@ -556,6 +556,34 @@
等待执行
+
+
+ I-MR 单值-移动极差图
+ I-MR
+
+
等待执行
+
+
+
+ Run Chart 趋势图
+ Run
+
+
等待执行
+
+
+
+ Median-R 中位数-极差图
+ Median-R
+
+
等待执行
+
+
+
+ EWMA 指数加权移动平均图
+ EWMA
+
+
等待执行
+
@@ -690,6 +718,10 @@
xs: document.getElementById("xsChart"),
capability: document.getElementById("capabilityChart"),
histogram: document.getElementById("histogramChart"),
+ imr: document.getElementById("imrChart"),
+ run: document.getElementById("runChart"),
+ medianR: document.getElementById("medianRChart"),
+ ewma: document.getElementById("ewmaChart"),
};
let wasmModule = null;
@@ -756,6 +788,10 @@
xs: echarts.init(chartElements.xs),
capability: echarts.init(chartElements.capability),
histogram: echarts.init(chartElements.histogram),
+ imr: echarts.init(chartElements.imr),
+ run: echarts.init(chartElements.run),
+ medianR: echarts.init(chartElements.medianR),
+ ewma: echarts.init(chartElements.ewma),
};
window.addEventListener("resize", () => {
@@ -1172,9 +1208,244 @@
};
}
+ // 计算输入数据的中位数。
+ function median(values) {
+ if (!values.length) return 0;
+ const sorted = [...values].sort((a, b) => a - b);
+ const middle = Math.floor(sorted.length / 2);
+ if (sorted.length % 2 === 0) {
+ return (sorted[middle - 1] + sorted[middle]) / 2;
+ }
+ return sorted[middle];
+ }
+
+ // 计算 I-MR 单值和移动极差控制图数据。
+ function calculateImrData(values) {
+ const movingRanges = values
+ .slice(1)
+ .map((value, index) => Math.abs(value - values[index]));
+ const center = mean(values);
+ const mrBar = mean(movingRanges);
+ const sigma = mrBar / 1.128;
+ const upper = center + 3 * sigma;
+ const lower = center - 3 * sigma;
+ return {
+ values: values.map(round3),
+ movingRanges: movingRanges.map(round3),
+ center: round3(center),
+ upper: round3(upper),
+ lower: round3(lower),
+ mrCenter: round3(mrBar),
+ mrUpper: round3(3.267 * mrBar),
+ mrLower: 0,
+ };
+ }
+
+ // 计算 Median-R 中位数和极差控制图数据。
+ function calculateMedianRData(groups, subgroupSize) {
+ const coeff = coefficients[subgroupSize];
+ const medians = groups.map(median);
+ const ranges = groups.map(range);
+ const center = mean(medians);
+ const rBar = mean(ranges);
+ return {
+ medians: medians.map(round3),
+ ranges: ranges.map(round3),
+ center: round3(center),
+ upper: round3(center + coeff.A2 * rBar),
+ lower: round3(center - coeff.A2 * rBar),
+ rCenter: round3(rBar),
+ rUpper: round3(coeff.D4 * rBar),
+ rLower: round3(coeff.D3 * rBar),
+ };
+ }
+
+ // 计算 EWMA 控制图数据,默认 lambda 取 0.2。
+ function calculateEwmaData(values, lambda = 0.2) {
+ const center = mean(values);
+ const movingRanges = values
+ .slice(1)
+ .map((value, index) => Math.abs(value - values[index]));
+ const sigma = mean(movingRanges) / 1.128;
+ const ewma = [];
+ let previous = center;
+
+ values.forEach((value) => {
+ previous = lambda * value + (1 - lambda) * previous;
+ ewma.push(previous);
+ });
+
+ const factor = Math.sqrt(lambda / (2 - lambda));
+ return {
+ values: ewma.map(round3),
+ center: round3(center),
+ upper: round3(center + 3 * sigma * factor),
+ lower: round3(center - 3 * sigma * factor),
+ lambda,
+ };
+ }
+
+ // 创建 I-MR 控制图配置。
+ function createImrOption(imr) {
+ const valueLabels = sequenceLabels(imr.values.length);
+ const mrLabels = sequenceLabels(imr.movingRanges.length);
+ return {
+ color: ["#126b5c", "#b45f06", "#8a5a00", "#b42318"],
+ tooltip: { trigger: "axis" },
+ legend: { top: 0, right: 0 },
+ grid: [
+ { left: 48, right: 24, top: 42, height: 82 },
+ { left: 48, right: 24, top: 188, height: 72 },
+ ],
+ xAxis: [
+ { type: "category", gridIndex: 0, data: valueLabels, axisLabel: { color: "#65736e" } },
+ { type: "category", gridIndex: 1, data: mrLabels, axisLabel: { color: "#65736e" } },
+ ],
+ yAxis: [
+ {
+ type: "value",
+ name: "I",
+ gridIndex: 0,
+ scale: true,
+ axisLabel: { color: "#65736e" },
+ splitLine: { lineStyle: { color: "#e5ebe8" } },
+ },
+ {
+ type: "value",
+ name: "MR",
+ gridIndex: 1,
+ scale: true,
+ axisLabel: { color: "#65736e" },
+ splitLine: { lineStyle: { color: "#e5ebe8" } },
+ },
+ ],
+ series: [
+ { name: "单值", type: "line", data: imr.values, xAxisIndex: 0, yAxisIndex: 0, symbolSize: 5 },
+ { name: "CL_I", type: "line", data: constantSeries(imr.values.length, imr.center), xAxisIndex: 0, yAxisIndex: 0, symbol: "none", lineStyle: { type: "dashed" } },
+ { name: "UCL/LCL_I", type: "line", data: constantSeries(imr.values.length, imr.upper), xAxisIndex: 0, yAxisIndex: 0, symbol: "none", lineStyle: { type: "dotted" } },
+ { name: "UCL/LCL_I", type: "line", data: constantSeries(imr.values.length, imr.lower), xAxisIndex: 0, yAxisIndex: 0, symbol: "none", lineStyle: { type: "dotted" } },
+ { name: "移动极差", type: "bar", data: imr.movingRanges, xAxisIndex: 1, yAxisIndex: 1, barMaxWidth: 14 },
+ { name: "CL_MR", type: "line", data: constantSeries(imr.movingRanges.length, imr.mrCenter), xAxisIndex: 1, yAxisIndex: 1, symbol: "none", lineStyle: { type: "dashed" } },
+ { name: "UCL_MR", type: "line", data: constantSeries(imr.movingRanges.length, imr.mrUpper), xAxisIndex: 1, yAxisIndex: 1, symbol: "none", lineStyle: { type: "dotted" } },
+ ],
+ };
+ }
+
+ // 创建 Run Chart 趋势图配置。
+ function createRunChartOption(values) {
+ const roundedValues = values.map(round3);
+ const center = round3(median(values));
+ return {
+ color: ["#126b5c", "#b45f06"],
+ tooltip: { trigger: "axis" },
+ legend: { top: 0, right: 0 },
+ grid: { left: 48, right: 24, top: 42, bottom: 38 },
+ xAxis: {
+ type: "category",
+ data: sequenceLabels(values.length),
+ axisLabel: { color: "#65736e" },
+ },
+ yAxis: {
+ type: "value",
+ scale: true,
+ axisLabel: { color: "#65736e" },
+ splitLine: { lineStyle: { color: "#e5ebe8" } },
+ },
+ series: [
+ {
+ name: "原始序列",
+ type: "line",
+ data: roundedValues,
+ symbolSize: 4,
+ },
+ {
+ name: "中位线",
+ type: "line",
+ data: constantSeries(values.length, center),
+ symbol: "none",
+ lineStyle: { type: "dashed" },
+ },
+ ],
+ };
+ }
+
+ // 创建 Median-R 控制图配置。
+ function createMedianROption(medianR) {
+ return createControlChartOption(
+ "Median-R",
+ medianR.medians,
+ medianR.ranges,
+ {
+ CL_X: medianR.center,
+ UCL_X: medianR.upper,
+ LCL_X: medianR.lower,
+ CL_R: medianR.rCenter,
+ UCL_R: medianR.rUpper,
+ LCL_R: medianR.rLower,
+ },
+ "R",
+ );
+ }
+
+ // 创建 EWMA 控制图配置。
+ function createEwmaOption(ewma) {
+ return {
+ color: ["#126b5c", "#b45f06", "#b42318"],
+ tooltip: { trigger: "axis" },
+ legend: { top: 0, right: 0 },
+ grid: { left: 48, right: 24, top: 42, bottom: 38 },
+ xAxis: {
+ type: "category",
+ data: sequenceLabels(ewma.values.length),
+ axisLabel: { color: "#65736e" },
+ },
+ yAxis: {
+ type: "value",
+ scale: true,
+ axisLabel: { color: "#65736e" },
+ splitLine: { lineStyle: { color: "#e5ebe8" } },
+ },
+ series: [
+ {
+ name: `EWMA λ=${ewma.lambda}`,
+ type: "line",
+ data: ewma.values,
+ smooth: true,
+ symbolSize: 4,
+ },
+ {
+ name: "CL",
+ type: "line",
+ data: constantSeries(ewma.values.length, ewma.center),
+ symbol: "none",
+ lineStyle: { type: "dashed" },
+ },
+ {
+ name: "UCL/LCL",
+ type: "line",
+ data: constantSeries(ewma.values.length, ewma.upper),
+ symbol: "none",
+ lineStyle: { type: "dotted" },
+ },
+ {
+ name: "UCL/LCL",
+ type: "line",
+ data: constantSeries(ewma.values.length, ewma.lower),
+ symbol: "none",
+ lineStyle: { type: "dotted" },
+ },
+ ],
+ };
+ }
+
// 使用 WASM 返回结果刷新全部 ECharts 图形。
- function renderCharts(spcResult) {
+ function renderCharts(spcResult, input) {
const chartMap = ensureCharts();
+ const groups = buildGroups(input.x, input.n);
+ const imr = calculateImrData(input.x);
+ const medianR = calculateMedianRData(groups, input.n);
+ const ewma = calculateEwmaData(input.x);
+
chartMap.xr.setOption(
createControlChartOption(
"XR",
@@ -1197,6 +1468,10 @@
);
chartMap.capability.setOption(createCapabilityOption(spcResult.Cpk), true);
chartMap.histogram.setOption(createHistogramOption(spcResult.Cpk), true);
+ chartMap.imr.setOption(createImrOption(imr), true);
+ chartMap.run.setOption(createRunChartOption(input.x), true);
+ chartMap.medianR.setOption(createMedianROption(medianR), true);
+ chartMap.ewma.setOption(createEwmaOption(ewma), true);
}
// 更新输入数据数量显示。
@@ -1329,7 +1604,7 @@
renderJsonOutputs(response, localResult);
renderComparison(response.res_data, localResult);
- renderCharts(response.res_data);
+ renderCharts(response.res_data, input);
setMessage("验证完成", "is-ok");
} catch (error) {
verifyStatus.textContent = "失败";