{"spec_id":"ecdf-basic","library":"d3","language":"javascript","code":"// anyplot.ai\n// ecdf-basic: Basic ECDF Plot\n// Library: d3 7.9.0 | JavaScript 22.22.3\n// Quality: 90/100 | Created: 2026-06-25\n\n//# anyplot-orientation: landscape\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\nconst margin = { top: 80, right: 130, bottom: 80, left: 90 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\n// Deterministic LCG + Box-Muller for reproducible normal samples\nfunction makeLCG(seed) {\n  let s = seed >>> 0;\n  return () => { s = (Math.imul(1664525, s) + 1013904223) >>> 0; return s / 0x100000000; };\n}\n\nfunction normalSamples(n, mean, std, rng) {\n  const out = [];\n  for (let i = 0; i < n; i++) {\n    const u1 = Math.max(1e-12, rng());\n    const u2 = rng();\n    out.push(mean + std * Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2));\n  }\n  return out.sort((a, b) => a - b);\n}\n\nconst expressDelivery = normalSamples(160, 3.2, 0.6, makeLCG(42));\nconst standardDelivery = normalSamples(160, 4.8, 1.3, makeLCG(137));\n\n// Build ECDF step points: prepend (xStart, 0) so the line starts from 0 at left\nfunction ecdfPoints(sorted, xStart, xEnd) {\n  const n = sorted.length;\n  return [\n    { x: xStart, y: 0 },\n    ...sorted.map((v, i) => ({ x: v, y: (i + 1) / n })),\n    { x: xEnd, y: 1 },\n  ];\n}\n\nconst allValues = [...expressDelivery, ...standardDelivery];\nconst pad = (d3.max(allValues) - d3.min(allValues)) * 0.04;\nconst xDomMin = d3.min(allValues) - pad;\nconst xDomMax = d3.max(allValues) + pad;\n\nconst expressECDF = ecdfPoints(expressDelivery, xDomMin, xDomMax);\nconst standardECDF = ecdfPoints(standardDelivery, xDomMin, xDomMax);\n\n// SVG mount\nconst svg = d3.select(\"#container\").append(\"svg\").attr(\"width\", width).attr(\"height\", height);\nconst g = svg.append(\"g\").attr(\"transform\", `translate(${margin.left},${margin.top})`);\n\n// Scales\nconst x = d3.scaleLinear().domain([xDomMin, xDomMax]).range([0, iw]);\nconst y = d3.scaleLinear().domain([0, 1]).range([ih, 0]);\n\n// Horizontal grid lines at quartile proportions\ng.selectAll(\".hgrid\").data([0.25, 0.5, 0.75]).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).attr(\"stroke-width\", 1);\n\n// Axes\nconst xAxis = g.append(\"g\").attr(\"transform\", `translate(0,${ih})`)\n  .call(d3.axisBottom(x).ticks(8).tickSize(5));\nconst yAxis = g.append(\"g\")\n  .call(d3.axisLeft(y).tickValues([0, 0.25, 0.5, 0.75, 1]).tickFormat(d3.format(\".0%\")).tickSize(5));\n\nfor (const ax of [xAxis, yAxis]) {\n  ax.selectAll(\"text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"14px\");\n  ax.selectAll(\".tick line\").attr(\"stroke\", t.inkSoft);\n  ax.select(\".domain\").attr(\"stroke\", t.inkSoft);\n}\n\n// ECDF step lines — curveStepAfter matches ECDF semantics: step up AT each observation\nconst line = d3.line().x(d => x(d.x)).y(d => y(d.y)).curve(d3.curveStepAfter);\n\ng.append(\"path\").datum(expressECDF)\n  .attr(\"fill\", \"none\").attr(\"stroke\", t.palette[0]).attr(\"stroke-width\", 3).attr(\"d\", line);\n\ng.append(\"path\").datum(standardECDF)\n  .attr(\"fill\", \"none\").attr(\"stroke\", t.palette[1]).attr(\"stroke-width\", 3).attr(\"d\", line);\n\n// Axis labels\nsvg.append(\"text\")\n  .attr(\"x\", margin.left + iw / 2).attr(\"y\", height - 18)\n  .attr(\"text-anchor\", \"middle\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"16px\")\n  .text(\"Delivery Time (days)\");\n\nsvg.append(\"text\")\n  .attr(\"transform\", \"rotate(-90)\")\n  .attr(\"x\", -(margin.top + ih / 2)).attr(\"y\", 22)\n  .attr(\"text-anchor\", \"middle\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"16px\")\n  .text(\"Cumulative Proportion\");\n\n// Legend (top-left inside plot area — region near (xMin, y=1) is empty for ECDF)\n[\n  { label: \"Express Shipping (n=160)\", color: t.palette[0] },\n  { label: \"Standard Shipping (n=160)\", color: t.palette[1] },\n].forEach(({ label, color }, i) => {\n  const ly = 24 + i * 32;\n  g.append(\"line\").attr(\"x1\", 20).attr(\"x2\", 58).attr(\"y1\", ly).attr(\"y2\", ly)\n    .attr(\"stroke\", color).attr(\"stroke-width\", 3);\n  g.append(\"text\").attr(\"x\", 68).attr(\"y\", ly + 5)\n    .attr(\"fill\", t.inkSoft).style(\"font-size\", \"14px\").text(label);\n});\n\n// Title\nsvg.append(\"text\")\n  .attr(\"x\", width / 2).attr(\"y\", 44).attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink).style(\"font-size\", \"22px\").style(\"font-weight\", \"600\")\n  .text(\"ecdf-basic · javascript · d3 · anyplot.ai\");\n"}