{"spec_id":"quiver-basic","library":"chartjs","language":"javascript","code":"// anyplot.ai\n// quiver-basic: Basic Quiver Plot\n// Library: chartjs 4.4.7 | JavaScript 22.23.1\n// Quality: 91/100 | Created: 2026-07-24\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data: circular ocean-current rotation field, u = -y, v = x ------------\n// (rigid rotation: |velocity| = sqrt(u^2+v^2) = distance from the eddy center)\nconst X_MIN = -7,\n  X_MAX = 7,\n  X_STEP = 1;\nconst Y_MIN = -4,\n  Y_MAX = 4,\n  Y_STEP = 1;\n\nconst vectors = [];\nfor (let y = Y_MIN; y <= Y_MAX; y += Y_STEP) {\n  for (let x = X_MIN; x <= X_MAX; x += X_STEP) {\n    const u = -y; // eastward current component (km/h)\n    const v = x; // northward current component (km/h)\n    vectors.push({ x, y, u, v, mag: Math.sqrt(u * u + v * v) });\n  }\n}\n\nconst maxMag = Math.max(...vectors.map((p) => p.mag));\nconst ARROW_SCALE = 0.8 / maxMag; // longest arrow spans 0.8 grid units, avoids overlap\n\nfunction hexToRgb(hex) {\n  const n = parseInt(hex.slice(1), 16);\n  return [(n >> 16) & 255, (n >> 8) & 255, n & 255];\n}\nfunction lerpColor(hexA, hexB, f) {\n  const a = hexToRgb(hexA);\n  const b = hexToRgb(hexB);\n  const c = a.map((channel, i) => Math.round(channel + (b[i] - channel) * f));\n  return `rgb(${c[0]}, ${c[1]}, ${c[2]})`;\n}\n\n// Color encodes current speed via the Imprint sequential scale (single-polarity).\nvectors.forEach((p) => {\n  p.color = lerpColor(t.seq[0], t.seq[1], p.mag / maxMag);\n});\n\n// --- Mount -------------------------------------------------------------------\nconst canvas = document.createElement(\"canvas\");\ndocument.getElementById(\"container\").appendChild(canvas);\n\n// --- Arrow-drawing plugin (native Chart.js plugin API, no community deps) ---\nconst quiverArrowsPlugin = {\n  id: \"quiverArrows\",\n  afterDatasetsDraw(chart) {\n    const { ctx, scales } = chart;\n    const xScale = scales.x;\n    const yScale = scales.y;\n    const headLen = 9; // px, screen space\n\n    ctx.save();\n    vectors.forEach((p) => {\n      const tailX = xScale.getPixelForValue(p.x);\n      const tailY = yScale.getPixelForValue(p.y);\n      const tipX = xScale.getPixelForValue(p.x + p.u * ARROW_SCALE);\n      const tipY = yScale.getPixelForValue(p.y + p.v * ARROW_SCALE);\n      const angle = Math.atan2(tipY - tailY, tipX - tailX);\n\n      ctx.strokeStyle = p.color;\n      ctx.fillStyle = p.color;\n      ctx.lineWidth = 1.8;\n      ctx.lineCap = \"round\";\n\n      ctx.beginPath();\n      ctx.moveTo(tailX, tailY);\n      ctx.lineTo(tipX, tipY);\n      ctx.stroke();\n\n      ctx.beginPath();\n      ctx.moveTo(tipX, tipY);\n      ctx.lineTo(\n        tipX - headLen * Math.cos(angle - Math.PI / 7),\n        tipY - headLen * Math.sin(angle - Math.PI / 7),\n      );\n      ctx.lineTo(\n        tipX - headLen * Math.cos(angle + Math.PI / 7),\n        tipY - headLen * Math.sin(angle + Math.PI / 7),\n      );\n      ctx.closePath();\n      ctx.fill();\n    });\n    ctx.restore();\n  },\n};\n\n// --- Chart ---------------------------------------------------------------\nnew Chart(canvas, {\n  type: \"scatter\",\n  data: {\n    datasets: [\n      {\n        label: \"Grid points\",\n        data: vectors.map((p) => ({ x: p.x, y: p.y })),\n        backgroundColor: vectors.map((p) => p.color),\n        pointRadius: 3,\n        pointHoverRadius: 3,\n        showLine: false,\n      },\n    ],\n  },\n  options: {\n    responsive: true,\n    maintainAspectRatio: false,\n    animation: false,\n    plugins: {\n      title: {\n        display: true,\n        text: \"quiver-basic · javascript · chartjs · anyplot.ai\",\n        color: t.ink,\n        font: { size: 22 },\n      },\n      subtitle: {\n        display: true,\n        text: \"Ocean current rotation field — arrow color encodes current speed (km/h)\",\n        color: t.inkSoft,\n        font: { size: 14 },\n        padding: { bottom: 12 },\n      },\n      legend: { display: false },\n    },\n    scales: {\n      x: {\n        min: -9,\n        max: 9,\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { color: t.grid },\n        title: {\n          display: true,\n          text: \"Eastward distance (km)\",\n          color: t.ink,\n          font: { size: 16 },\n        },\n      },\n      y: {\n        min: -5,\n        max: 5,\n        ticks: { color: t.inkSoft, font: { size: 14 } },\n        grid: { color: t.grid },\n        title: {\n          display: true,\n          text: \"Northward distance (km)\",\n          color: t.ink,\n          font: { size: 16 },\n        },\n      },\n    },\n  },\n  plugins: [quiverArrowsPlugin],\n});\n"}