{"spec_id":"quiver-basic","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// quiver-basic: Basic Quiver Plot\n// Library: highcharts 12.6.0 | JavaScript 22.23.1\n// Quality: 92/100 | Created: 2026-07-24\n//# anyplot-orientation: square\n\nconst t = window.ANYPLOT_TOKENS;\n\n// Distinctive Highcharts feature: register a reusable custom arrowhead shape\n// via the SVGRenderer symbol registry, then draw it with a rotation transform\n// per vector — this is Highcharts' idiomatic mechanism for custom markers,\n// not just a generic path drawn by hand.\nHighcharts.SVGRenderer.prototype.symbols.quiverArrowhead = (x, y, w, h) => [\n  \"M\", x, y,\n  \"L\", x + w, y + h / 2,\n  \"L\", x, y + h,\n  \"Z\",\n];\n\n// --- Data: idealized vortex/eddy flow field u = -y + 0.2x, v = x + 0.2y, ---\n// --- on a 12x12 grid (a rotating current with a slight outward drift, as ---\n// --- seen in atmospheric/ocean eddies) -------------------------------------\nconst GRID = 12;\nconst EXTENT = 6;\nconst STEP = (2 * EXTENT) / (GRID - 1);\n\nconst vectors = [];\nlet maxMagnitude = 0;\nfor (let i = 0; i < GRID; i++) {\n  for (let j = 0; j < GRID; j++) {\n    const x = -EXTENT + i * STEP;\n    const y = -EXTENT + j * STEP;\n    const u = -y + 0.2 * x;\n    const v = x + 0.2 * y;\n    const magnitude = Math.hypot(u, v);\n    maxMagnitude = Math.max(maxMagnitude, magnitude);\n    vectors.push({ x, y, u, v, magnitude });\n  }\n}\n\n// Scale every vector so the longest arrow spans ~90% of the grid spacing —\n// keeps arrows from overlapping their neighbors regardless of field strength.\nconst ARROW_SCALE = (STEP * 0.9) / maxMagnitude;\n\n// --- Color: imprint_seq (brand green -> blue), driven by vector magnitude -\nconst hexToRgb = (hex) => [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16));\nconst [r0, g0, b0] = hexToRgb(t.seq[0]);\nconst [r1, g1, b1] = hexToRgb(t.seq[1]);\nconst colorForMagnitude = (magnitude) => {\n  const f = magnitude / maxMagnitude;\n  const r = Math.round(r0 + (r1 - r0) * f);\n  const g = Math.round(g0 + (g1 - g0) * f);\n  const b = Math.round(b0 + (b1 - b0) * f);\n  return `rgb(${r}, ${g}, ${b})`;\n};\n\n// --- Chart ------------------------------------------------------------------\n// The core bundle has no vector-field series type, so the axes below only fix\n// the coordinate system; every arrow is drawn as a native SVG path through\n// chart.renderer (toPixels() converts data coords to screen space) on each\n// render pass — no add-on module, no other library involved.\nHighcharts.chart(\"container\", {\n  chart: {\n    backgroundColor: \"transparent\",\n    animation: false,\n    marginRight: 100,\n    style: { fontFamily: \"inherit\" },\n    events: {\n      render() {\n        if (this.arrowGroup) this.arrowGroup.destroy();\n        this.arrowGroup = this.renderer.g(\"arrows\").add();\n\n        vectors.forEach(({ x, y, u, v, magnitude }) => {\n          const dx = (u * ARROW_SCALE) / 2;\n          const dy = (v * ARROW_SCALE) / 2;\n          const x0 = this.xAxis[0].toPixels(x - dx);\n          const y0 = this.yAxis[0].toPixels(y - dy);\n          const x1 = this.xAxis[0].toPixels(x + dx);\n          const y1 = this.yAxis[0].toPixels(y + dy);\n          const color = colorForMagnitude(magnitude);\n\n          this.renderer\n            .path([\"M\", x0, y0, \"L\", x1, y1])\n            .attr({ stroke: color, \"stroke-width\": 2.2, opacity: 0.9 })\n            .add(this.arrowGroup);\n\n          // Reusable arrowhead symbol, rotated to the vector's angle and\n          // pivoted around its tip — Highcharts' renderer.symbol() registry\n          // plus the rotation/rotationOriginX/Y transform is the idiomatic\n          // way to place oriented custom markers.\n          const angleDeg = (Math.atan2(y1 - y0, x1 - x0) * 180) / Math.PI;\n          const shaftLength = Math.hypot(x1 - x0, y1 - y0);\n          const headLength = Math.min(10, shaftLength * 0.45);\n\n          this.renderer\n            .symbol(\"quiverArrowhead\", x1 - headLength, y1 - headLength / 2, headLength, headLength)\n            .attr({\n              fill: color,\n              opacity: 0.9,\n              rotation: angleDeg,\n              rotationOriginX: x1,\n              rotationOriginY: y1,\n            })\n            .add(this.arrowGroup);\n        });\n\n        // --- Magnitude color scale (reserved in the marginRight strip) ----\n        if (this.magnitudeScale) this.magnitudeScale.destroy();\n        this.magnitudeScale = this.renderer.g(\"magnitude-scale\").add();\n\n        const barX = this.plotLeft + this.plotWidth + 30;\n        const barWidth = 14;\n        const barTop = this.plotTop + 10;\n        const barHeight = this.plotHeight - 20;\n        const segments = 32;\n\n        for (let s = 0; s < segments; s++) {\n          const value = (maxMagnitude * (s + 0.5)) / segments;\n          const segHeight = barHeight / segments;\n          const segY = barTop + barHeight - (s + 1) * segHeight;\n          this.renderer\n            .rect(barX, segY, barWidth, segHeight + 0.5)\n            .attr({ fill: colorForMagnitude(value), stroke: \"none\" })\n            .add(this.magnitudeScale);\n        }\n        this.renderer\n          .rect(barX, barTop, barWidth, barHeight)\n          .attr({ stroke: t.inkSoft, \"stroke-width\": 1, fill: \"none\" })\n          .add(this.magnitudeScale);\n\n        this.renderer\n          .text(maxMagnitude.toFixed(1), barX + barWidth + 6, barTop + 10)\n          .attr({ align: \"left\" })\n          .css({ color: t.inkSoft, fontSize: \"12px\" })\n          .add(this.magnitudeScale);\n        this.renderer\n          .text(\"0\", barX + barWidth + 6, barTop + barHeight)\n          .attr({ align: \"left\" })\n          .css({ color: t.inkSoft, fontSize: \"12px\" })\n          .add(this.magnitudeScale);\n        this.renderer\n          .text(\"‖v‖\", barX + barWidth / 2, barTop - 12)\n          .attr({ align: \"center\" })\n          .css({ color: t.inkSoft, fontSize: \"12px\" })\n          .add(this.magnitudeScale);\n      },\n    },\n  },\n  credits: { enabled: false },\n  colors: t.palette,\n  title: {\n    text: \"quiver-basic · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  subtitle: {\n    text: \"Idealized vortex/eddy flow field · arrow color + scale encode vector magnitude\",\n    style: { color: t.inkSoft, fontSize: \"14px\" },\n  },\n  xAxis: {\n    min: -EXTENT - STEP,\n    max: EXTENT + STEP,\n    startOnTick: false,\n    endOnTick: false,\n    title: { text: \"Grid X\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    gridLineColor: t.grid,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n  },\n  yAxis: {\n    min: -EXTENT - STEP,\n    max: EXTENT + STEP,\n    startOnTick: false,\n    endOnTick: false,\n    title: { text: \"Grid Y\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    gridLineColor: t.grid,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n  },\n  legend: { enabled: false },\n  tooltip: { enabled: false },\n  plotOptions: { series: { animation: false, enableMouseTracking: false } },\n  series: [\n    {\n      type: \"scatter\",\n      name: \"Grid\",\n      data: vectors.map((p) => [p.x, p.y]),\n      marker: { enabled: false },\n      showInLegend: false,\n    },\n  ],\n});\n"}