diff --git a/spc-vue/README.md b/spc-vue/README.md index 265db89..ad7e554 100644 --- a/spc-vue/README.md +++ b/spc-vue/README.md @@ -15,6 +15,7 @@ npm run dev ## 页面路由 - `/overview`:SPC 总览和关键指标 +- `/run-chart`:基本趋势图,显示原始序列、均值线、中位线、规格限和移动平均线 - `/xbar-r`:Xbar 控制图,控制限来自 XR 结果 - `/r-chart`:R 极差控制图 - `/xbar-s`:Xbar 控制图,控制限来自 XS 结果 diff --git a/spc-vue/src/router.ts b/spc-vue/src/router.ts index 688ea56..41d1182 100644 --- a/spc-vue/src/router.ts +++ b/spc-vue/src/router.ts @@ -5,10 +5,12 @@ import RChartView from "./views/RChartView.vue"; import XBarSView from "./views/XBarSView.vue"; import SChartView from "./views/SChartView.vue"; import CpkView from "./views/CpkView.vue"; +import RunChartView from "./views/RunChartView.vue"; export const routes = [ { path: "/", redirect: "/overview" }, { path: "/overview", component: OverviewView, meta: { title: "总览" } }, + { path: "/run-chart", component: RunChartView, meta: { title: "趋势图" } }, { path: "/xbar-r", component: XBarRView, meta: { title: "Xbar-R" } }, { path: "/r-chart", component: RChartView, meta: { title: "R 图" } }, { path: "/xbar-s", component: XBarSView, meta: { title: "Xbar-S" } }, diff --git a/spc-vue/src/utils/chartOptions.ts b/spc-vue/src/utils/chartOptions.ts index ba86785..3948c35 100644 --- a/spc-vue/src/utils/chartOptions.ts +++ b/spc-vue/src/utils/chartOptions.ts @@ -3,6 +3,14 @@ import type { ControlLimitChart, CpkResult } from "../types/spc"; const chartColors = ["#0f766e", "#b45309", "#991b1b", "#2563eb"]; +export interface RunChartInput { + values: readonly number[]; + usl: number; + lsl: number; + target: number; + movingAverageWindow?: number; +} + // 生成从 1 开始的子组序号,用于控制图横轴。 export function sequenceLabels(length: number): string[] { return Array.from({ length }, (_, index) => String(index + 1)); @@ -13,6 +21,36 @@ function constantSeries(length: number, value: number): number[] { return Array.from({ length }, () => value); } +// 计算算术平均值,用于基本趋势图的均值线。 +function mean(values: readonly number[]): number { + if (!values.length) return 0; + return values.reduce((sum, value) => sum + value, 0) / values.length; +} + +// 计算中位数,用于基本趋势图的中心参考线。 +function median(values: readonly number[]): number { + if (!values.length) return 0; + const sorted = [...values].sort((a, b) => a - b); + const middle = Math.floor(sorted.length / 2); + return sorted.length % 2 === 0 ? (sorted[middle - 1] + sorted[middle]) / 2 : sorted[middle]; +} + +// 计算移动平均线,窗口不足时使用当前已有数据,避免前几项为空。 +function movingAverage(values: readonly number[], windowSize: number): number[] { + return values.map((_, index) => { + const start = Math.max(0, index - windowSize + 1); + return mean(values.slice(start, index + 1)); + }); +} + +// 找出超出规格限的原始数据点,用于基本趋势图标红。 +function outOfSpecPoints(values: readonly number[], usl: number, lsl: number): Array<[number, number]> { + return values + .map((value, index) => ({ value, index })) + .filter((point) => point.value > usl || point.value < lsl) + .map((point) => [point.index, point.value]); +} + // 计算直方图 X 轴范围,覆盖分箱、规格限、正态曲线和原始极值,并保留少量边距。 function calculateHistogramXAxisRange(cpk: CpkResult): { min: number; max: number } { const values = [ @@ -114,6 +152,95 @@ export function createControlLimitOption(chart: ControlLimitChart): EChartsOptio }; } +// 创建基本趋势图配置,按原始顺序展示数据并叠加规格限、均值线、中位线和移动平均线。 +export function createRunChartOption(input: RunChartInput): EChartsOption { + const labels = sequenceLabels(input.values.length); + const average = mean(input.values); + const middle = median(input.values); + const windowSize = Math.max(2, Math.floor(input.movingAverageWindow || 5)); + const outliers = outOfSpecPoints(input.values, input.usl, input.lsl); + + return { + color: chartColors, + tooltip: { trigger: "axis" }, + legend: { top: 0, right: 0 }, + grid: { left: 56, right: 34, top: 46, bottom: 44 }, + xAxis: { + type: "category", + name: "样本序号", + data: labels, + boundaryGap: false, + axisLabel: { color: "#667085" } + }, + yAxis: { + type: "value", + name: "测量值", + scale: true, + axisLabel: { color: "#667085" }, + splitLine: { lineStyle: { color: "#e5e7eb" } } + }, + series: [ + { + name: "原始数据", + type: "line", + data: [...input.values], + symbolSize: 4, + lineStyle: { width: 1.8 } + }, + { + name: `${windowSize}点移动平均`, + type: "line", + data: movingAverage(input.values, windowSize), + smooth: true, + symbol: "none", + lineStyle: { width: 2 } + }, + { + name: "均值", + type: "line", + data: constantSeries(input.values.length, average), + symbol: "none", + lineStyle: { type: "dashed", width: 1.4 } + }, + { + name: "中位线", + type: "line", + data: constantSeries(input.values.length, middle), + symbol: "none", + lineStyle: { type: "dashed", width: 1.4 } + }, + { + name: "超规格点", + type: "scatter", + data: outliers, + symbolSize: 10, + itemStyle: { color: "#dc2626" } + }, + { + name: "USL", + type: "line", + data: constantSeries(input.values.length, input.usl), + symbol: "none", + lineStyle: { type: "dotted", width: 1.5 } + }, + { + name: "LSL", + type: "line", + data: constantSeries(input.values.length, input.lsl), + symbol: "none", + lineStyle: { type: "dotted", width: 1.5 } + }, + { + name: "目标值", + type: "line", + data: constantSeries(input.values.length, input.target), + symbol: "none", + lineStyle: { type: "dotted", width: 1.5 } + } + ] + }; +} + // 创建过程能力指标柱状图,便于快速查看 Cp/Cpk/Pp/Ppk 门槛。 export function createCapabilityBarOption(cpk: CpkResult): EChartsOption { const labels = ["Cp", "Cpk", "Pp", "Ppk", "CPU", "CPL"]; diff --git a/spc-vue/src/views/RunChartView.vue b/spc-vue/src/views/RunChartView.vue new file mode 100644 index 0000000..f809497 --- /dev/null +++ b/spc-vue/src/views/RunChartView.vue @@ -0,0 +1,49 @@ + + +