为SPC验证页增加ECharts图表
This commit is contained in:
@@ -268,6 +268,49 @@
|
||||
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 {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
@@ -385,6 +428,10 @@
|
||||
.summary {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.chart-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 560px) {
|
||||
@@ -480,6 +527,37 @@
|
||||
</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">
|
||||
<table>
|
||||
<thead>
|
||||
@@ -513,6 +591,7 @@
|
||||
</section>
|
||||
</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>
|
||||
const sampleData = [
|
||||
@@ -606,8 +685,15 @@
|
||||
const localCpk = document.getElementById("localCpk");
|
||||
const responseTab = document.getElementById("responseTab");
|
||||
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 charts = null;
|
||||
|
||||
// 设置提示信息,统一处理错误和成功状态。
|
||||
function setMessage(text, type = "") {
|
||||
@@ -652,6 +738,33 @@
|
||||
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() {
|
||||
const values = dataInput.value
|
||||
@@ -836,6 +949,256 @@
|
||||
}, 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() {
|
||||
try {
|
||||
@@ -966,6 +1329,7 @@
|
||||
|
||||
renderJsonOutputs(response, localResult);
|
||||
renderComparison(response.res_data, localResult);
|
||||
renderCharts(response.res_data);
|
||||
setMessage("验证完成", "is-ok");
|
||||
} catch (error) {
|
||||
verifyStatus.textContent = "失败";
|
||||
|
||||
Reference in New Issue
Block a user