{"spec_id":"step-basic","library":"d3","language":"javascript","code":"// anyplot.ai\n// step-basic: Basic Step Plot\n// Library: d3 7.9.0 | JavaScript 22.23.1\n// Quality: 90/100 | Created: 2026-07-25\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\nconst margin = { top: 110, right: 70, bottom: 110, left: 130 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\n// --- Data: hourly warehouse inventory (units), restocked at hour 8 and 16 ---\nconst inventory = [\n  { hour: 0, units: 480 },\n  { hour: 1, units: 460 },\n  { hour: 2, units: 460 },\n  { hour: 3, units: 435 },\n  { hour: 4, units: 435 },\n  { hour: 5, units: 410 },\n  { hour: 6, units: 390 },\n  { hour: 7, units: 365 },\n  { hour: 8, units: 600 },\n  { hour: 9, units: 575 },\n  { hour: 10, units: 550 },\n  { hour: 11, units: 550 },\n  { hour: 12, units: 515 },\n  { hour: 13, units: 490 },\n  { hour: 14, units: 460 },\n  { hour: 15, units: 425 },\n  { hour: 16, units: 620 },\n  { hour: 17, units: 600 },\n  { hour: 18, units: 580 },\n  { hour: 19, units: 555 },\n  { hour: 20, units: 530 },\n  { hour: 21, units: 510 },\n  { hour: 22, units: 490 },\n  { hour: 23, units: 470 },\n];\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(d3.extent(inventory, (d) => d.hour)).range([0, iw]);\nconst y = d3\n  .scaleLinear()\n  .domain([300, d3.max(inventory, (d) => d.units)])\n  .nice()\n  .range([ih, 0]);\n\n// --- Gridlines (y-axis only, per style guide) ---------------------------------\ng.append(\"g\")\n  .attr(\"class\", \"grid\")\n  .call(d3.axisLeft(y).tickSize(-iw).tickFormat(\"\"))\n  .call((sel) => sel.select(\".domain\").remove())\n  .selectAll(\"line\")\n  .attr(\"stroke\", t.grid);\n\n// --- Step line (post style: value holds until the next reading) --------------\nconst stepLine = d3\n  .line()\n  .x((d) => x(d.hour))\n  .y((d) => y(d.units))\n  .curve(d3.curveStepAfter);\n\n// Restock jumps are the story: soft halo behind the two vertical step\n// transitions at hour 8 and hour 16, drawn before the main line so it reads\n// as a glow rather than an outline.\nconst restockSegments = [\n  [inventory[7], inventory[8]],\n  [inventory[15], inventory[16]],\n];\ng.selectAll(\".restock-glow\")\n  .data(restockSegments)\n  .join(\"path\")\n  .attr(\"class\", \"restock-glow\")\n  .attr(\"fill\", \"none\")\n  .attr(\"stroke\", t.palette[0])\n  .attr(\"stroke-width\", 12)\n  .attr(\"stroke-linecap\", \"round\")\n  .attr(\"stroke-opacity\", 0.16)\n  .attr(\"d\", stepLine);\n\ng.append(\"path\")\n  .datum(inventory)\n  .attr(\"fill\", \"none\")\n  .attr(\"stroke\", t.palette[0])\n  .attr(\"stroke-width\", 4)\n  .attr(\"stroke-linejoin\", \"round\")\n  .attr(\"d\", stepLine);\n\n// --- Markers at each recorded reading (restock readings emphasized) ----------\nconst restockHours = new Set([8, 16]);\nconst markerFill = d3.scaleOrdinal().domain([true, false]).range([t.palette[0], t.pageBg]);\nconst markerRadius = d3.scaleOrdinal().domain([true, false]).range([9, 7]);\n\ng.selectAll(\"circle\")\n  .data(inventory)\n  .join(\"circle\")\n  .attr(\"cx\", (d) => x(d.hour))\n  .attr(\"cy\", (d) => y(d.units))\n  .attr(\"r\", (d) => markerRadius(restockHours.has(d.hour)))\n  .attr(\"fill\", (d) => markerFill(restockHours.has(d.hour)))\n  .attr(\"stroke\", t.palette[0])\n  .attr(\"stroke-width\", 3);\n\n// --- Axes -----------------------------------------------------------------------\nconst xAxis = g\n  .append(\"g\")\n  .attr(\"transform\", `translate(0,${ih})`)\n  .call(\n    d3\n      .axisBottom(x)\n      .tickValues(d3.range(0, 24, 4))\n      .tickFormat((d) => `${d}:00`),\n  );\nconst yAxis = g.append(\"g\").call(d3.axisLeft(y));\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  // Lightened, spine-free baseline: keep the L-shaped frame but de-emphasize it\n  // relative to the data.\n  ax.select(\".domain\").attr(\"stroke\", t.inkSoft).attr(\"stroke-opacity\", 0.4).attr(\"stroke-width\", 1);\n}\n\n// --- Axis labels --------------------------------------------------------------\ng.append(\"text\")\n  .attr(\"x\", iw / 2)\n  .attr(\"y\", ih + 70)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"17px\")\n  .text(\"Hour of Day\");\n\ng.append(\"text\")\n  .attr(\"transform\", \"rotate(-90)\")\n  .attr(\"x\", -ih / 2)\n  .attr(\"y\", -95)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"17px\")\n  .text(\"Warehouse Inventory (units)\");\n\n// --- Title ----------------------------------------------------------------------\nsvg\n  .append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", 56)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"22px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"step-basic · javascript · d3 · anyplot.ai\");\n"}