{"spec_id":"ecdf-basic","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// ecdf-basic: Basic ECDF Plot\n// Library: highcharts 12.6.0 | JavaScript 22.23.0\n// Quality: 87/100 | Created: 2026-06-25\n\n//# anyplot-orientation: landscape\n\nconst t = window.ANYPLOT_TOKENS;\n\n// Deterministic LCG random number generator\nfunction lcg(seed) {\n    let s = seed >>> 0;\n    return () => {\n        s = (Math.imul(1664525, s) + 1013904223) >>> 0;\n        return s / 4294967296;\n    };\n}\n\n// Box-Muller transform — normally distributed samples\nfunction normalSamples(rng, mu, sigma, n) {\n    const out = [];\n    while (out.length < n) {\n        const u1 = Math.max(rng(), 1e-10);\n        const u2 = rng();\n        const mag = sigma * Math.sqrt(-2 * Math.log(u1));\n        out.push(mu + mag * Math.cos(2 * Math.PI * u2));\n        if (out.length < n) out.push(mu + mag * Math.sin(2 * Math.PI * u2));\n    }\n    return out;\n}\n\n// ECDF as [x, proportion] step-function points (right-continuous convention)\n// startX is a virtual point at proportion=0 before the first data value\nfunction ecdfSeries(data, startX) {\n    const sorted = [...data].sort((a, b) => a - b);\n    const n = sorted.length;\n    const pts = [[startX, 0]];\n    sorted.forEach((x, i) => pts.push([x, (i + 1) / n]));\n    return pts;\n}\n\n// Plant height (cm) under two growing conditions — 100 plants each\nconst standard = normalSamples(lcg(42), 15.0, 3.0, 100);\nconst enhanced = normalSamples(lcg(137), 18.5, 2.5, 100);\n\nconst allX = [...standard, ...enhanced];\nconst xMin = Math.min(...allX);\nconst xMax = Math.max(...allX);\nconst startX = xMin - (xMax - xMin) * 0.04;\n\nconst seriesStandard = ecdfSeries(standard, startX);\nconst seriesEnhanced = ecdfSeries(enhanced, startX);\n\n// Actual sample medians for annotation (average of 50th and 51st values in 100-item sorted array)\nconst sortedStd = [...standard].sort((a, b) => a - b);\nconst sortedEnh = [...enhanced].sort((a, b) => a - b);\nconst medStd = (sortedStd[49] + sortedStd[50]) / 2;\nconst medEnh = (sortedEnh[49] + sortedEnh[50]) / 2;\n\n// Title fits within 67 chars — use default 22px\nconst titleText = 'Plant Heights · ecdf-basic · javascript · highcharts · anyplot.ai';\n\nHighcharts.chart('container', {\n    chart: {\n        type: 'area',\n        backgroundColor: 'transparent',\n        animation: false,\n        style: { fontFamily: 'inherit' },\n    },\n    credits: { enabled: false },\n    colors: t.palette,\n    title: {\n        text: titleText,\n        style: { color: t.ink, fontSize: '22px', fontWeight: '600' },\n    },\n    subtitle: {\n        text: `Enhanced nutrients shift median height from ${medStd.toFixed(1)} cm to ${medEnh.toFixed(1)} cm`,\n        style: { color: t.inkSoft, fontSize: '14px' },\n    },\n    xAxis: {\n        title: {\n            text: 'Plant Height (cm)',\n            style: { color: t.inkSoft, fontSize: '16px' },\n        },\n        lineColor: t.inkSoft,\n        tickColor: t.inkSoft,\n        gridLineColor: t.grid,\n        labels: { style: { color: t.inkSoft, fontSize: '14px' } },\n        plotLines: [\n            {\n                value: medStd,\n                color: t.palette[0],\n                dashStyle: 'Dash',\n                width: 1.5,\n                zIndex: 3,\n                label: {\n                    text: `median ${medStd.toFixed(1)} cm`,\n                    style: { color: t.palette[0], fontSize: '11px' },\n                    rotation: 0,\n                    y: 16,\n                    x: 4,\n                },\n            },\n            {\n                value: medEnh,\n                color: t.palette[1],\n                dashStyle: 'Dash',\n                width: 1.5,\n                zIndex: 3,\n                label: {\n                    text: `median ${medEnh.toFixed(1)} cm`,\n                    style: { color: t.palette[1], fontSize: '11px' },\n                    rotation: 0,\n                    y: 16,\n                    x: 4,\n                },\n            },\n        ],\n    },\n    yAxis: {\n        title: {\n            text: 'Cumulative Proportion',\n            style: { color: t.inkSoft, fontSize: '16px' },\n        },\n        min: 0,\n        max: 1,\n        tickInterval: 0.25,\n        gridLineColor: t.grid,\n        lineColor: t.inkSoft,\n        tickColor: t.inkSoft,\n        labels: {\n            style: { color: t.inkSoft, fontSize: '14px' },\n            formatter() { return `${Math.round(this.value * 100)}%`; },\n        },\n        plotLines: [\n            {\n                value: 0.5,\n                color: t.inkSoft,\n                dashStyle: 'ShortDash',\n                width: 1,\n                zIndex: 2,\n                label: {\n                    text: '50th percentile',\n                    style: { color: t.inkSoft, fontSize: '11px' },\n                    align: 'right',\n                    x: -4,\n                    y: -4,\n                },\n            },\n        ],\n    },\n    legend: {\n        enabled: true,\n        itemStyle: { color: t.inkSoft, fontSize: '14px' },\n        itemHoverStyle: { color: t.ink },\n    },\n    plotOptions: {\n        series: {\n            animation: false,\n            step: 'right',\n            marker: { enabled: false },\n        },\n    },\n    series: [\n        {\n            name: 'Standard Nutrients',\n            data: seriesStandard,\n            color: t.palette[0],\n            lineWidth: 2.5,\n            fillOpacity: 0.08,\n        },\n        {\n            name: 'Enhanced Nutrients',\n            data: seriesEnhanced,\n            color: t.palette[1],\n            lineWidth: 3.0,\n            fillOpacity: 0.08,\n        },\n    ],\n});\n"}