为SPC验证页增加ECharts图表
This commit is contained in:
@@ -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 = "失败";
|
||||||
|
|||||||
Reference in New Issue
Block a user