{"spec_id":"scatter-complex-plane","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// scatter-complex-plane: Complex Plane Visualization (Argand Diagram)\n// Library: highcharts 12.6.0 | JavaScript 22.23.2\n// Quality: 93/100 | Created: 2026-08-26\n//# anyplot-orientation: square\n// anyplot.ai\n// scatter-complex-plane: Complex Plane Visualization (Argand Diagram)\n// Library: Highcharts 12.6.0 | Node 22\n// License: Highcharts — commercial license, free for non-commercial use (highcharts.com/license)\n// Quality: pending | Created: 2026-08-26\n\nconst t = window.ANYPLOT_TOKENS;\n\n// --- Data (in-memory, deterministic) ---------------------------------------\n\n// 5th roots of unity: z_k = e^(2*pi*i*k/5), k = 0..4 — sit exactly on the unit circle\nconst ROOT_COUNT = 5;\nconst roots = [];\nfor (let k = 0; k < ROOT_COUNT; k++) {\n  const angle = (2 * Math.PI * k) / ROOT_COUNT;\n  roots.push({\n    re: Math.cos(angle),\n    im: Math.sin(angle),\n    angleDeg: Math.round((360 * k) / ROOT_COUNT),\n  });\n}\n\n// A few arbitrary complex numbers away from the unit circle\nconst arbitraryPoints = [\n  { re: 2.4, im: 1.2 },\n  { re: -1.8, im: 1.6 },\n  { re: -0.6, im: -2.1 },\n];\n\n// Axis runs -3..3; flip the anchor inward once a point sits within\n// EDGE_MARGIN of an edge so its label doesn't crowd the canvas border.\nconst AXIS_LIMIT = 3;\nconst EDGE_MARGIN = 0.7;\n\nfunction pointLabelConfig(re, im, extraLine) {\n  const rect = `${re.toFixed(2)} ${im >= 0 ? \"+\" : \"-\"} ${Math.abs(im).toFixed(2)}i`;\n  const nearRight = re > AXIS_LIMIT - EDGE_MARGIN;\n  const nearLeft = re < -AXIS_LIMIT + EDGE_MARGIN;\n  const nearTop = im > AXIS_LIMIT - EDGE_MARGIN;\n  const nearBottom = im < -AXIS_LIMIT + EDGE_MARGIN;\n  const align = nearRight ? \"right\" : nearLeft ? \"left\" : re >= 0 ? \"left\" : \"right\";\n  const verticalAlign = nearTop ? \"top\" : nearBottom ? \"bottom\" : im >= 0 ? \"bottom\" : \"top\";\n  return {\n    align,\n    verticalAlign,\n    x: align === \"left\" ? 10 : -10,\n    y: verticalAlign === \"bottom\" ? -10 : 10,\n    formatter: function () {\n      return extraLine ? `${rect}<br/>${extraLine}` : rect;\n    },\n  };\n}\n\nconst rootData = roots.map((p) => ({\n  x: p.re,\n  y: p.im,\n  dataLabels: pointLabelConfig(p.re, p.im, `r=1.00, θ=${p.angleDeg}°`),\n}));\n\nconst arbitraryData = arbitraryPoints.map((p) => ({\n  x: p.re,\n  y: p.im,\n  dataLabels: pointLabelConfig(p.re, p.im),\n}));\n\n// Unit circle reference (dashed), sampled every 5 degrees\nconst circlePoints = [];\nfor (let deg = 0; deg <= 360; deg += 5) {\n  const rad = (deg * Math.PI) / 180;\n  circlePoints.push([Math.cos(rad), Math.sin(rad)]);\n}\n\n// Vector bodies from the origin to each point, one series per category with\n// null gaps between segments so each point gets its own line\nfunction vectorSegments(points) {\n  const segments = [];\n  points.forEach((p) => {\n    segments.push([0, 0], [p.re, p.im], null);\n  });\n  return segments;\n}\n\nconst rootVectors = vectorSegments(roots);\nconst arbitraryVectors = vectorSegments(arbitraryPoints);\n\n// --- Custom SVG overlays ------------------------------------------------------\n// Core Highcharts has no vector/arrow series and no way to fill a parametric\n// closed curve, so both the unit-circle shading and the arrowhead triangles\n// are drawn directly onto the SVG with the renderer once the axis pixel\n// mapping (including the equal-aspect adjustment below) is final.\nfunction drawCircleFill(chart, xAxis, yAxis, color) {\n  const cx = xAxis.toPixels(0);\n  const cy = yAxis.toPixels(0);\n  const r = Math.abs(xAxis.toPixels(1) - cx);\n  chart.renderer.circle(cx, cy, r).attr({ fill: color, opacity: 0.07, zIndex: 0 }).add();\n}\n\nfunction drawArrowhead(chart, xAxis, yAxis, re, im, color) {\n  const x0 = xAxis.toPixels(0);\n  const y0 = yAxis.toPixels(0);\n  const x1 = xAxis.toPixels(re);\n  const y1 = yAxis.toPixels(im);\n  const dx = x1 - x0;\n  const dy = y1 - y0;\n  const len = Math.sqrt(dx * dx + dy * dy) || 1;\n  const ux = dx / len;\n  const uy = dy / len;\n  const headLength = 14;\n  const headWidth = 8;\n  const baseX = x1 - ux * headLength;\n  const baseY = y1 - uy * headLength;\n  const px = -uy;\n  const py = ux;\n  chart.renderer\n    .path([\n      \"M\", x1, y1,\n      \"L\", baseX + px * headWidth, baseY + py * headWidth,\n      \"L\", baseX - px * headWidth, baseY - py * headWidth,\n      \"Z\",\n    ])\n    .attr({ fill: color, zIndex: 6 })\n    .add();\n}\n\n// --- Chart -------------------------------------------------------------------\nHighcharts.chart(\"container\", {\n  chart: {\n    type: \"line\",\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n    events: {\n      load: function () {\n        const chart = this;\n        const xAxis = chart.xAxis[0];\n        const yAxis = chart.yAxis[0];\n        const xRange = xAxis.max - xAxis.min;\n        const yRange = yAxis.max - yAxis.min;\n        const xScale = chart.plotWidth / xRange;\n        const yScale = chart.plotHeight / yRange;\n\n        // Force equal units-per-pixel on both axes so the unit circle renders circular\n        if (xScale > yScale) {\n          const targetRange = chart.plotWidth / yScale;\n          const mid = (xAxis.min + xAxis.max) / 2;\n          xAxis.setExtremes(mid - targetRange / 2, mid + targetRange / 2, false);\n        } else if (yScale > xScale) {\n          const targetRange = chart.plotHeight / xScale;\n          const mid = (yAxis.min + yAxis.max) / 2;\n          yAxis.setExtremes(mid - targetRange / 2, mid + targetRange / 2, false);\n        }\n        chart.redraw();\n\n        const finalXAxis = chart.xAxis[0];\n        const finalYAxis = chart.yAxis[0];\n        drawCircleFill(chart, finalXAxis, finalYAxis, t.inkSoft);\n        roots.forEach((p) => drawArrowhead(chart, finalXAxis, finalYAxis, p.re, p.im, t.palette[0]));\n        arbitraryPoints.forEach((p) => drawArrowhead(chart, finalXAxis, finalYAxis, p.re, p.im, t.palette[1]));\n      },\n    },\n  },\n  credits: { enabled: false },\n  colors: t.palette,\n  title: {\n    text: \"scatter-complex-plane · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  xAxis: {\n    title: { text: \"Real\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n    min: -3,\n    max: 3,\n    tickInterval: 1,\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    gridLineColor: t.grid,\n    plotLines: [{ value: 0, color: t.inkSoft, width: 1.5, zIndex: 4 }],\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n  },\n  yAxis: {\n    title: { text: \"Imaginary\", style: { color: t.inkSoft, fontSize: \"16px\" } },\n    min: -3,\n    max: 3,\n    tickInterval: 1,\n    gridLineColor: t.grid,\n    plotLines: [{ value: 0, color: t.inkSoft, width: 1.5, zIndex: 4 }],\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n  },\n  legend: {\n    itemStyle: { color: t.inkSoft, fontSize: \"14px\" },\n    itemHoverStyle: { color: t.ink },\n  },\n  tooltip: { enabled: false },\n  plotOptions: {\n    series: { animation: false },\n    line: { enableMouseTracking: false, marker: { enabled: false } },\n    scatter: {\n      // Small — the arrowhead tip drawn by drawArrowhead() already marks the\n      // point; this just keeps the point visible under the triangle.\n      marker: { radius: 3, lineWidth: 0 },\n      dataLabels: {\n        enabled: true,\n        useHTML: false,\n        style: { color: t.ink, fontSize: \"13px\", fontWeight: \"400\", textOutline: \"none\" },\n        backgroundColor: t.elevatedBg,\n        borderRadius: 4,\n        padding: 4,\n      },\n    },\n  },\n  series: [\n    {\n      type: \"line\",\n      name: \"Unit circle\",\n      data: circlePoints,\n      color: t.inkSoft,\n      dashStyle: \"Dash\",\n      lineWidth: 1.5,\n      showInLegend: false,\n      zIndex: 1,\n    },\n    {\n      type: \"line\",\n      name: \"Roots-of-unity vectors\",\n      data: rootVectors,\n      color: t.palette[0],\n      lineWidth: 2,\n      showInLegend: false,\n      zIndex: 2,\n    },\n    {\n      type: \"line\",\n      name: \"Arbitrary-point vectors\",\n      data: arbitraryVectors,\n      color: t.palette[1],\n      lineWidth: 2,\n      dashStyle: \"ShortDash\",\n      showInLegend: false,\n      zIndex: 2,\n    },\n    {\n      type: \"scatter\",\n      name: \"5th roots of unity\",\n      data: rootData,\n      color: t.palette[0],\n      zIndex: 3,\n    },\n    {\n      type: \"scatter\",\n      name: \"Arbitrary points\",\n      data: arbitraryData,\n      color: t.palette[1],\n      zIndex: 3,\n    },\n  ],\n});\n"}