{"spec_id":"curve-oc","library":"d3","language":"javascript","code":"// anyplot.ai\n// curve-oc: Operating Characteristic (OC) Curve\n// Library: d3 7.9.0 | JavaScript 22.22.3\n// Quality: 91/100 | Created: 2026-06-20\n//# anyplot-orientation: landscape\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\nconst margin = { top: 90, right: 190, bottom: 88, left: 110 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\n// --- Helpers ------------------------------------------------------------------\nfunction logFact(k) {\n  let s = 0;\n  for (let i = 2; i <= k; i++) s += Math.log(i);\n  return s;\n}\n\nfunction binomialCDF(n, c, p) {\n  if (p <= 0) return 1;\n  if (p >= 1) return c >= n ? 1 : 0;\n  const lp = Math.log(p);\n  const l1p = Math.log(1 - p);\n  const lfn = logFact(n);\n  let sum = 0;\n  for (let k = 0; k <= c; k++) {\n    sum += Math.exp(lfn - logFact(k) - logFact(n - k) + k * lp + (n - k) * l1p);\n  }\n  return Math.min(1, sum);\n}\n\n// --- Data ---------------------------------------------------------------------\nconst plans = [\n  { label: \"n=50,  c=1\", n: 50,  c: 1, color: t.palette[0] },\n  { label: \"n=50,  c=2\", n: 50,  c: 2, color: t.palette[1] },\n  { label: \"n=100, c=2\", n: 100, c: 2, color: t.palette[2] },\n  { label: \"n=100, c=3\", n: 100, c: 3, color: t.palette[3] },\n];\n\nconst X_MAX = 0.15;\nconst fracs = d3.range(201).map(i => (i * X_MAX) / 200);\nfor (const plan of plans) {\n  plan.pts = fracs.map(p => ({ p, pa: binomialCDF(plan.n, plan.c, p) }));\n}\n\n// --- SVG mount ----------------------------------------------------------------\nconst svg = d3.select(\"#container\")\n  .append(\"svg\")\n  .attr(\"width\", width)\n  .attr(\"height\", height);\n\nconst g = svg.append(\"g\")\n  .attr(\"transform\", `translate(${margin.left},${margin.top})`);\n\n// --- Scales -------------------------------------------------------------------\nconst x = d3.scaleLinear().domain([0, X_MAX]).range([0, iw]);\nconst y = d3.scaleLinear().domain([0, 1]).range([ih, 0]);\n\n// --- Gridlines ----------------------------------------------------------------\ng.append(\"g\")\n  .selectAll(\"line\")\n  .data(y.ticks(5))\n  .join(\"line\")\n  .attr(\"x1\", 0).attr(\"x2\", iw)\n  .attr(\"y1\", d => y(d)).attr(\"y2\", d => y(d))\n  .attr(\"stroke\", t.grid)\n  .attr(\"stroke-width\", 1);\n\n// --- Vertical reference lines: AQL and LTPD -----------------------------------\nconst vRefs = [\n  { px: 0.02, label: \"AQL = 2%\" },\n  { px: 0.08, label: \"LTPD = 8%\" },\n];\nfor (const { px, label } of vRefs) {\n  g.append(\"line\")\n    .attr(\"x1\", x(px)).attr(\"x2\", x(px))\n    .attr(\"y1\", 0).attr(\"y2\", ih)\n    .attr(\"stroke\", t.inkSoft)\n    .attr(\"stroke-width\", 1.5)\n    .attr(\"stroke-dasharray\", \"8,5\");\n  g.append(\"text\")\n    .attr(\"x\", x(px)).attr(\"y\", -18)\n    .attr(\"text-anchor\", \"middle\")\n    .attr(\"fill\", t.inkSoft)\n    .style(\"font-size\", \"13px\")\n    .text(label);\n}\n\n// --- Horizontal reference lines: producer / consumer risk ---------------------\nconst hRefs = [\n  { pa: 0.95, label: \"1−α = 95%\" },\n  { pa: 0.10, label: \"β = 10%\" },\n];\nfor (const { pa, label } of hRefs) {\n  g.append(\"line\")\n    .attr(\"x1\", 0).attr(\"x2\", iw)\n    .attr(\"y1\", y(pa)).attr(\"y2\", y(pa))\n    .attr(\"stroke\", t.inkSoft)\n    .attr(\"stroke-width\", 1)\n    .attr(\"stroke-dasharray\", \"5,4\");\n  g.append(\"text\")\n    .attr(\"x\", iw - 8).attr(\"y\", y(pa) - 7)\n    .attr(\"text-anchor\", \"end\")\n    .attr(\"fill\", t.inkSoft)\n    .style(\"font-size\", \"13px\")\n    .text(label);\n}\n\n// --- OC curves ----------------------------------------------------------------\nconst lineGen = d3.line()\n  .x(d => x(d.p))\n  .y(d => y(d.pa))\n  .curve(d3.curveMonotoneX);\n\nfor (const plan of plans) {\n  g.append(\"path\")\n    .datum(plan.pts)\n    .attr(\"fill\", \"none\")\n    .attr(\"stroke\", plan.color)\n    .attr(\"stroke-width\", 3)\n    .attr(\"d\", lineGen);\n}\n\n// --- Axes ---------------------------------------------------------------------\nconst xAxis = g.append(\"g\")\n  .attr(\"transform\", `translate(0,${ih})`)\n  .call(d3.axisBottom(x).ticks(5).tickFormat(d3.format(\".0%\")));\n\nconst yAxis = g.append(\"g\")\n  .call(d3.axisLeft(y).ticks(5).tickFormat(d3.format(\".0%\")));\n\nfor (const ax of [xAxis, yAxis]) {\n  ax.selectAll(\"text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"14px\");\n  ax.selectAll(\"line\").attr(\"stroke\", t.inkSoft);\n  ax.select(\".domain\").attr(\"stroke\", t.inkSoft);\n}\n\n// --- Axis labels --------------------------------------------------------------\nsvg.append(\"text\")\n  .attr(\"x\", margin.left + iw / 2)\n  .attr(\"y\", height - 24)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"16px\")\n  .text(\"Fraction Defective (p)\");\n\nsvg.append(\"text\")\n  .attr(\"transform\", \"rotate(-90)\")\n  .attr(\"x\", -(margin.top + ih / 2))\n  .attr(\"y\", 24)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"16px\")\n  .text(\"Probability of Acceptance Pa(p)\");\n\n// --- Legend (right margin) ---------------------------------------------------\nconst lgX = iw + 18;\nconst lgSpacing = 32;\nconst lgTotal = plans.length * lgSpacing + 20;\nconst lgY = (ih - lgTotal) / 2;\n\ng.append(\"rect\")\n  .attr(\"x\", lgX - 8).attr(\"y\", lgY - 12)\n  .attr(\"width\", 155).attr(\"height\", lgTotal + 12)\n  .attr(\"fill\", t.elevatedBg)\n  .attr(\"rx\", 5)\n  .attr(\"stroke\", t.grid)\n  .attr(\"stroke-width\", 1);\n\nfor (let i = 0; i < plans.length; i++) {\n  const ly = lgY + i * lgSpacing;\n  g.append(\"line\")\n    .attr(\"x1\", lgX).attr(\"x2\", lgX + 28)\n    .attr(\"y1\", ly + 7).attr(\"y2\", ly + 7)\n    .attr(\"stroke\", plans[i].color)\n    .attr(\"stroke-width\", 3);\n  g.append(\"text\")\n    .attr(\"x\", lgX + 36).attr(\"y\", ly + 12)\n    .attr(\"fill\", t.ink)\n    .style(\"font-size\", \"14px\")\n    .style(\"font-family\", \"monospace\")\n    .text(plans[i].label);\n}\n\n// --- Title --------------------------------------------------------------------\nsvg.append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", 52)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"22px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"curve-oc · javascript · d3 · anyplot.ai\");\n"}