{"spec_id":"rose-basic","library":"muix","language":"javascript","code":"// anyplot.ai\n// rose-basic: Basic Rose Chart\n// Library: muix 7.29.1 | JavaScript 22.23.1\n// Quality: 91/100 | Created: 2026-07-25\n//# anyplot-orientation: square\n// anyplot.ai\n// rose-basic: Basic Rose Chart\n// Library: MUI X Charts | React | Node 22\n// License: @mui/x-charts — MIT (community). Pro/Premium are out of scope.\n// Quality: pending | Created: 2026-07-25\nimport { PieChart } from \"@mui/x-charts/PieChart\";\n\nconst t = window.ANYPLOT_TOKENS;\nconst TITLE = \"rose-basic · javascript · muix · anyplot.ai\";\n\n// --- Data (in-memory, deterministic): average monthly rainfall, mm ----------\nconst MONTHS = [\n  \"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\",\n  \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\",\n];\nconst RAINFALL_MM = [88, 65, 70, 52, 48, 32, 22, 28, 45, 78, 95, 92];\n\n// Linear interpolation between two Imprint hex stops (imprint_seq).\nfunction mixHex(hexA, hexB, ratio) {\n  const a = [1, 3, 5].map((i) => parseInt(hexA.slice(i, i + 2), 16));\n  const b = [1, 3, 5].map((i) => parseInt(hexB.slice(i, i + 2), 16));\n  const mixed = a.map((v, i) => Math.round(v + (b[i] - v) * ratio));\n  return `#${mixed.map((v) => v.toString(16).padStart(2, \"0\")).join(\"\")}`;\n}\n\n// 0deg = 12 o'clock, positive = clockwise (matches PieChart's own arc convention).\nfunction polarPoint(cx, cy, radius, angleDeg) {\n  const angleRad = (angleDeg * Math.PI) / 180;\n  return [cx + radius * Math.sin(angleRad), cy - radius * Math.cos(angleRad)];\n}\n\n// --- Chart (default-exported component — the harness mounts it) ------------\nexport default function Chart() {\n  const { width, height } = window.ANYPLOT_SIZE;\n  const titleHeight = 64;\n  const side = Math.min(width, height - titleHeight);\n  const cx = width / 2;\n  const cy = titleHeight + side / 2;\n  const outerMargin = 96;\n  const maxRadius = side / 2 - outerMargin;\n  const innerFloor = maxRadius * 0.1;\n\n  const maxValue = Math.max(...RAINFALL_MM);\n  const radiusFor = (value) =>\n    innerFloor + (value / maxValue) * (maxRadius - innerFloor);\n\n  const sliceAngle = 360 / MONTHS.length;\n  const gapDeg = 3;\n  const halfWidth = (sliceAngle - gapDeg) / 2;\n\n  // Radial gridlines — three value rings; their values are called out in a\n  // corner caption rather than inline (inline labels crowd the short petals).\n  const gridTicks = [1 / 3, 2 / 3, 1].map((frac) => Math.round(maxValue * frac));\n\n  const petals = MONTHS.map((month, i) => {\n    const value = RAINFALL_MM[i];\n    const centerAngle = i * sliceAngle;\n    return {\n      type: \"pie\",\n      id: `petal-${month}`,\n      data: [\n        {\n          id: month,\n          value: 1,\n          label: `${month}: ${value} mm`,\n          color: mixHex(t.seq[0], t.seq[1], value / maxValue),\n        },\n      ],\n      cx,\n      cy,\n      innerRadius: 0,\n      outerRadius: radiusFor(value),\n      cornerRadius: 3,\n      startAngle: centerAngle - halfWidth,\n      endAngle: centerAngle + halfWidth,\n    };\n  });\n\n  return (\n    <div style={{ position: \"relative\", width, height }}>\n      <svg width={width} height={height} style={{ position: \"absolute\", top: 0, left: 0 }}>\n        <text x={width / 2} y={36} textAnchor=\"middle\" fontSize={22} fontWeight={500} fill={t.ink}>\n          {TITLE}\n        </text>\n        <text x={width - 32} y={36} textAnchor=\"end\" fontSize={14} fill={t.inkSoft}>\n          {`gridlines: ${gridTicks.join(\" / \")} mm`}\n        </text>\n\n        {gridTicks.map((tick) => (\n          <circle\n            key={`ring-${tick}`}\n            cx={cx}\n            cy={cy}\n            r={radiusFor(tick)}\n            fill=\"none\"\n            stroke={t.grid}\n            strokeWidth={1.5}\n          />\n        ))}\n\n        {MONTHS.map((month, i) => {\n          const [x2, y2] = polarPoint(cx, cy, maxRadius, i * sliceAngle);\n          return (\n            <line\n              key={`spoke-${month}`}\n              x1={cx}\n              y1={cy}\n              x2={x2}\n              y2={y2}\n              stroke={t.grid}\n              strokeWidth={1}\n            />\n          );\n        })}\n\n        {MONTHS.map((month, i) => {\n          const angle = i * sliceAngle;\n          const [x, y] = polarPoint(cx, cy, maxRadius + 28, angle);\n          const dx = x - cx;\n          let anchor = \"middle\";\n          let baseline = \"middle\";\n          if (angle < 1 || angle > 359) {\n            baseline = \"auto\";\n          } else if (Math.abs(angle - 180) < 1) {\n            baseline = \"hanging\";\n          } else {\n            anchor = dx > 0 ? \"start\" : \"end\";\n          }\n          return (\n            <text key={`label-${month}`} x={x} y={y} textAnchor={anchor} dominantBaseline={baseline} fontSize={15} fill={t.inkSoft}>\n              {month}\n            </text>\n          );\n        })}\n      </svg>\n\n      <div style={{ position: \"absolute\", top: titleHeight, left: (width - side) / 2 }}>\n        <PieChart\n          width={side}\n          height={side}\n          series={petals}\n          margin={{ top: 0, right: 0, bottom: 0, left: 0 }}\n          legend={{ hidden: true }}\n          skipAnimation\n        />\n      </div>\n    </div>\n  );\n}\n"}