{"spec_id":"bifurcation-basic","library":"d3","language":"javascript","code":"// anyplot.ai\n// bifurcation-basic: Bifurcation Diagram for Dynamical Systems\n// Library: d3 7.9.0 | JavaScript 22.22.3\n// Quality: 91/100 | Created: 2026-06-17\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\n\nconst margin = { top: 90, right: 60, bottom: 85, left: 90 };\nconst iw = width - margin.left - margin.right;\nconst ih = height - margin.top - margin.bottom;\n\n// Container layout — canvas + SVG stack\nconst container = document.getElementById('container');\ncontainer.style.position = 'relative';\ncontainer.style.overflow = 'hidden';\n\n// Canvas for data points (2× DPR so pixels are crisp at deviceScaleFactor 2)\nconst DPR = 2;\nconst canvas = document.createElement('canvas');\ncanvas.width = width * DPR;\ncanvas.height = height * DPR;\ncanvas.style.width = width + 'px';\ncanvas.style.height = height + 'px';\ncanvas.style.position = 'absolute';\ncanvas.style.top = '0';\ncanvas.style.left = '0';\ncontainer.appendChild(canvas);\n\nconst ctx = canvas.getContext('2d');\nctx.scale(DPR, DPR);\n\n// Background\nctx.fillStyle = t.pageBg;\nctx.fillRect(0, 0, width, height);\n\n// D3 scales\nconst rMin = 2.5, rMax = 4.0;\nconst xScale = d3.scaleLinear().domain([rMin, rMax]).range([margin.left, margin.left + iw]);\nconst yScale = d3.scaleLinear().domain([0, 1]).range([margin.top + ih, margin.top]);\n\n// Horizontal gridlines drawn before data so they sit behind the points\nconst yGridVals = [0.2, 0.4, 0.6, 0.8];\nctx.strokeStyle = t.grid;\nctx.lineWidth = 1;\nyGridVals.forEach(v => {\n  const cy = yScale(v);\n  ctx.beginPath();\n  ctx.moveTo(margin.left, cy);\n  ctx.lineTo(margin.left + iw, cy);\n  ctx.stroke();\n});\n\n// Clip to chart area then draw bifurcation data\nctx.save();\nctx.beginPath();\nctx.rect(margin.left, margin.top, iw, ih);\nctx.clip();\n\nconst numR = 1500;\nconst transient = 200;\nconst plotIter = 100;\n\nctx.globalAlpha = 0.38;\nctx.fillStyle = t.palette[0]; // #009E73 — Imprint brand green, always first series\n\nfor (let i = 0; i <= numR; i++) {\n  const r = rMin + (rMax - rMin) * i / numR;\n  let x = 0.5;\n  for (let j = 0; j < transient; j++) {\n    x = r * x * (1 - x);\n  }\n  const cx = xScale(r);\n  for (let j = 0; j < plotIter; j++) {\n    x = r * x * (1 - x);\n    const cy = yScale(x);\n    ctx.fillRect(cx - 0.5, cy - 0.5, 1, 1);\n  }\n}\n\nctx.restore();\n\n// SVG overlay — axes, labels, bifurcation markers, title\nconst svg = d3.select('#container').append('svg')\n  .attr('width', width)\n  .attr('height', height)\n  .style('position', 'absolute')\n  .style('top', '0')\n  .style('left', '0');\n\n// X axis\nconst xAxisG = svg.append('g')\n  .attr('transform', `translate(0,${margin.top + ih})`)\n  .call(d3.axisBottom(xScale).ticks(7).tickFormat(d3.format('.1f')));\n\nxAxisG.selectAll('text')\n  .attr('fill', t.inkSoft)\n  .style('font-size', '14px')\n  .style('font-family', 'system-ui, sans-serif');\nxAxisG.selectAll('.tick line').attr('stroke', t.inkSoft);\nxAxisG.select('.domain').attr('stroke', t.inkSoft);\n\n// Y axis\nconst yAxisG = svg.append('g')\n  .attr('transform', `translate(${margin.left},0)`)\n  .call(d3.axisLeft(yScale).ticks(5).tickFormat(d3.format('.1f')));\n\nyAxisG.selectAll('text')\n  .attr('fill', t.inkSoft)\n  .style('font-size', '14px')\n  .style('font-family', 'system-ui, sans-serif');\nyAxisG.selectAll('.tick line').attr('stroke', t.inkSoft);\nyAxisG.select('.domain').attr('stroke', t.inkSoft);\n\n// Bifurcation point markers (dashed verticals + labels above chart)\nconst bifPoints = [\n  { r: 3.0, label: 'r ≈ 3.0' },\n  { r: 3.449, label: 'r ≈ 3.449' },\n  { r: 3.544, label: 'r ≈ 3.544' },\n];\n\nbifPoints.forEach(({ r, label }) => {\n  const cx = xScale(r);\n\n  svg.append('line')\n    .attr('x1', cx).attr('x2', cx)\n    .attr('y1', margin.top).attr('y2', margin.top + ih)\n    .attr('stroke', t.inkSoft)\n    .attr('stroke-width', 1)\n    .attr('stroke-dasharray', '5,5')\n    .attr('opacity', 0.55);\n\n  svg.append('text')\n    .attr('x', cx)\n    .attr('y', margin.top - 14)\n    .attr('text-anchor', 'middle')\n    .attr('fill', t.inkSoft)\n    .style('font-size', '12px')\n    .style('font-family', 'system-ui, sans-serif')\n    .text(label);\n});\n\n// Axis labels\nsvg.append('text')\n  .attr('x', margin.left + iw / 2)\n  .attr('y', margin.top + ih + 58)\n  .attr('text-anchor', 'middle')\n  .attr('fill', t.ink)\n  .style('font-size', '16px')\n  .style('font-family', 'system-ui, sans-serif')\n  .text('Growth Rate Parameter r');\n\nsvg.append('text')\n  .attr('transform', `translate(${margin.left - 65},${margin.top + ih / 2}) rotate(-90)`)\n  .attr('text-anchor', 'middle')\n  .attr('fill', t.ink)\n  .style('font-size', '16px')\n  .style('font-family', 'system-ui, sans-serif')\n  .text('Population x');\n\n// Title\nsvg.append('text')\n  .attr('x', width / 2)\n  .attr('y', 48)\n  .attr('text-anchor', 'middle')\n  .attr('fill', t.ink)\n  .style('font-size', '22px')\n  .style('font-weight', '600')\n  .style('font-family', 'system-ui, sans-serif')\n  .text('bifurcation-basic · javascript · d3 · anyplot.ai');\n"}