{"spec_id":"quiver-basic","library":"muix","language":"javascript","code":"// anyplot.ai\n// quiver-basic: Basic Quiver Plot\n// Library: muix 7.29.1 | JavaScript 22.23.1\n// Quality: 91/100 | Created: 2026-07-24\n//# anyplot-orientation: square\n// anyplot.ai\n// quiver-basic: Basic Quiver Plot\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\n\nimport { ChartContainer } from \"@mui/x-charts/ChartContainer\";\nimport { ChartsXAxis } from \"@mui/x-charts/ChartsXAxis\";\nimport { ChartsYAxis } from \"@mui/x-charts/ChartsYAxis\";\nimport { useXScale, useYScale, useDrawingArea } from \"@mui/x-charts/hooks\";\n\nconst t = window.ANYPLOT_TOKENS;\nconst FONT = \"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif\";\n\n// --- Data: cyclonic wind field around a low-pressure center ----------------\n// Rankine vortex model — solid-body rotation inside the core radius, decaying\n// tangential flow outside it. Positions are km from the storm center; wind\n// speed is m/s. This produces the calm \"eye\" at the center and the peak wind\n// band at the core radius that real low-pressure systems exhibit.\nconst DOMAIN = 7; // km, grid spans [-DOMAIN, DOMAIN] on both axes\nconst GRID_N = 15; // 15x15 = 225 arrows\nconst CORE_RADIUS = 3.2; // km, radius of peak tangential wind\nconst PEAK_SPEED = 22; // m/s, tangential wind speed at the core radius\nconst ARROW_SCALE = 0.85 / PEAK_SPEED; // km of arrow length per m/s of speed\n\nconst VECTORS = [];\nfor (let i = 0; i < GRID_N; i += 1) {\n  for (let j = 0; j < GRID_N; j += 1) {\n    const x = -DOMAIN + (2 * DOMAIN * i) / (GRID_N - 1);\n    const y = -DOMAIN + (2 * DOMAIN * j) / (GRID_N - 1);\n    const r = Math.sqrt(x * x + y * y) + 1e-6;\n    const speed = r <= CORE_RADIUS ? (PEAK_SPEED * r) / CORE_RADIUS : (PEAK_SPEED * CORE_RADIUS) / r;\n    const u = (-y / r) * speed; // horizontal component (dx)\n    const v = (x / r) * speed; // vertical component (dy)\n    VECTORS.push({ x, y, u, v, speed });\n  }\n}\n\n// Interpolate between the two imprint_seq stops by a 0..1 fraction.\nfunction lerpColor(hexA, hexB, frac) {\n  const a = parseInt(hexA.slice(1), 16);\n  const b = parseInt(hexB.slice(1), 16);\n  const ar = (a >> 16) & 255, ag = (a >> 8) & 255, ab = a & 255;\n  const br = (b >> 16) & 255, bg = (b >> 8) & 255, bb = b & 255;\n  const rr = Math.round(ar + (br - ar) * frac);\n  const rg = Math.round(ag + (bg - ag) * frac);\n  const rb = Math.round(ab + (bb - ab) * frac);\n  return `rgb(${rr}, ${rg}, ${rb})`;\n}\n\n// --- Arrows — drawn on the MUI X coordinate system via scale hooks ---------\nfunction QuiverArrows() {\n  const xScale = useXScale();\n  const yScale = useYScale();\n  const HEAD = 10; // px\n\n  return (\n    <g strokeLinecap=\"round\">\n      {VECTORS.map((vec, i) => {\n        const x1 = xScale(vec.x);\n        const y1 = yScale(vec.y);\n        const x2 = xScale(vec.x + vec.u * ARROW_SCALE);\n        const y2 = yScale(vec.y + vec.v * ARROW_SCALE);\n        const frac = Math.min(1, vec.speed / PEAK_SPEED);\n        const color = lerpColor(t.seq[0], t.seq[1], frac);\n        const angle = Math.atan2(y2 - y1, x2 - x1);\n        const a1 = angle - 0.4;\n        const a2 = angle + 0.4;\n        const headPoints = `${x2},${y2} ${x2 - HEAD * Math.cos(a1)},${y2 - HEAD * Math.sin(a1)} ${x2 - HEAD * Math.cos(a2)},${y2 - HEAD * Math.sin(a2)}`;\n        return (\n          <g key={i}>\n            <line x1={x1} y1={y1} x2={x2} y2={y2} stroke={color} strokeWidth={2.2} />\n            <polygon points={headPoints} fill={color} />\n          </g>\n        );\n      })}\n    </g>\n  );\n}\n\n// Colorbar gradient encoding wind speed magnitude.\nfunction Colorbar() {\n  const { left, top, width: gW, height: gH } = useDrawingArea();\n  const cbX = left + gW + 26;\n  const cbW = 18;\n\n  return (\n    <>\n      <defs>\n        <linearGradient id=\"quiverSpeedGrad\" x1=\"0\" y1=\"1\" x2=\"0\" y2=\"0\">\n          <stop offset=\"0%\" stopColor={t.seq[0]} />\n          <stop offset=\"100%\" stopColor={t.seq[1]} />\n        </linearGradient>\n      </defs>\n      <rect x={cbX} y={top} width={cbW} height={gH} fill=\"url(#quiverSpeedGrad)\" />\n      <text x={cbX + cbW / 2} y={top - 8} textAnchor=\"middle\" fontSize={13} fill={t.inkSoft} fontFamily={FONT}>\n        {PEAK_SPEED}\n      </text>\n      <text x={cbX + cbW / 2} y={top + gH + 18} textAnchor=\"middle\" fontSize={13} fill={t.inkSoft} fontFamily={FONT}>\n        0\n      </text>\n      <text\n        x={cbX + cbW + 20}\n        y={top + gH / 2}\n        textAnchor=\"middle\"\n        fontSize={14}\n        fill={t.inkSoft}\n        fontFamily={FONT}\n        transform={`rotate(90, ${cbX + cbW + 20}, ${top + gH / 2})`}\n      >\n        Wind Speed (m/s)\n      </text>\n    </>\n  );\n}\n\nfunction ChartTitle({ text, fontSize }) {\n  const { left, top, width: gW } = useDrawingArea();\n  return (\n    <text\n      x={left + gW / 2}\n      y={top - 62}\n      textAnchor=\"middle\"\n      fontSize={fontSize}\n      fontWeight={600}\n      fill={t.ink}\n      fontFamily={FONT}\n    >\n      {text}\n    </text>\n  );\n}\n\nexport default function Chart() {\n  const W = window.ANYPLOT_SIZE.width;\n  const H = window.ANYPLOT_SIZE.height;\n\n  const title = \"Cyclonic Wind Field · quiver-basic · javascript · muix · anyplot.ai\";\n  const titleSize = title.length > 67 ? Math.round(22 * (67 / title.length)) : 22;\n\n  const pad = 0.8;\n\n  return (\n    <ChartContainer\n      width={W}\n      height={H}\n      skipAnimation\n      series={[]}\n      xAxis={[{ scaleType: \"linear\", min: -DOMAIN - pad, max: DOMAIN + pad, label: \"X Position (km)\" }]}\n      yAxis={[{ scaleType: \"linear\", min: -DOMAIN - pad, max: DOMAIN + pad, label: \"Y Position (km)\" }]}\n      margin={{ top: 130, right: 170, bottom: 100, left: 100 }}\n    >\n      <ChartTitle text={title} fontSize={titleSize} />\n      <QuiverArrows />\n      <Colorbar />\n      <ChartsXAxis labelStyle={{ fontSize: 16 }} tickLabelStyle={{ fontSize: 14 }} />\n      <ChartsYAxis labelStyle={{ fontSize: 16 }} tickLabelStyle={{ fontSize: 14 }} />\n    </ChartContainer>\n  );\n}\n"}