{"spec_id":"radar-basic","library":"muix","language":"javascript","code":"// anyplot.ai\n// radar-basic: Basic Radar Chart\n// Library: muix 7.29.1 | JavaScript 22.23.1\n// Quality: 89/100 | Created: 2026-07-24\n//# anyplot-orientation: square\n// anyplot.ai\n// radar-basic: Basic Radar 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-24\nimport { ChartContainer } from \"@mui/x-charts/ChartContainer\";\nimport { useDrawingArea } from \"@mui/x-charts/hooks\";\n\n// @mui/x-charts 7.x community has no RadarChart component (added in v8), so the\n// radar is composed on MUI X's own charting surface: ChartContainer provides the\n// sized <svg> + drawing area, and useDrawingArea() gives the plot rect we map the\n// polar geometry onto. Everything is real data drawn to scale — no faked chrome.\n\nconst t = window.ANYPLOT_TOKENS;\nconst size = window.ANYPLOT_SIZE;\n\n// Theme-adaptive chrome (ThemeProvider handles MUI text; these are for our SVG).\nconst INK = t.ink;\nconst INK_SOFT = t.inkSoft;\nconst GRID = t.grid;\nconst PAGE_BG = t.pageBg;\n\n// --- Data (in-memory, deterministic) — basketball player scouting report ------\nconst axes = [\"Speed\", \"Shooting\", \"Passing\", \"Defense\", \"Rebounding\", \"Stamina\"];\nconst series = [\n  { label: \"Guard\", color: t.palette[0], values: [90, 84, 92, 68, 52, 86] },\n  { label: \"Forward\", color: t.palette[1], values: [70, 76, 62, 85, 90, 80] },\n];\nconst MAX = 100;\nconst RINGS = [20, 40, 60, 80, 100];\nconst N = axes.length;\n\n// Axis i points from the top (-90°) going clockwise; SVG y grows downward.\nconst angleOf = (i) => (-90 + (i * 360) / N) * (Math.PI / 180);\n\n// --- Radar layer: rendered as children inside MUI X's ChartsSurface -----------\nfunction RadarLayer() {\n  const area = useDrawingArea();\n  const cx = area.left + area.width / 2;\n  const cy = area.top + area.height / 2;\n  const half = Math.min(area.width, area.height) / 2;\n  const R = half - 54; // data radius; the margin between R and half holds labels\n  const labelR = half - 4;\n\n  const point = (frac, i) => {\n    const a = angleOf(i);\n    return [cx + frac * R * Math.cos(a), cy + frac * R * Math.sin(a)];\n  };\n  const polygon = (frac) =>\n    axes.map((_, i) => point(frac, i).join(\",\")).join(\" \");\n\n  return (\n    <g>\n      {/* Concentric grid rings at each value level — outer ring solid, inner\n          rings lighter/thinner so the nested grid stays subtle at the center */}\n      {RINGS.map((level) => (\n        <polygon\n          key={`ring-${level}`}\n          points={polygon(level / MAX)}\n          fill=\"none\"\n          stroke={GRID}\n          strokeWidth={level === MAX ? 2 : 1}\n          strokeOpacity={level === MAX ? 1 : 0.6}\n        />\n      ))}\n\n      {/* Radial spokes + outer axis labels */}\n      {axes.map((label, i) => {\n        const [ox, oy] = point(1, i);\n        const a = angleOf(i);\n        const lx = cx + labelR * Math.cos(a);\n        const ly = cy + labelR * Math.sin(a);\n        const cos = Math.cos(a);\n        const anchor = cos > 0.15 ? \"start\" : cos < -0.15 ? \"end\" : \"middle\";\n        const sin = Math.sin(a);\n        const baseline = sin > 0.5 ? \"hanging\" : sin < -0.5 ? \"auto\" : \"central\";\n        return (\n          <g key={`axis-${label}`}>\n            <line x1={cx} y1={cy} x2={ox} y2={oy} stroke={GRID} strokeWidth={1.25} />\n            <text\n              x={lx}\n              y={ly}\n              fill={INK}\n              fontSize={18}\n              fontWeight={600}\n              textAnchor={anchor}\n              dominantBaseline={baseline}\n            >\n              {label}\n            </text>\n          </g>\n        );\n      })}\n\n      {/* Value tick labels, offset off the top spoke so they read on their own */}\n      {RINGS.map((level) => (\n        <text\n          key={`tick-${level}`}\n          x={cx + 12}\n          y={cy - (level / MAX) * R}\n          fill={INK_SOFT}\n          fontSize={15}\n          textAnchor=\"start\"\n          dominantBaseline=\"central\"\n        >\n          {level}\n        </text>\n      ))}\n\n      {/* Series polygons: translucent fill + solid outline + vertex markers */}\n      {series.map((s) => (\n        <g key={`series-${s.label}`}>\n          <polygon\n            points={axes.map((_, i) => point(s.values[i] / MAX, i).join(\",\")).join(\" \")}\n            fill={s.color}\n            fillOpacity={0.25}\n            stroke={s.color}\n            strokeWidth={3}\n            strokeLinejoin=\"round\"\n          />\n          {axes.map((_, i) => {\n            const [px, py] = point(s.values[i] / MAX, i);\n            return (\n              <circle\n                key={`pt-${s.label}-${i}`}\n                cx={px}\n                cy={py}\n                r={6}\n                fill={s.color}\n                stroke={PAGE_BG}\n                strokeWidth={2}\n              />\n            );\n          })}\n        </g>\n      ))}\n    </g>\n  );\n}\n\n// --- Title + legend drawn on the surface, then the radar layer ----------------\nfunction Chrome() {\n  const legendGap = 240;\n  const legendStart = size.width / 2 - ((series.length - 1) * legendGap) / 2;\n  const legendY = size.height - 44;\n  return (\n    <g>\n      <text\n        x={size.width / 2}\n        y={52}\n        fill={INK}\n        fontSize={28}\n        fontWeight={700}\n        textAnchor=\"middle\"\n      >\n        radar-basic · javascript · muix · anyplot.ai\n      </text>\n      {series.map((s, i) => {\n        const x = legendStart + i * legendGap;\n        return (\n          <g key={`legend-${s.label}`}>\n            <rect x={x - 84} y={legendY - 13} width={26} height={26} rx={5} fill={s.color} />\n            <text\n              x={x - 50}\n              y={legendY}\n              fill={INK}\n              fontSize={17}\n              textAnchor=\"start\"\n              dominantBaseline=\"central\"\n            >\n              {s.label}\n            </text>\n          </g>\n        );\n      })}\n    </g>\n  );\n}\n\n// --- Chart (default-exported component — the harness mounts it) ----------------\nexport default function Chart() {\n  return (\n    <ChartContainer\n      width={size.width}\n      height={size.height}\n      series={[]}\n      margin={{ top: 84, bottom: 84, left: 96, right: 96 }}\n      skipAnimation\n    >\n      <Chrome />\n      <RadarLayer />\n    </ChartContainer>\n  );\n}\n"}