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]]) } ] }; }