{"spec_id":"span-basic","library":"d3","language":"javascript","code":"// anyplot.ai\n// span-basic: Basic Span Plot (Highlighted Region)\n// Library: d3 7.9.0 | JavaScript 22.23.1\n// Quality: 91/100 | Created: 2026-07-25\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\nconst margin = { top: 110, right: 60, bottom: 90, left: 110 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Quarterly stock index level, 2006–2011, with two recession periods marked.\nfunction lcg(seed) {\n  let s = seed;\n  return () => {\n    s = (s * 1103515245 + 12345) & 0x7fffffff;\n    return s / 0x7fffffff;\n  };\n}\nconst rand = lcg(42);\n\nconst quarters = [];\nfor (let year = 2006; year <= 2011; year++) {\n  for (let q = 0; q < 4; q++) quarters.push(new Date(year, q * 3, 1));\n}\n\nlet level = 100;\nconst series = quarters.map((date, i) => {\n  const inRecession =\n    (date >= new Date(2007, 9, 1) && date < new Date(2009, 3, 1)) ||\n    (date >= new Date(2011, 6, 1) && date < new Date(2011, 12, 1));\n  const drift = inRecession ? -3.2 : 1.8;\n  level += drift + (rand() - 0.5) * 4;\n  level = Math.max(level, 55);\n  return { date, index: level };\n});\n\nconst verticalSpans = [\n  { start: new Date(2007, 9, 1), end: new Date(2009, 3, 1), label: \"2007–09 recession\" },\n  { start: new Date(2011, 6, 1), end: new Date(2011, 12, 1), label: \"2011 slowdown\" },\n];\n\n// A horizontal span demonstrates the spec's other direction: a fixed value-threshold\n// \"correction zone\" band, independent of x, spanning the full plot width.\nconst horizontalSpans = [{ start: 60, end: 97, label: \"Correction zone (index < 97)\" }];\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 --------------------------------------------------------------------\n// Domain covers both the series dates and every vertical span's start/end so a span\n// that extends past the last data point never extrapolates past the plotted x-axis.\nconst x = d3\n  .scaleTime()\n  .domain(d3.extent([...series.map((d) => d.date), ...verticalSpans.flatMap((s) => [s.start, s.end])]))\n  .range([0, iw]);\nconst y = d3\n  .scaleLinear()\n  .domain([d3.min(series, (d) => d.index) * 0.95, d3.max(series, (d) => d.index) * 1.05])\n  .nice()\n  .range([ih, 0]);\n\n// --- Gridlines (y-axis only) ---------------------------------------------------\ng.append(\"g\")\n  .selectAll(\"line\")\n  .data(y.ticks(6))\n  .join(\"line\")\n  .attr(\"x1\", 0)\n  .attr(\"x2\", iw)\n  .attr(\"y1\", (d) => y(d))\n  .attr(\"y2\", (d) => y(d))\n  .attr(\"stroke\", t.grid)\n  .attr(\"stroke-width\", 1);\n\n// --- Horizontal span (drawn beneath everything) — value-threshold band ----------\nconst hSpanColor = t.amber; // amber — semantic anchor for warning/caution thresholds\ng.selectAll(\".hspan\")\n  .data(horizontalSpans)\n  .join(\"rect\")\n  .attr(\"class\", \"hspan\")\n  .attr(\"x\", 0)\n  .attr(\"y\", (d) => Math.max(y(d.end), 0))\n  .attr(\"width\", iw)\n  .attr(\"height\", (d) => Math.min(y(d.start), ih) - Math.max(y(d.end), 0))\n  .attr(\"fill\", hSpanColor)\n  .attr(\"opacity\", 0.22);\n\ng.selectAll(\".hspan-label\")\n  .data(horizontalSpans)\n  .join(\"text\")\n  .attr(\"class\", \"hspan-label\")\n  .attr(\"x\", 12)\n  .attr(\"y\", (d) => Math.max(y(d.end), 0) + 20)\n  .attr(\"text-anchor\", \"start\")\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"13px\")\n  .style(\"font-weight\", \"500\")\n  .text((d) => d.label);\n\n// --- Vertical spans (drawn beneath the line) ------------------------------------\nconst spanColor = t.palette[4]; // matte red — semantic anchor for downturn periods\ng.selectAll(\".span\")\n  .data(verticalSpans)\n  .join(\"rect\")\n  .attr(\"class\", \"span\")\n  .attr(\"x\", (d) => x(d.start))\n  .attr(\"y\", 0)\n  .attr(\"width\", (d) => x(d.end) - x(d.start))\n  .attr(\"height\", ih)\n  .attr(\"fill\", spanColor)\n  .attr(\"opacity\", 0.22);\n\ng.selectAll(\".span-label\")\n  .data(verticalSpans)\n  .join(\"text\")\n  .attr(\"class\", \"span-label\")\n  .attr(\"x\", (d) => x(d.start) + (x(d.end) - x(d.start)) / 2)\n  .attr(\"y\", 22)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"13px\")\n  .style(\"font-weight\", \"500\")\n  .text((d) => d.label);\n\n// --- Line (drawn above the spans) -----------------------------------------------\nconst line = d3\n  .line()\n  .x((d) => x(d.date))\n  .y((d) => y(d.index));\n\ng.append(\"path\")\n  .datum(series)\n  .attr(\"fill\", \"none\")\n  .attr(\"stroke\", t.palette[0])\n  .attr(\"stroke-width\", 3)\n  .attr(\"stroke-linejoin\", \"round\")\n  .attr(\"stroke-linecap\", \"round\")\n  .attr(\"d\", line);\n\n// --- Axes -------------------------------------------------------------------\nconst xAxis = g\n  .append(\"g\")\n  .attr(\"transform\", `translate(0,${ih})`)\n  .call(d3.axisBottom(x).ticks(d3.timeYear.every(1)).tickFormat(d3.timeFormat(\"%Y\")).tickSizeOuter(0));\nconst yAxis = g.append(\"g\").call(d3.axisLeft(y).ticks(6).tickSizeOuter(0));\n\nfor (const ax of [xAxis, yAxis]) {\n  ax.selectAll(\"text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"15px\");\n  ax.selectAll(\"line\").attr(\"stroke\", t.inkSoft);\n  ax.select(\".domain\").attr(\"stroke\", t.inkSoft);\n}\nxAxis.selectAll(\"line\").remove();\nyAxis.selectAll(\"line\").remove();\n\n// --- Axis titles --------------------------------------------------------------\ng.append(\"text\")\n  .attr(\"x\", iw / 2)\n  .attr(\"y\", ih + 56)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"17px\")\n  .text(\"Year\");\n\ng.append(\"text\")\n  .attr(\"transform\", \"rotate(-90)\")\n  .attr(\"x\", -ih / 2)\n  .attr(\"y\", -80)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"17px\")\n  .text(\"Market Index\");\n\n// --- Title ------------------------------------------------------------------\nsvg\n  .append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", 50)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"26px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"span-basic · javascript · d3 · anyplot.ai\");\n"}