{"spec_id":"line-parametric","library":"d3","language":"javascript","code":"// anyplot.ai\n// line-parametric: Parametric Curve Plot\n// Library: d3 7.9.0 | JavaScript 22.22.3\n// Quality: 88/100 | Created: 2026-06-20\n\nconst tok = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\n\n// Sequential Imprint colormap: green (t=0) → blue (t=max)\nconst colorT = d3.interpolateRgbBasis(tok.seq);\n\n// --- Data (in-memory, deterministic) ---\nconst N = 800;\nconst TWO_PI = 2 * Math.PI;\n\n// Lissajous figure: x = sin(3t), y = sin(2t), t ∈ [0, 2π]\nconst lissajous = Array.from({ length: N + 1 }, (_, i) => {\n  const p = (i / N) * TWO_PI;\n  return { u: i / N, x: Math.sin(3 * p), y: Math.sin(2 * p) };\n});\n\n// Archimedean spiral: x = t·cos(t), y = t·sin(t), t ∈ [0, 4π]\nconst T_SPIRAL = 4 * Math.PI;\nconst spiral = Array.from({ length: N + 1 }, (_, i) => {\n  const p = (i / N) * T_SPIRAL;\n  return { u: i / N, x: p * Math.cos(p), y: p * Math.sin(p) };\n});\n\n// --- SVG mount ---\nconst svg = d3.select(\"#container\").append(\"svg\")\n  .attr(\"width\", width).attr(\"height\", height);\n\n// Gradient definition for the legend bar\nconst defs = svg.append(\"defs\");\nconst gradDef = defs.append(\"linearGradient\").attr(\"id\", \"tSeq\")\n  .attr(\"x1\", \"0%\").attr(\"x2\", \"100%\");\nfor (let i = 0; i <= 10; i++) {\n  gradDef.append(\"stop\").attr(\"offset\", `${i * 10}%`).attr(\"stop-color\", colorT(i / 10));\n}\n\n// --- Layout: two square panels side by side ---\nconst panelSize = 660;\nconst panelGap = 80;\nconst startX = Math.floor((width - (2 * panelSize + panelGap)) / 2);\nconst startY = 110;\n\n// --- Helper: render one panel ---\nfunction drawPanel(points, xDomain, yDomain, titleText, formulaText, px, py) {\n  const xSc = d3.scaleLinear().domain(xDomain).range([0, panelSize]);\n  const ySc = d3.scaleLinear().domain(yDomain).range([panelSize, 0]);\n\n  const g = svg.append(\"g\").attr(\"transform\", `translate(${px},${py})`);\n\n  // Subtle gridlines (both axes — symmetric plot)\n  for (const v of xSc.ticks(5)) {\n    g.append(\"line\")\n      .attr(\"x1\", xSc(v)).attr(\"x2\", xSc(v)).attr(\"y1\", 0).attr(\"y2\", panelSize)\n      .attr(\"stroke\", tok.grid).attr(\"stroke-width\", 1);\n  }\n  for (const v of ySc.ticks(5)) {\n    g.append(\"line\")\n      .attr(\"x1\", 0).attr(\"x2\", panelSize).attr(\"y1\", ySc(v)).attr(\"y2\", ySc(v))\n      .attr(\"stroke\", tok.grid).attr(\"stroke-width\", 1);\n  }\n\n  // Axes\n  const xAx = g.append(\"g\").attr(\"transform\", `translate(0,${panelSize})`)\n    .call(d3.axisBottom(xSc).ticks(5).tickSizeOuter(0));\n  const yAx = g.append(\"g\")\n    .call(d3.axisLeft(ySc).ticks(5).tickSizeOuter(0));\n  for (const ax of [xAx, yAx]) {\n    ax.selectAll(\"text\").attr(\"fill\", tok.inkSoft).style(\"font-size\", \"13px\");\n    ax.selectAll(\".tick line\").attr(\"stroke\", tok.grid);\n    ax.select(\".domain\").attr(\"stroke\", tok.inkSoft);\n  }\n\n  // Curve: colored path segments encoding t-progress (Imprint sequential)\n  g.append(\"g\").selectAll(\"path\")\n    .data(d3.pairs(points))\n    .join(\"path\")\n    .attr(\"d\", ([a, b]) => `M${xSc(a.x)},${ySc(a.y)}L${xSc(b.x)},${ySc(b.y)}`)\n    .attr(\"stroke\", ([a]) => colorT(a.u))\n    .attr(\"stroke-width\", 2.5)\n    .attr(\"stroke-linecap\", \"round\")\n    .attr(\"fill\", \"none\");\n\n  // Direction chevron at ~30% of traversal\n  const ai = Math.floor(N * 0.3);\n  const pa = points[ai], pb = points[ai + 1];\n  const dx = xSc(pb.x) - xSc(pa.x);\n  const dy = ySc(pb.y) - ySc(pa.y);\n  const ang = Math.atan2(dy, dx) * 180 / Math.PI;\n  g.append(\"path\")\n    .attr(\"d\", \"M-9,-5 L0,0 L-9,5\")\n    .attr(\"transform\", `translate(${xSc(pa.x)},${ySc(pa.y)}) rotate(${ang})`)\n    .attr(\"stroke\", colorT(0.3))\n    .attr(\"stroke-width\", 2.5)\n    .attr(\"fill\", \"none\")\n    .attr(\"stroke-linecap\", \"round\")\n    .attr(\"stroke-linejoin\", \"round\");\n\n  // Start dot\n  const sp = points[0];\n  g.append(\"circle\")\n    .attr(\"cx\", xSc(sp.x)).attr(\"cy\", ySc(sp.y))\n    .attr(\"r\", 6).attr(\"fill\", colorT(0))\n    .attr(\"stroke\", tok.pageBg).attr(\"stroke-width\", 2);\n\n  // End dot (only if distinct from start — Lissajous is a closed curve)\n  const ep = points[N];\n  const distSE = Math.hypot(xSc(sp.x) - xSc(ep.x), ySc(sp.y) - ySc(ep.y));\n  if (distSE > 8) {\n    g.append(\"circle\")\n      .attr(\"cx\", xSc(ep.x)).attr(\"cy\", ySc(ep.y))\n      .attr(\"r\", 6).attr(\"fill\", colorT(1))\n      .attr(\"stroke\", tok.pageBg).attr(\"stroke-width\", 2);\n  }\n\n  // Panel title\n  g.append(\"text\")\n    .attr(\"x\", panelSize / 2).attr(\"y\", -34)\n    .attr(\"text-anchor\", \"middle\").attr(\"fill\", tok.ink)\n    .style(\"font-size\", \"17px\").style(\"font-weight\", \"600\")\n    .text(titleText);\n\n  // Formula subtitle (italic for visual hierarchy below panel title)\n  g.append(\"text\")\n    .attr(\"x\", panelSize / 2).attr(\"y\", -16)\n    .attr(\"text-anchor\", \"middle\").attr(\"fill\", tok.inkSoft)\n    .style(\"font-size\", \"13px\").style(\"font-style\", \"italic\")\n    .text(formulaText);\n\n  // Axis labels\n  g.append(\"text\")\n    .attr(\"x\", panelSize / 2).attr(\"y\", panelSize + 44)\n    .attr(\"text-anchor\", \"middle\").attr(\"fill\", tok.inkSoft)\n    .style(\"font-size\", \"13px\").text(\"x(t)\");\n\n  g.append(\"text\")\n    .attr(\"transform\", `rotate(-90) translate(${-panelSize / 2},${-50})`)\n    .attr(\"text-anchor\", \"middle\").attr(\"fill\", tok.inkSoft)\n    .style(\"font-size\", \"13px\").text(\"y(t)\");\n}\n\n// --- Draw panels ---\ndrawPanel(\n  lissajous,\n  [-1.15, 1.15], [-1.15, 1.15],\n  \"Lissajous Figure\",\n  \"x = sin(3t),  y = sin(2t),  t ∈ [0, 2π]\",\n  startX, startY\n);\n\nconst maxR = T_SPIRAL * 1.05;\ndrawPanel(\n  spiral,\n  [-maxR, maxR], [-maxR, maxR],\n  \"Archimedean Spiral\",\n  \"x = t·cos(t),  y = t·sin(t),  t ∈ [0, 4π]\",\n  startX + panelSize + panelGap, startY\n);\n\n// --- Direction legend ---\nconst legendW = 220, legendH = 12;\nconst legendX = (width - legendW) / 2;\nconst legendY = height - 42;\n\nsvg.append(\"text\")\n  .attr(\"x\", width / 2).attr(\"y\", legendY - 10)\n  .attr(\"text-anchor\", \"middle\").attr(\"fill\", tok.inkSoft)\n  .style(\"font-size\", \"13px\")\n  .text(\"Color encodes direction of parameter t\");\n\nsvg.append(\"rect\")\n  .attr(\"x\", legendX).attr(\"y\", legendY)\n  .attr(\"width\", legendW).attr(\"height\", legendH)\n  .attr(\"rx\", 3).attr(\"fill\", \"url(#tSeq)\");\n\nsvg.append(\"text\")\n  .attr(\"x\", legendX - 5).attr(\"y\", legendY + 10)\n  .attr(\"text-anchor\", \"end\").attr(\"fill\", tok.inkSoft).style(\"font-size\", \"13px\")\n  .text(\"t = 0 (start)\");\n\nsvg.append(\"text\")\n  .attr(\"x\", legendX + legendW + 5).attr(\"y\", legendY + 10)\n  .attr(\"text-anchor\", \"start\").attr(\"fill\", tok.inkSoft).style(\"font-size\", \"13px\")\n  .text(\"t = max (end)\");\n\n// --- Main title ---\nsvg.append(\"text\")\n  .attr(\"x\", width / 2).attr(\"y\", 52)\n  .attr(\"text-anchor\", \"middle\").attr(\"fill\", tok.ink)\n  .style(\"font-size\", \"22px\").style(\"font-weight\", \"600\")\n  .text(\"line-parametric · javascript · d3 · anyplot.ai\");\n"}