221 lines
6.2 KiB
TypeScript
221 lines
6.2 KiB
TypeScript
import type { EChartsOption } from "echarts";
|
|
import type { ControlLimitChart, CpkResult } from "../types/spc";
|
|
|
|
const chartColors = ["#0f766e", "#b45309", "#991b1b", "#2563eb"];
|
|
|
|
// 生成从 1 开始的子组序号,用于控制图横轴。
|
|
export function sequenceLabels(length: number): string[] {
|
|
return Array.from({ length }, (_, index) => String(index + 1));
|
|
}
|
|
|
|
// 生成固定值线,用于 CL、UCL、LCL。
|
|
function constantSeries(length: number, value: number): number[] {
|
|
return Array.from({ length }, () => value);
|
|
}
|
|
|
|
// 计算直方图 X 轴范围,覆盖分箱、规格限、正态曲线和原始极值,并保留少量边距。
|
|
function calculateHistogramXAxisRange(cpk: CpkResult): { min: number; max: number } {
|
|
const values = [
|
|
...cpk.Xk,
|
|
...cpk.XkUp,
|
|
...cpk.XkDown,
|
|
...cpk.NormalDistributionX,
|
|
cpk.LSL,
|
|
cpk.USL,
|
|
cpk.SL,
|
|
cpk.ValueMin,
|
|
cpk.ValueMax
|
|
].filter((value) => Number.isFinite(value));
|
|
|
|
if (!values.length) {
|
|
return { min: 0, max: 1 };
|
|
}
|
|
|
|
const rawMin = Math.min(...values);
|
|
const rawMax = Math.max(...values);
|
|
const span = rawMax - rawMin;
|
|
const padding = span > 0 ? Math.max(span * 0.08, cpk.GroupWidth || 0) : Math.max(Math.abs(rawMin) * 0.08, 0.1);
|
|
|
|
return {
|
|
min: rawMin - padding,
|
|
max: rawMax + padding
|
|
};
|
|
}
|
|
|
|
// 找出越过控制限的点,作为红色散点叠加在控制图上。
|
|
function outOfLimitPoints(values: readonly number[], upper: number, lower: number): Array<[number, number]> {
|
|
return values
|
|
.map((value, index) => ({ value, index }))
|
|
.filter((point) => point.value > upper || point.value < lower)
|
|
.map((point) => [point.index, point.value]);
|
|
}
|
|
|
|
// 创建单张控制图配置,包含数据线、中心线、上下控制限和越界点。
|
|
export function createControlLimitOption(chart: ControlLimitChart): EChartsOption {
|
|
const labels = sequenceLabels(chart.values.length);
|
|
const outliers = outOfLimitPoints(chart.values, chart.upper, chart.lower);
|
|
|
|
return {
|
|
color: chartColors,
|
|
tooltip: { trigger: "axis" },
|
|
legend: { top: 0, right: 0 },
|
|
grid: { left: 56, right: 28, top: 46, bottom: 44 },
|
|
xAxis: {
|
|
type: "category",
|
|
name: "子组",
|
|
data: labels,
|
|
boundaryGap: false,
|
|
axisLabel: { color: "#667085" }
|
|
},
|
|
yAxis: {
|
|
type: "value",
|
|
name: chart.valueName,
|
|
scale: true,
|
|
axisLabel: { color: "#667085" },
|
|
splitLine: { lineStyle: { color: "#e5e7eb" } }
|
|
},
|
|
series: [
|
|
{
|
|
name: chart.valueName,
|
|
type: "line",
|
|
data: [...chart.values],
|
|
symbolSize: 6,
|
|
lineStyle: { width: 2 }
|
|
},
|
|
{
|
|
name: "CL",
|
|
type: "line",
|
|
data: constantSeries(chart.values.length, chart.center),
|
|
symbol: "none",
|
|
lineStyle: { type: "dashed", width: 1.5 }
|
|
},
|
|
{
|
|
name: "UCL",
|
|
type: "line",
|
|
data: constantSeries(chart.values.length, chart.upper),
|
|
symbol: "none",
|
|
lineStyle: { type: "dotted", width: 1.5 }
|
|
},
|
|
{
|
|
name: "LCL",
|
|
type: "line",
|
|
data: constantSeries(chart.values.length, chart.lower),
|
|
symbol: "none",
|
|
lineStyle: { type: "dotted", width: 1.5 }
|
|
},
|
|
{
|
|
name: "越界点",
|
|
type: "scatter",
|
|
data: outliers,
|
|
symbolSize: 10,
|
|
itemStyle: { color: "#dc2626" }
|
|
}
|
|
]
|
|
};
|
|
}
|
|
|
|
// 创建过程能力指标柱状图,便于快速查看 Cp/Cpk/Pp/Ppk 门槛。
|
|
export function createCapabilityBarOption(cpk: CpkResult): EChartsOption {
|
|
const labels = ["Cp", "Cpk", "Pp", "Ppk", "CPU", "CPL"];
|
|
|
|
return {
|
|
color: ["#0f766e"],
|
|
tooltip: { trigger: "axis" },
|
|
grid: { left: 48, right: 24, top: 36, bottom: 42 },
|
|
xAxis: {
|
|
type: "category",
|
|
data: labels,
|
|
axisLabel: { color: "#667085" }
|
|
},
|
|
yAxis: {
|
|
type: "value",
|
|
axisLabel: { color: "#667085" },
|
|
splitLine: { lineStyle: { color: "#e5e7eb" } }
|
|
},
|
|
series: [
|
|
{
|
|
name: "能力指标",
|
|
type: "bar",
|
|
data: labels.map((label) => cpk[label as keyof CpkResult] as number),
|
|
barMaxWidth: 34,
|
|
itemStyle: {
|
|
color: (params) => {
|
|
const value = Number(params.value);
|
|
if (value >= 1.33) return "#15803d";
|
|
if (value >= 1) return "#b45309";
|
|
return "#b91c1c";
|
|
}
|
|
},
|
|
markLine: {
|
|
symbol: "none",
|
|
data: [
|
|
{ yAxis: 1, name: "1.00" },
|
|
{ yAxis: 1.33, name: "1.33" }
|
|
],
|
|
label: { color: "#667085" },
|
|
lineStyle: { color: "#b45309", type: "dashed" }
|
|
}
|
|
}
|
|
]
|
|
};
|
|
}
|
|
|
|
// 创建直方图和正态曲线配置,用来核对分布和规格限位置。
|
|
export function createHistogramOption(cpk: CpkResult): EChartsOption {
|
|
const xRange = calculateHistogramXAxisRange(cpk);
|
|
|
|
return {
|
|
color: ["#0f766e", "#b45309"],
|
|
tooltip: { trigger: "axis" },
|
|
legend: { top: 0, right: 0 },
|
|
grid: { left: 52, right: 48, top: 46, bottom: 42 },
|
|
xAxis: {
|
|
type: "value",
|
|
name: "测量值",
|
|
min: xRange.min,
|
|
max: xRange.max,
|
|
axisLabel: { color: "#667085" }
|
|
},
|
|
yAxis: [
|
|
{
|
|
type: "value",
|
|
name: "频数",
|
|
axisLabel: { color: "#667085" },
|
|
splitLine: { lineStyle: { color: "#e5e7eb" } }
|
|
},
|
|
{
|
|
type: "value",
|
|
name: "正态曲线",
|
|
axisLabel: { color: "#667085" },
|
|
splitLine: { show: false }
|
|
}
|
|
],
|
|
series: [
|
|
{
|
|
name: "频数",
|
|
type: "bar",
|
|
data: cpk.Xk.map((value, index) => [value, cpk.YkCount[index]]),
|
|
barMaxWidth: 28,
|
|
markLine: {
|
|
symbol: "none",
|
|
data: [
|
|
{ xAxis: cpk.LSL, name: "LSL" },
|
|
{ xAxis: cpk.USL, name: "USL" },
|
|
{ xAxis: cpk.SL, name: "SL" }
|
|
],
|
|
label: { color: "#667085" },
|
|
lineStyle: { color: "#991b1b", type: "dashed" }
|
|
}
|
|
},
|
|
{
|
|
name: "正态曲线",
|
|
type: "line",
|
|
yAxisIndex: 1,
|
|
smooth: true,
|
|
symbolSize: 4,
|
|
data: cpk.NormalDistributionX.map((value, index) => [value, cpk.NormalDistributionY[index]])
|
|
}
|
|
]
|
|
};
|
|
}
|