增加SPC扩展控制图验证
This commit is contained in:
@@ -556,6 +556,34 @@
|
||||
</div>
|
||||
<div class="chart empty" id="histogramChart">等待执行</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 class="table-wrap">
|
||||
@@ -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 = "失败";
|
||||
|
||||
Reference in New Issue
Block a user