{"spec_id":"line-basic","library":"d3","language":"javascript","code":"// anyplot.ai\n// line-basic: Basic Line Plot\n// Library: d3 7.9.0 | JavaScript 22.22.3\n// Quality: 94/100 | Created: 2026-06-02\n//# anyplot-orientation: landscape\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\nconst margin = { top: 80, right: 90, bottom: 70, left: 90 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\n// Data — daily high temperature (°C) for a spring month, deterministic LCG noise\nconst start = new Date(2026, 2, 1);\nlet seed = 42;\nconst rand = () => ((seed = (seed * 1103515245 + 12345) & 0x7fffffff) / 0x7fffffff);\nconst data = d3.range(31).map((i) => {\n  const day = new Date(start);\n  day.setDate(start.getDate() + i);\n  const trend = 9 + (i / 30) * 11;\n  const wobble = Math.sin(i / 3.2) * 2.6 + (rand() - 0.5) * 2.4;\n  return { date: day, temp: +(trend + wobble).toFixed(1) };\n});\nconst endpoint = data[data.length - 1];\n\n// SVG mount\nconst svg = d3.select(\"#container\").append(\"svg\")\n  .attr(\"width\", width).attr(\"height\", height);\nconst g = svg.append(\"g\").attr(\"transform\", `translate(${margin.left},${margin.top})`);\n\n// Scales\nconst x = d3.scaleTime()\n  .domain(d3.extent(data, (d) => d.date))\n  .range([0, iw]);\nconst yMin = d3.min(data, (d) => d.temp);\nconst yMax = d3.max(data, (d) => d.temp);\nconst y = d3.scaleLinear()\n  .domain([Math.floor(yMin - 2), Math.ceil(yMax + 2)])\n  .nice()\n  .range([ih, 0]);\n\n// Vertical area-fill gradient — brand green, fading top->bottom\nconst defs = svg.append(\"defs\");\nconst grad = defs.append(\"linearGradient\")\n  .attr(\"id\", \"fillGrad\")\n  .attr(\"gradientUnits\", \"userSpaceOnUse\")\n  .attr(\"x1\", 0).attr(\"y1\", 0).attr(\"x2\", 0).attr(\"y2\", ih);\ngrad.append(\"stop\").attr(\"offset\", \"0%\").attr(\"stop-color\", t.palette[0]).attr(\"stop-opacity\", 0.22);\ngrad.append(\"stop\").attr(\"offset\", \"100%\").attr(\"stop-color\", t.palette[0]).attr(\"stop-opacity\", 0.02);\n\n// Gridlines (y-axis only, behind the data)\ng.append(\"g\")\n  .attr(\"class\", \"grid\")\n  .call(d3.axisLeft(y).tickSize(-iw).tickFormat(\"\"))\n  .call((sel) => {\n    sel.select(\".domain\").remove();\n    sel.selectAll(\"line\").attr(\"stroke\", t.grid).attr(\"stroke-width\", 1);\n  });\n\n// Axes — drop bottom domain spine for a lighter, more refined chrome\nconst xAxis = g.append(\"g\")\n  .attr(\"transform\", `translate(0,${ih})`)\n  .call(\n    d3.axisBottom(x)\n      .ticks(d3.timeWeek.every(1))\n      .tickFormat(d3.timeFormat(\"%b %d\"))\n      .tickSizeOuter(0),\n  );\nconst yAxis = g.append(\"g\")\n  .call(d3.axisLeft(y).ticks(7).tickFormat((d) => `${d}°`).tickSizeOuter(0));\n\nfor (const ax of [xAxis, yAxis]) {\n  ax.selectAll(\"text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"18px\");\n  ax.selectAll(\".tick line\").attr(\"stroke\", t.inkSoft);\n}\nxAxis.select(\".domain\").remove();\nyAxis.select(\".domain\").attr(\"stroke\", t.inkSoft).attr(\"stroke-width\", 1.2);\n\n// Axis labels\ng.append(\"text\")\n  .attr(\"x\", iw / 2).attr(\"y\", ih + 56)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink).style(\"font-size\", \"22px\")\n  .text(\"Date (March 2026)\");\ng.append(\"text\")\n  .attr(\"transform\", \"rotate(-90)\")\n  .attr(\"x\", -ih / 2).attr(\"y\", -60)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink).style(\"font-size\", \"22px\")\n  .text(\"Daily high temperature (°C)\");\n\n// Area + line — first categorical series is Imprint palette[0]\nconst area = d3.area()\n  .x((d) => x(d.date))\n  .y0(ih)\n  .y1((d) => y(d.temp))\n  .curve(d3.curveMonotoneX);\n\nconst line = d3.line()\n  .x((d) => x(d.date))\n  .y((d) => y(d.temp))\n  .curve(d3.curveMonotoneX);\n\ng.append(\"path\")\n  .datum(data)\n  .attr(\"fill\", \"url(#fillGrad)\")\n  .attr(\"d\", area);\n\ng.append(\"path\")\n  .datum(data)\n  .attr(\"fill\", \"none\")\n  .attr(\"stroke\", t.palette[0])\n  .attr(\"stroke-width\", 3.5)\n  .attr(\"stroke-linecap\", \"round\")\n  .attr(\"stroke-linejoin\", \"round\")\n  .attr(\"d\", line);\n\n// Interior markers (skip the endpoint — emphasized separately below)\ng.selectAll(\"circle.point\")\n  .data(data.slice(0, -1)).join(\"circle\")\n  .attr(\"class\", \"point\")\n  .attr(\"cx\", (d) => x(d.date))\n  .attr(\"cy\", (d) => y(d.temp))\n  .attr(\"r\", 4)\n  .attr(\"fill\", t.palette[0])\n  .attr(\"stroke\", t.pageBg)\n  .attr(\"stroke-width\", 1.5);\n\n// Emphasized endpoint marker + value label — narrative anchor for the warming trend\ng.append(\"circle\")\n  .attr(\"cx\", x(endpoint.date)).attr(\"cy\", y(endpoint.temp))\n  .attr(\"r\", 9)\n  .attr(\"fill\", t.palette[0])\n  .attr(\"stroke\", t.pageBg)\n  .attr(\"stroke-width\", 2.5);\n\ng.append(\"text\")\n  .attr(\"x\", x(endpoint.date) + 18).attr(\"y\", y(endpoint.temp))\n  .attr(\"dy\", \"0.35em\")\n  .attr(\"text-anchor\", \"start\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"20px\")\n  .style(\"font-weight\", \"600\")\n  .text(`${endpoint.temp.toFixed(1)}°`);\n\n// Title\nsvg.append(\"text\")\n  .attr(\"x\", width / 2).attr(\"y\", 46)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"32px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"Spring Warming Trend · line-basic · javascript · d3 · anyplot.ai\");\n"}