{"spec_id":"recurrence-basic","library":"d3","language":"javascript","code":"// anyplot.ai\n// recurrence-basic: Recurrence Plot for Nonlinear Time Series\n// Library: d3 7.9.0 | JavaScript 22.22.3\n// Quality: 93/100 | Created: 2026-06-10\n\n//# anyplot-orientation: square\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\n\n// Extended right and bottom margins for structural annotations\nconst margin = { top: 70, right: 180, bottom: 200, left: 90 };\nconst iw = width - margin.left - margin.right;   // 930 (square plot area)\nconst ih = height - margin.top - margin.bottom;  // 930\n\n// --- Data: logistic map (deterministic, r=3.85 → chaotic regime) ----------\nconst R = 3.85, TAU = 3, N_STEPS = 350, N_BURN = 50;\nconst xs = new Array(N_STEPS);\nxs[0] = 0.5;\nfor (let k = 1; k < N_STEPS; k++) xs[k] = R * xs[k - 1] * (1 - xs[k - 1]);\n\nconst series = xs.slice(N_BURN);      // 300 post-transient values\nconst M = series.length - TAU;        // 297 embedded points\n\n// Time-delay embedding: e[i] = [x(i), x(i + TAU)]\nconst emb = new Array(M);\nfor (let i = 0; i < M; i++) emb[i] = [series[i], series[i + TAU]];\n\n// Euclidean distance matrix — collect upper triangle for threshold\nconst dist = Array.from({ length: M }, () => new Float32Array(M));\nconst upperD = [];\nfor (let i = 0; i < M; i++) {\n  for (let j = i + 1; j < M; j++) {\n    const d = Math.hypot(emb[i][0] - emb[j][0], emb[i][1] - emb[j][1]);\n    dist[i][j] = dist[j][i] = d;\n    upperD.push(d);\n  }\n}\nupperD.sort((a, b) => a - b);\nconst eps = upperD[Math.floor(upperD.length * 0.15)];  // ~15% recurrence rate\n\n// --- Recurrence matrix → offscreen canvas → SVG image ---------------------\nconst offCanvas = document.createElement(\"canvas\");\noffCanvas.width = M;\noffCanvas.height = M;\nconst ctx = offCanvas.getContext(\"2d\");\n\nconst hr = h => [parseInt(h.slice(1, 3), 16), parseInt(h.slice(3, 5), 16), parseInt(h.slice(5, 7), 16)];\nconst [bR, bG, bB] = hr(t.pageBg);\nconst [fR, fG, fB] = hr(t.palette[0]);  // Imprint palette[0] = #009E73\n\nconst imgData = ctx.createImageData(M, M);\nconst px = imgData.data;\n\nfor (let i = 0; i < M; i++) {\n  for (let j = 0; j < M; j++) {\n    const idx = 4 * (i * M + j);\n    if (dist[i][j] <= eps) {\n      px[idx] = fR; px[idx + 1] = fG; px[idx + 2] = fB; px[idx + 3] = 255;\n    } else {\n      px[idx] = bR; px[idx + 1] = bG; px[idx + 2] = bB; px[idx + 3] = 255;\n    }\n  }\n}\nctx.putImageData(imgData, 0, 0);\n\n// --- SVG mount --------------------------------------------------------------\nconst svg = d3.select(\"#container\").append(\"svg\")\n  .attr(\"width\", width).attr(\"height\", height);\n\nsvg.append(\"rect\").attr(\"width\", width).attr(\"height\", height).attr(\"fill\", t.pageBg);\n\n// Arrowhead marker for annotation leader lines\nsvg.append(\"defs\").append(\"marker\")\n  .attr(\"id\", \"arr\")\n  .attr(\"viewBox\", \"0 0 6 6\")\n  .attr(\"markerWidth\", 6).attr(\"markerHeight\", 6)\n  .attr(\"refX\", 5).attr(\"refY\", 3)\n  .attr(\"orient\", \"auto\")\n  .append(\"path\").attr(\"d\", \"M0,0 L0,6 L6,3 z\").attr(\"fill\", t.inkSoft);\n\nconst g = svg.append(\"g\").attr(\"transform\", `translate(${margin.left},${margin.top})`);\n\n// Recurrence image — pixelated rendering keeps cell edges crisp\ng.append(\"image\")\n  .attr(\"x\", 0).attr(\"y\", 0)\n  .attr(\"width\", iw).attr(\"height\", ih)\n  .attr(\"preserveAspectRatio\", \"none\")\n  .style(\"image-rendering\", \"pixelated\")\n  .attr(\"href\", offCanvas.toDataURL());\n\n// Plot border — slightly heavier for visual definition\ng.append(\"rect\")\n  .attr(\"x\", 0).attr(\"y\", 0)\n  .attr(\"width\", iw).attr(\"height\", ih)\n  .attr(\"fill\", \"none\")\n  .attr(\"stroke\", t.inkSoft).attr(\"stroke-width\", 1);\n\n// --- Axes ------------------------------------------------------------------\nconst xScale = d3.scaleLinear().domain([0, M - 1]).range([0, iw]);\nconst yScale = d3.scaleLinear().domain([0, M - 1]).range([0, ih]);\n\nconst xAxisG = g.append(\"g\").attr(\"transform\", `translate(0,${ih})`)\n  .call(d3.axisBottom(xScale).ticks(6).tickSize(4));\nconst yAxisG = g.append(\"g\")\n  .call(d3.axisLeft(yScale).ticks(6).tickSize(4));\n\nfor (const ax of [xAxisG, yAxisG]) {\n  ax.selectAll(\"text\").attr(\"fill\", t.inkSoft).style(\"font-size\", \"14px\");\n  ax.selectAll(\"line\").attr(\"stroke\", t.inkSoft);\n  ax.select(\".domain\").attr(\"stroke\", \"none\");  // border rect handles the edge\n}\n\n// --- Axis labels -----------------------------------------------------------\ng.append(\"text\")\n  .attr(\"x\", iw / 2).attr(\"y\", ih + 65)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.inkSoft).style(\"font-size\", \"16px\")\n  .text(\"Time Index (i)\");\n\ng.append(\"text\")\n  .attr(\"transform\", \"rotate(-90)\")\n  .attr(\"x\", -(ih / 2)).attr(\"y\", -68)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.inkSoft).style(\"font-size\", \"16px\")\n  .text(\"Time Index (j)\");\n\n// --- ε threshold annotation (analytical context) --------------------------\ng.append(\"text\")\n  .attr(\"x\", iw / 2).attr(\"y\", ih + 40)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.inkSoft).style(\"font-size\", \"13px\").style(\"font-style\", \"italic\")\n  .text(`ε = ${eps.toFixed(3)}  ·  15% recurrence rate  ·  logistic map r = 3.85, τ = 3`);\n\n// --- Structural annotations (D3 text + leader lines into matrix) -----------\n// Pixel positions of annotation targets inside the 930×930 plot area\nconst diagTargetX = Math.round(ih * 0.10);  // point on main diagonal (i=j≈30)\nconst diagTargetY = diagTargetX;\nconst stripeTargetX = Math.round(iw * 0.505); // offset-stripe at i≈150, j≈137\nconst stripeTargetY = Math.round(ih * 0.461);\n\nconst annX = iw + 18;  // start of annotation text in right margin\n\n// Annotation 1: main diagonal — leader from text region into diagonal\ng.append(\"line\")\n  .attr(\"x1\", annX - 10).attr(\"y1\", diagTargetY)\n  .attr(\"x2\", diagTargetX + 4).attr(\"y2\", diagTargetY)\n  .attr(\"stroke\", t.inkSoft).attr(\"stroke-width\", 0.7)\n  .attr(\"stroke-dasharray\", \"4,3\")\n  .attr(\"marker-end\", \"url(#arr)\");\ng.append(\"text\")\n  .attr(\"x\", annX).attr(\"y\", diagTargetY - 5)\n  .attr(\"fill\", t.ink).style(\"font-size\", \"13px\").style(\"font-weight\", \"600\")\n  .text(\"Main diagonal\");\ng.append(\"text\")\n  .attr(\"x\", annX).attr(\"y\", diagTargetY + 12)\n  .attr(\"fill\", t.inkSoft).style(\"font-size\", \"12px\").style(\"font-style\", \"italic\")\n  .text(\"i = j  (identity)\");\n\n// Annotation 2: parallel off-diagonal stripe — quasi-periodicity indicator\ng.append(\"line\")\n  .attr(\"x1\", annX - 10).attr(\"y1\", stripeTargetY)\n  .attr(\"x2\", stripeTargetX + 4).attr(\"y2\", stripeTargetY)\n  .attr(\"stroke\", t.inkSoft).attr(\"stroke-width\", 0.7)\n  .attr(\"stroke-dasharray\", \"4,3\")\n  .attr(\"marker-end\", \"url(#arr)\");\ng.append(\"text\")\n  .attr(\"x\", annX).attr(\"y\", stripeTargetY - 5)\n  .attr(\"fill\", t.ink).style(\"font-size\", \"13px\").style(\"font-weight\", \"600\")\n  .text(\"Parallel stripes\");\ng.append(\"text\")\n  .attr(\"x\", annX).attr(\"y\", stripeTargetY + 12)\n  .attr(\"fill\", t.inkSoft).style(\"font-size\", \"12px\").style(\"font-style\", \"italic\")\n  .text(\"quasi-periodicity\");\n\n// --- Title -----------------------------------------------------------------\nsvg.append(\"text\")\n  .attr(\"x\", width / 2).attr(\"y\", 48)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"22px\").style(\"font-weight\", \"600\")\n  .text(\"recurrence-basic · javascript · d3 · anyplot.ai\");\n"}