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 = "失败";