增加SPC扩展控制图验证
This commit is contained in:
@@ -556,6 +556,34 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="chart empty" id="histogramChart">等待执行</div>
|
<div class="chart empty" id="histogramChart">等待执行</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="chart-card wide">
|
||||||
|
<div class="chart-title">
|
||||||
|
<span>I-MR 单值-移动极差图</span>
|
||||||
|
<span>I-MR</span>
|
||||||
|
</div>
|
||||||
|
<div class="chart empty" id="imrChart">等待执行</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-card wide">
|
||||||
|
<div class="chart-title">
|
||||||
|
<span>Run Chart 趋势图</span>
|
||||||
|
<span>Run</span>
|
||||||
|
</div>
|
||||||
|
<div class="chart empty" id="runChart">等待执行</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-card wide">
|
||||||
|
<div class="chart-title">
|
||||||
|
<span>Median-R 中位数-极差图</span>
|
||||||
|
<span>Median-R</span>
|
||||||
|
</div>
|
||||||
|
<div class="chart empty" id="medianRChart">等待执行</div>
|
||||||
|
</div>
|
||||||
|
<div class="chart-card wide">
|
||||||
|
<div class="chart-title">
|
||||||
|
<span>EWMA 指数加权移动平均图</span>
|
||||||
|
<span>EWMA</span>
|
||||||
|
</div>
|
||||||
|
<div class="chart empty" id="ewmaChart">等待执行</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="table-wrap">
|
<div class="table-wrap">
|
||||||
@@ -690,6 +718,10 @@
|
|||||||
xs: document.getElementById("xsChart"),
|
xs: document.getElementById("xsChart"),
|
||||||
capability: document.getElementById("capabilityChart"),
|
capability: document.getElementById("capabilityChart"),
|
||||||
histogram: document.getElementById("histogramChart"),
|
histogram: document.getElementById("histogramChart"),
|
||||||
|
imr: document.getElementById("imrChart"),
|
||||||
|
run: document.getElementById("runChart"),
|
||||||
|
medianR: document.getElementById("medianRChart"),
|
||||||
|
ewma: document.getElementById("ewmaChart"),
|
||||||
};
|
};
|
||||||
|
|
||||||
let wasmModule = null;
|
let wasmModule = null;
|
||||||
@@ -756,6 +788,10 @@
|
|||||||
xs: echarts.init(chartElements.xs),
|
xs: echarts.init(chartElements.xs),
|
||||||
capability: echarts.init(chartElements.capability),
|
capability: echarts.init(chartElements.capability),
|
||||||
histogram: echarts.init(chartElements.histogram),
|
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", () => {
|
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 图形。
|
// 使用 WASM 返回结果刷新全部 ECharts 图形。
|
||||||
function renderCharts(spcResult) {
|
function renderCharts(spcResult, input) {
|
||||||
const chartMap = ensureCharts();
|
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(
|
chartMap.xr.setOption(
|
||||||
createControlChartOption(
|
createControlChartOption(
|
||||||
"XR",
|
"XR",
|
||||||
@@ -1197,6 +1468,10 @@
|
|||||||
);
|
);
|
||||||
chartMap.capability.setOption(createCapabilityOption(spcResult.Cpk), true);
|
chartMap.capability.setOption(createCapabilityOption(spcResult.Cpk), true);
|
||||||
chartMap.histogram.setOption(createHistogramOption(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);
|
renderJsonOutputs(response, localResult);
|
||||||
renderComparison(response.res_data, localResult);
|
renderComparison(response.res_data, localResult);
|
||||||
renderCharts(response.res_data);
|
renderCharts(response.res_data, input);
|
||||||
setMessage("验证完成", "is-ok");
|
setMessage("验证完成", "is-ok");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
verifyStatus.textContent = "失败";
|
verifyStatus.textContent = "失败";
|
||||||
|
|||||||
Reference in New Issue
Block a user