{"spec_id":"circos-basic","library":"d3","language":"javascript","code":"// anyplot.ai\n// circos-basic: Circos Plot\n// Library: d3 7.9.0 | JavaScript 22.23.2\n// Quality: 90/100 | Created: 2026-09-04\n\n//# anyplot-orientation: square\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Inter-service call volume (calls / minute) between microservice modules.\nconst services = [\n  \"Frontend\",\n  \"API Gateway\",\n  \"Auth Service\",\n  \"User DB\",\n  \"Payment DB\",\n  \"Cache\",\n  \"Message Queue\",\n  \"Analytics\",\n];\n\n// matrix[i][j] = calls/min routed from services[i] to services[j]\nconst matrix = [\n  [0, 85, 0, 0, 0, 15, 0, 0],\n  [60, 0, 45, 30, 22, 55, 25, 0],\n  [0, 40, 0, 18, 0, 8, 0, 0],\n  [0, 28, 15, 0, 0, 0, 0, 0],\n  [0, 20, 0, 0, 0, 0, 10, 0],\n  [12, 50, 8, 0, 0, 0, 0, 0],\n  [0, 0, 0, 0, 12, 0, 0, 35],\n  [0, 0, 0, 0, 0, 0, 5, 0],\n];\n\n// Inner track: average response latency (ms) per service.\nconst latencyMs = [12, 8, 15, 25, 30, 3, 18, 40];\nconst maxLatency = d3.max(latencyMs);\n\n// --- Layout -------------------------------------------------------------\nconst titleSpace = 70;\nconst captionSpace = 50;\nconst sideMargin = 40;\nconst labelPad = 90;\n\nconst drawableW = width - sideMargin * 2;\nconst drawableH = height - titleSpace - captionSpace;\nconst outerRadius = Math.min(drawableW, drawableH) / 2 - labelPad;\nconst cx = width / 2;\nconst cy = titleSpace + drawableH / 2;\n\nconst segmentOuter = outerRadius;\nconst segmentInner = outerRadius - 34;\nconst trackOuter = segmentInner - 14;\nconst trackInner = trackOuter - 50;\nconst ribbonRadius = trackInner - 20;\n\nconst trackScale = d3.scaleLinear().domain([0, maxLatency]).range([0, trackOuter - trackInner]);\n\n// --- Chord layout ------------------------------------------------------\nconst chordLayout = d3.chord().padAngle(0.04).sortSubgroups(d3.descending);\nconst chords = chordLayout(matrix);\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(${cx},${cy})`);\n\n// --- Ribbons (connections between segments) -------------------------------\nconst ribbon = d3.ribbon().radius(ribbonRadius);\ng.append(\"g\")\n  .attr(\"fill-opacity\", 0.7)\n  .selectAll(\"path\")\n  .data(chords)\n  .join(\"path\")\n  .attr(\"d\", ribbon)\n  .attr(\"fill\", (d) => t.palette[d.source.index])\n  .attr(\"stroke\", t.pageBg)\n  .attr(\"stroke-width\", 1);\n\n// --- Segment groups ----------------------------------------------------\nconst group = g.append(\"g\").selectAll(\"g\").data(chords.groups).join(\"g\");\n\n// track background (full-scale reference band)\nconst trackArc = d3.arc().innerRadius(trackInner).outerRadius(trackOuter);\ngroup.append(\"path\").attr(\"d\", trackArc).attr(\"fill\", t.grid);\n\n// track value (latency, scaled from the shared baseline)\ngroup\n  .append(\"path\")\n  .attr(\n    \"d\",\n    (d) => d3.arc().innerRadius(trackInner).outerRadius(trackInner + trackScale(latencyMs[d.index]))(d),\n  )\n  .attr(\"fill\", (d) => t.palette[d.index])\n  .attr(\"fill-opacity\", 0.85);\n\n// outer segment arc\nconst segmentArc = d3.arc().innerRadius(segmentInner).outerRadius(segmentOuter);\ngroup\n  .append(\"path\")\n  .attr(\"d\", segmentArc)\n  .attr(\"fill\", (d) => t.palette[d.index])\n  .attr(\"stroke\", t.pageBg)\n  .attr(\"stroke-width\", 2);\n\n// segment labels\ngroup\n  .append(\"text\")\n  .each((d) => {\n    d.midAngle = (d.startAngle + d.endAngle) / 2;\n  })\n  .attr(\"dy\", \"0.35em\")\n  .attr(\n    \"transform\",\n    (d) => `\n      rotate(${(d.midAngle * 180) / Math.PI - 90})\n      translate(${segmentOuter + 14})\n      ${d.midAngle > Math.PI ? \"rotate(180)\" : \"\"}\n    `,\n  )\n  .attr(\"text-anchor\", (d) => (d.midAngle > Math.PI ? \"end\" : \"start\"))\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"16px\")\n  .style(\"font-weight\", \"500\")\n  .text((d) => services[d.index]);\n\n// --- Title ---------------------------------------------------------------\nsvg\n  .append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", 44)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"22px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"circos-basic · javascript · d3 · anyplot.ai\");\n\n// --- Caption (encoding legend) ---------------------------------------------\nsvg\n  .append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", height - captionSpace / 2 + 6)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"14px\")\n  .text(`Ribbon width ∝ calls/min between services · inner ring ∝ avg. latency (0–${maxLatency} ms)`);\n"}