{"spec_id":"curve-oc","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// curve-oc: Operating Characteristic (OC) Curve\n// Library: highcharts 12.6.0 | JavaScript 22.22.3\n// Quality: 92/100 | Created: 2026-06-20\n\n//# anyplot-orientation: landscape\n\nconst t = window.ANYPLOT_TOKENS;\n\n// Binomial CDF in log-space PMF for numerical stability\nfunction Pa(n, c, p) {\n    if (p <= 0) return 1;\n    if (p >= 1) return 0;\n    let lnN = 0;\n    for (let i = 2; i <= n; i++) lnN += Math.log(i);\n    let lnK = 0, lnNK = lnN, prob = 0;\n    for (let k = 0; k <= c; k++) {\n        if (k > 0) { lnK += Math.log(k); lnNK -= Math.log(n - k + 1); }\n        prob += Math.exp(lnN - lnK - lnNK + k * Math.log(p) + (n - k) * Math.log(1 - p));\n    }\n    return Math.min(1, Math.max(0, prob));\n}\n\n// --- Sampling plans ----------------------------------------------------------\nconst plans = [\n    { n: 50,  c: 1, label: \"n=50, c=1\"  },\n    { n: 100, c: 2, label: \"n=100, c=2\" },\n    { n: 150, c: 3, label: \"n=150, c=3\" },\n];\n\nconst AQL  = 0.01;   // Acceptable Quality Level\nconst LTPD = 0.08;   // Lot Tolerance Percent Defective\n\nconst N_POINTS = 120;\nconst X_MAX = 0.15;\nconst xValues = Array.from({ length: N_POINTS }, (_, i) => i * X_MAX / (N_POINTS - 1));\n\n// OC curves\nconst ocSeries = plans.map(({ n, c, label }) => ({\n    name: label,\n    type: \"spline\",\n    data: xValues.map(p => [p, Pa(n, c, p)]),\n    lineWidth: 2.5,\n    marker: { enabled: false },\n}));\n\n// Crossover points at AQL and LTPD for each plan (where curves meet the reference lines)\nconst crossoverSeries = plans.map(({ n, c }, i) => ({\n    type: \"scatter\",\n    name: \"\",\n    showInLegend: false,\n    color: t.palette[i],\n    marker: { symbol: \"circle\", radius: 5 },\n    data: [\n        [AQL,  Pa(n, c, AQL)],\n        [LTPD, Pa(n, c, LTPD)],\n    ],\n}));\n\n// --- Chart -------------------------------------------------------------------\nHighcharts.chart(\"container\", {\n    chart: {\n        backgroundColor: \"transparent\",\n        animation: false,\n        style: { fontFamily: \"inherit\" },\n        marginRight: 30,\n    },\n    credits: { enabled: false },\n    colors: t.palette,\n    title: {\n        text: \"curve-oc · javascript · highcharts · anyplot.ai\",\n        style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n    },\n    xAxis: {\n        title: {\n            text: \"Fraction Defective (p)\",\n            style: { color: t.inkSoft, fontSize: \"16px\" },\n        },\n        min: 0,\n        max: X_MAX,\n        tickAmount: 7,\n        lineColor: t.inkSoft,\n        tickColor: t.inkSoft,\n        gridLineWidth: 0,\n        labels: {\n            style: { color: t.inkSoft, fontSize: \"14px\" },\n            formatter: function () { return (this.value * 100).toFixed(0) + \"%\"; },\n        },\n        plotBands: [\n            {\n                from: AQL,\n                to: LTPD,\n                color: \"rgba(0, 158, 115, 0.08)\",\n                label: {\n                    text: \"Discrimination Zone\",\n                    style: { color: t.inkSoft, fontSize: \"11px\" },\n                    align: \"center\",\n                    verticalAlign: \"bottom\",\n                    y: -8,\n                },\n                zIndex: 0,\n            },\n        ],\n        plotLines: [\n            {\n                value: AQL,\n                color: t.inkSoft,\n                dashStyle: \"Dash\",\n                width: 1.5,\n                label: {\n                    text: \"AQL (1%)\",\n                    style: { color: t.inkSoft, fontSize: \"13px\" },\n                    rotation: 0,\n                    y: -6,\n                    x: 4,\n                },\n                zIndex: 3,\n            },\n            {\n                value: LTPD,\n                color: t.inkSoft,\n                dashStyle: \"Dash\",\n                width: 1.5,\n                label: {\n                    text: \"LTPD (8%)\",\n                    style: { color: t.inkSoft, fontSize: \"13px\" },\n                    rotation: 0,\n                    y: -6,\n                    x: 4,\n                },\n                zIndex: 3,\n            },\n        ],\n    },\n    yAxis: {\n        title: {\n            text: \"Probability of Acceptance\",\n            style: { color: t.inkSoft, fontSize: \"16px\" },\n        },\n        min: 0,\n        max: 1,\n        tickAmount: 6,\n        gridLineColor: t.grid,\n        lineColor: t.inkSoft,\n        tickColor: t.inkSoft,\n        labels: {\n            style: { color: t.inkSoft, fontSize: \"14px\" },\n            formatter: function () { return (this.value * 100).toFixed(0) + \"%\"; },\n        },\n        plotLines: [\n            {\n                value: 0.95,\n                color: t.inkSoft,\n                dashStyle: \"ShortDot\",\n                width: 1,\n                label: {\n                    text: \"α = 5% (producer's risk)\",\n                    align: \"right\",\n                    style: { color: t.inkSoft, fontSize: \"13px\" },\n                    x: -6,\n                },\n                zIndex: 3,\n            },\n            {\n                value: 0.10,\n                color: t.inkSoft,\n                dashStyle: \"ShortDot\",\n                width: 1,\n                label: {\n                    text: \"β = 10% (consumer's risk)\",\n                    align: \"right\",\n                    style: { color: t.inkSoft, fontSize: \"13px\" },\n                    x: -6,\n                },\n                zIndex: 3,\n            },\n        ],\n    },\n    legend: {\n        enabled: true,\n        align: \"right\",\n        verticalAlign: \"middle\",\n        layout: \"vertical\",\n        itemStyle: { color: t.inkSoft, fontSize: \"14px\", fontWeight: \"normal\" },\n        itemHoverStyle: { color: t.ink },\n        title: {\n            text: \"Sampling Plan\",\n            style: { color: t.inkSoft, fontSize: \"12px\", fontWeight: \"normal\" },\n        },\n    },\n    plotOptions: {\n        series: { animation: false },\n        spline: {\n            lineWidth: 2.5,\n            states: { hover: { lineWidth: 2.5 } },\n        },\n    },\n    series: [...ocSeries, ...crossoverSeries],\n});\n"}