{"spec_id":"rose-basic","library":"d3","language":"javascript","code":"// anyplot.ai\n// rose-basic: Basic Rose Chart\n// Library: d3 7.9.0 | JavaScript 22.23.1\n// Quality: 91/100 | Created: 2026-07-25\n//# anyplot-orientation: square\n\nconst t = window.ANYPLOT_TOKENS;\nconst { width, height } = window.ANYPLOT_SIZE;\n\n// --- Data (in-memory, deterministic) ----------------------------------------\n// Monthly rainfall for a temperate coastal city (mm), Jan at 12 o'clock\nconst monthlyRainfall = [\n  { month: \"Jan\", mm: 142 },\n  { month: \"Feb\", mm: 118 },\n  { month: \"Mar\", mm: 98 },\n  { month: \"Apr\", mm: 68 },\n  { month: \"May\", mm: 47 },\n  { month: \"Jun\", mm: 38 },\n  { month: \"Jul\", mm: 19 },\n  { month: \"Aug\", mm: 24 },\n  { month: \"Sep\", mm: 52 },\n  { month: \"Oct\", mm: 96 },\n  { month: \"Nov\", mm: 151 },\n  { month: \"Dec\", mm: 158 },\n];\n\n// --- Layout -------------------------------------------------------------------\nconst marginTop = 110;\nconst cx = width / 2;\nconst cy = marginTop + (height - marginTop) / 2;\nconst maxRadius = Math.min(width, height - marginTop) / 2 - 110;\n\n// --- Scales ---------------------------------------------------------------------\nconst angleStep = (2 * Math.PI) / monthlyRainfall.length;\nconst wedgeAngle = angleStep * 0.82;\nconst maxRainfall = d3.max(monthlyRainfall, (d) => d.mm);\nconst radius = d3.scaleLinear().domain([0, maxRainfall]).nice().range([0, maxRadius]);\n// Explicit lightness-scaled hex per value (not `fill-opacity`) so equal rainfall\n// values read as the same color in both themes — opacity would otherwise blend\n// against the theme-dependent page background and shift hue between renders.\nconst wedgeColor = d3.scaleLinear().domain([0, maxRainfall]).range([\"#BFE9DB\", t.palette[0]]);\n\nmonthlyRainfall.forEach((d, i) => {\n  d.centerAngle = i * angleStep;\n  d.startAngle = d.centerAngle - wedgeAngle / 2;\n  d.endAngle = d.centerAngle + wedgeAngle / 2;\n});\n\n// --- SVG mount --------------------------------------------------------------------\nconst svg = d3.select(\"#container\").append(\"svg\").attr(\"width\", width).attr(\"height\", height);\nconst plot = svg.append(\"g\").attr(\"transform\", `translate(${cx},${cy})`);\n\n// --- Radial gridlines (aid value estimation) ---------------------------------------\nconst gridTicks = radius.ticks(4).filter((v) => v > 0);\n\nplot\n  .selectAll(\"circle.grid-ring\")\n  .data(gridTicks)\n  .join(\"circle\")\n  .attr(\"class\", \"grid-ring\")\n  .attr(\"r\", (d) => radius(d))\n  .attr(\"fill\", \"none\")\n  .attr(\"stroke\", t.grid)\n  .attr(\"stroke-width\", 1.5);\n\n// Placed along the bottom (Jul) spoke, where the shortest wedges leave the\n// rings clear — every other spoke direction is crossed by a tall wedge.\nplot\n  .selectAll(\"text.grid-label\")\n  .data(gridTicks)\n  .join(\"text\")\n  .attr(\"class\", \"grid-label\")\n  .attr(\"x\", 8)\n  .attr(\"y\", (d) => radius(d) + 4)\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"16px\")\n  .text((d) => `${d} mm`);\n\n// --- Wedges (radius proportional to value) --------------------------------------------\nconst wedge = d3\n  .arc()\n  .innerRadius(0)\n  .outerRadius((d) => radius(d.mm))\n  .startAngle((d) => d.startAngle)\n  .endAngle((d) => d.endAngle);\n\nplot\n  .selectAll(\"path.wedge\")\n  .data(monthlyRainfall)\n  .join(\"path\")\n  .attr(\"class\", \"wedge\")\n  .attr(\"d\", wedge)\n  .attr(\"fill\", (d) => wedgeColor(d.mm))\n  .attr(\"stroke\", t.pageBg)\n  .attr(\"stroke-width\", 2);\n\n// --- Month labels -----------------------------------------------------------------\nconst labelRadius = maxRadius + 46;\n\nplot\n  .selectAll(\"text.month-label\")\n  .data(monthlyRainfall)\n  .join(\"text\")\n  .attr(\"class\", \"month-label\")\n  .attr(\"x\", (d) => labelRadius * Math.sin(d.centerAngle))\n  .attr(\"y\", (d) => -labelRadius * Math.cos(d.centerAngle) + 5)\n  .attr(\"text-anchor\", (d) => {\n    const s = Math.sin(d.centerAngle);\n    if (s > 0.15) return \"start\";\n    if (s < -0.15) return \"end\";\n    return \"middle\";\n  })\n  .attr(\"fill\", t.inkSoft)\n  .style(\"font-size\", \"16px\")\n  .text((d) => d.month);\n\n// --- Title --------------------------------------------------------------------------\nsvg\n  .append(\"text\")\n  .attr(\"x\", width / 2)\n  .attr(\"y\", 52)\n  .attr(\"text-anchor\", \"middle\")\n  .attr(\"fill\", t.ink)\n  .style(\"font-size\", \"26px\")\n  .style(\"font-weight\", \"600\")\n  .text(\"rose-basic · javascript · d3 · anyplot.ai\");\n"}