{"spec_id":"curve-oc","library":"echarts","language":"javascript","code":"// anyplot.ai\n// curve-oc: Operating Characteristic (OC) Curve\n// Library: echarts 5.5.1 | JavaScript 22.22.3\n// Quality: 89/100 | Created: 2026-06-20\n//# anyplot-orientation: landscape\n// anyplot.ai\n// curve-oc: Operating Characteristic (OC) Curve\n// Library: echarts 5.5.1 | JavaScript 22\n// Quality: pending | Created: 2026-06-20\n\nconst t = window.ANYPLOT_TOKENS;\n\n// Binomial coefficient (numerically stable for n <= 200)\nfunction binomCoeff(n, k) {\n  if (k === 0 || k === n) return 1;\n  if (k > n - k) k = n - k;\n  let c = 1;\n  for (let i = 0; i < k; i++) c = (c * (n - i)) / (i + 1);\n  return c;\n}\n\n// P(accept | n, c, p) = sum_{k=0}^{c} C(n,k) * p^k * (1-p)^(n-k)\nfunction ocProb(n, c, p) {\n  if (p <= 0) return 1;\n  if (p >= 1) return 0;\n  let sum = 0;\n  for (let k = 0; k <= c; k++) {\n    sum += binomCoeff(n, k) * Math.pow(p, k) * Math.pow(1 - p, n - k);\n  }\n  return Math.min(1, Math.max(0, sum));\n}\n\n// --- Data -------------------------------------------------------------------\nconst X_MAX = 0.20;\nconst pVals = Array.from({ length: 201 }, (_, i) => (i * X_MAX) / 200);\n\nconst AQL  = 0.02;   // Acceptable Quality Level\nconst LTPD = 0.10;   // Lot Tolerance Percent Defective\n\nconst PLANS = [\n  { n:  50, c: 1, label: \"n=50, c=1\"  },\n  { n:  50, c: 2, label: \"n=50, c=2\"  },\n  { n: 100, c: 2, label: \"n=100, c=2\" },\n  { n: 100, c: 3, label: \"n=100, c=3\" },\n];\n\nconst refLineStyle = {\n  silent: true,\n  symbol: \"none\",\n  animation: false,\n  lineStyle: { type: \"dashed\", color: t.inkSoft, width: 1.5, opacity: 0.65 },\n  label: { show: true, fontSize: 14, color: t.inkSoft, formatter: \"{b}\" },\n  data: [\n    { name: \"AQL (2%)\",  xAxis: AQL,  label: { position: \"insideStartTop\" } },\n    { name: \"LTPD (10%)\", xAxis: LTPD, label: { position: \"insideStartTop\" } },\n    { name: \"1−α = 95%\", yAxis: 0.95, label: { position: \"insideEndTop\" } },\n    { name: \"β = 10%\",   yAxis: 0.10, label: { position: \"insideEndTop\" } },\n  ],\n};\n\nconst curveSeries = PLANS.map((plan, idx) => ({\n  name: plan.label,\n  type: \"line\",\n  data: pVals.map((p) => [+(p.toFixed(6)), +(ocProb(plan.n, plan.c, p).toFixed(6))]),\n  lineStyle: { width: 3.5, color: t.palette[idx] },\n  itemStyle: { color: t.palette[idx] },\n  symbol: \"none\",\n  z: 3,\n  ...(idx === 0 ? { markLine: refLineStyle } : {}),\n}));\n\n// --- Chart ------------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\nchart.setOption({\n  animation: false,\n  color: t.palette,\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"curve-oc · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    top: 24,\n    textStyle: { color: t.ink, fontSize: 22, fontWeight: \"500\" },\n  },\n  subtitle: {\n    text: \"Acceptance Sampling Plans — Probability of Acceptance vs Fraction Defective\",\n    left: \"center\",\n    top: 58,\n    textStyle: { color: t.ink, fontSize: 17 },\n  },\n  legend: {\n    top: 100,\n    right: 36,\n    orient: \"vertical\",\n    textStyle: { color: t.ink, fontSize: 16 },\n    itemGap: 14,\n    itemWidth: 28,\n    itemHeight: 4,\n  },\n  grid: { left: 120, right: 200, top: 145, bottom: 90 },\n  xAxis: {\n    type: \"value\",\n    name: \"Fraction Defective (p)\",\n    nameLocation: \"middle\",\n    nameGap: 52,\n    nameTextStyle: { color: t.ink, fontSize: 18 },\n    min: 0,\n    max: X_MAX,\n    axisLabel: {\n      color: t.inkSoft,\n      fontSize: 14,\n      formatter: (v) => (v * 100).toFixed(0) + \"%\",\n    },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  yAxis: {\n    type: \"value\",\n    name: \"Probability of Acceptance\",\n    nameLocation: \"middle\",\n    nameGap: 70,\n    nameTextStyle: { color: t.ink, fontSize: 18 },\n    min: 0,\n    max: 1,\n    axisLabel: {\n      color: t.inkSoft,\n      fontSize: 14,\n      formatter: (v) => (v * 100).toFixed(0) + \"%\",\n    },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    splitLine: { lineStyle: { color: t.grid } },\n  },\n  tooltip: {\n    trigger: \"axis\",\n    backgroundColor: t.elevatedBg,\n    borderColor: t.grid,\n    textStyle: { color: t.ink, fontSize: 14 },\n    formatter: (params) => {\n      const p = params[0].data[0];\n      let s = `<b>p = ${(p * 100).toFixed(1)}%</b><br/>`;\n      params.forEach((item) => {\n        s += `${item.marker} ${item.seriesName}: ${(item.data[1] * 100).toFixed(1)}%<br/>`;\n      });\n      return s;\n    },\n  },\n  series: curveSeries,\n});\n"}