Complex Plane Visualization (Argand Diagram) — Highcharts

An Argand diagram plots complex numbers as points in the complex plane, with the real part on the x-axis and the imaginary part on the y-axis. Vectors from the origin to each point illustrate magnitude and phase angle, while a unit circle provides a geometric reference. This visualization is foundational for complex analysis, signal processing, and understanding operations like addition, multiplication, and roots of unity geometrically.

Complex Plane Visualization (Argand Diagram) rendered with Highcharts

Renders

JavaScript source (Highcharts)

// anyplot.ai
// scatter-complex-plane: Complex Plane Visualization (Argand Diagram)
// Library: highcharts 12.6.0 | JavaScript 22.23.2
// Quality: 93/100 | Created: 2026-08-26
//# anyplot-orientation: square
// anyplot.ai
// scatter-complex-plane: Complex Plane Visualization (Argand Diagram)
// Library: Highcharts 12.6.0 | Node 22
// License: Highcharts — commercial license, free for non-commercial use (highcharts.com/license)
// Quality: pending | Created: 2026-08-26

const t = window.ANYPLOT_TOKENS;

// --- Data (in-memory, deterministic) ---------------------------------------

// 5th roots of unity: z_k = e^(2*pi*i*k/5), k = 0..4 — sit exactly on the unit circle
const ROOT_COUNT = 5;
const roots = [];
for (let k = 0; k < ROOT_COUNT; k++) {
  const angle = (2 * Math.PI * k) / ROOT_COUNT;
  roots.push({
    re: Math.cos(angle),
    im: Math.sin(angle),
    angleDeg: Math.round((360 * k) / ROOT_COUNT),
  });
}

// A few arbitrary complex numbers away from the unit circle
const arbitraryPoints = [
  { re: 2.4, im: 1.2 },
  { re: -1.8, im: 1.6 },
  { re: -0.6, im: -2.1 },
];

// Axis runs -3..3; flip the anchor inward once a point sits within
// EDGE_MARGIN of an edge so its label doesn't crowd the canvas border.
const AXIS_LIMIT = 3;
const EDGE_MARGIN = 0.7;

function pointLabelConfig(re, im, extraLine) {
  const rect = `${re.toFixed(2)} ${im >= 0 ? "+" : "-"} ${Math.abs(im).toFixed(2)}i`;
  const nearRight = re > AXIS_LIMIT - EDGE_MARGIN;
  const nearLeft = re < -AXIS_LIMIT + EDGE_MARGIN;
  const nearTop = im > AXIS_LIMIT - EDGE_MARGIN;
  const nearBottom = im < -AXIS_LIMIT + EDGE_MARGIN;
  const align = nearRight ? "right" : nearLeft ? "left" : re >= 0 ? "left" : "right";
  const verticalAlign = nearTop ? "top" : nearBottom ? "bottom" : im >= 0 ? "bottom" : "top";
  return {
    align,
    verticalAlign,
    x: align === "left" ? 10 : -10,
    y: verticalAlign === "bottom" ? -10 : 10,
    formatter: function () {
      return extraLine ? `${rect}<br/>${extraLine}` : rect;
    },
  };
}

const rootData = roots.map((p) => ({
  x: p.re,
  y: p.im,
  dataLabels: pointLabelConfig(p.re, p.im, `r=1.00, θ=${p.angleDeg}°`),
}));

const arbitraryData = arbitraryPoints.map((p) => ({
  x: p.re,
  y: p.im,
  dataLabels: pointLabelConfig(p.re, p.im),
}));

// Unit circle reference (dashed), sampled every 5 degrees
const circlePoints = [];
for (let deg = 0; deg <= 360; deg += 5) {
  const rad = (deg * Math.PI) / 180;
  circlePoints.push([Math.cos(rad), Math.sin(rad)]);
}

// Vector bodies from the origin to each point, one series per category with
// null gaps between segments so each point gets its own line
function vectorSegments(points) {
  const segments = [];
  points.forEach((p) => {
    segments.push([0, 0], [p.re, p.im], null);
  });
  return segments;
}

const rootVectors = vectorSegments(roots);
const arbitraryVectors = vectorSegments(arbitraryPoints);

// --- Custom SVG overlays ------------------------------------------------------
// Core Highcharts has no vector/arrow series and no way to fill a parametric
// closed curve, so both the unit-circle shading and the arrowhead triangles
// are drawn directly onto the SVG with the renderer once the axis pixel
// mapping (including the equal-aspect adjustment below) is final.
function drawCircleFill(chart, xAxis, yAxis, color) {
  const cx = xAxis.toPixels(0);
  const cy = yAxis.toPixels(0);
  const r = Math.abs(xAxis.toPixels(1) - cx);
  chart.renderer.circle(cx, cy, r).attr({ fill: color, opacity: 0.07, zIndex: 0 }).add();
}

function drawArrowhead(chart, xAxis, yAxis, re, im, color) {
  const x0 = xAxis.toPixels(0);
  const y0 = yAxis.toPixels(0);
  const x1 = xAxis.toPixels(re);
  const y1 = yAxis.toPixels(im);
  const dx = x1 - x0;
  const dy = y1 - y0;
  const len = Math.sqrt(dx * dx + dy * dy) || 1;
  const ux = dx / len;
  const uy = dy / len;
  const headLength = 14;
  const headWidth = 8;
  const baseX = x1 - ux * headLength;
  const baseY = y1 - uy * headLength;
  const px = -uy;
  const py = ux;
  chart.renderer
    .path([
      "M", x1, y1,
      "L", baseX + px * headWidth, baseY + py * headWidth,
      "L", baseX - px * headWidth, baseY - py * headWidth,
      "Z",
    ])
    .attr({ fill: color, zIndex: 6 })
    .add();
}

// --- Chart -------------------------------------------------------------------
Highcharts.chart("container", {
  chart: {
    type: "line",
    backgroundColor: "transparent",
    animation: false,
    style: { fontFamily: "inherit" },
    events: {
      load: function () {
        const chart = this;
        const xAxis = chart.xAxis[0];
        const yAxis = chart.yAxis[0];
        const xRange = xAxis.max - xAxis.min;
        const yRange = yAxis.max - yAxis.min;
        const xScale = chart.plotWidth / xRange;
        const yScale = chart.plotHeight / yRange;

        // Force equal units-per-pixel on both axes so the unit circle renders circular
        if (xScale > yScale) {
          const targetRange = chart.plotWidth / yScale;
          const mid = (xAxis.min + xAxis.max) / 2;
          xAxis.setExtremes(mid - targetRange / 2, mid + targetRange / 2, false);
        } else if (yScale > xScale) {
          const targetRange = chart.plotHeight / xScale;
          const mid = (yAxis.min + yAxis.max) / 2;
          yAxis.setExtremes(mid - targetRange / 2, mid + targetRange / 2, false);
        }
        chart.redraw();

        const finalXAxis = chart.xAxis[0];
        const finalYAxis = chart.yAxis[0];
        drawCircleFill(chart, finalXAxis, finalYAxis, t.inkSoft);
        roots.forEach((p) => drawArrowhead(chart, finalXAxis, finalYAxis, p.re, p.im, t.palette[0]));
        arbitraryPoints.forEach((p) => drawArrowhead(chart, finalXAxis, finalYAxis, p.re, p.im, t.palette[1]));
      },
    },
  },
  credits: { enabled: false },
  colors: t.palette,
  title: {
    text: "scatter-complex-plane · javascript · highcharts · anyplot.ai",
    style: { color: t.ink, fontSize: "22px", fontWeight: "600" },
  },
  xAxis: {
    title: { text: "Real", style: { color: t.inkSoft, fontSize: "16px" } },
    min: -3,
    max: 3,
    tickInterval: 1,
    lineColor: t.inkSoft,
    tickColor: t.inkSoft,
    gridLineColor: t.grid,
    plotLines: [{ value: 0, color: t.inkSoft, width: 1.5, zIndex: 4 }],
    labels: { style: { color: t.inkSoft, fontSize: "14px" } },
  },
  yAxis: {
    title: { text: "Imaginary", style: { color: t.inkSoft, fontSize: "16px" } },
    min: -3,
    max: 3,
    tickInterval: 1,
    gridLineColor: t.grid,
    plotLines: [{ value: 0, color: t.inkSoft, width: 1.5, zIndex: 4 }],
    labels: { style: { color: t.inkSoft, fontSize: "14px" } },
  },
  legend: {
    itemStyle: { color: t.inkSoft, fontSize: "14px" },
    itemHoverStyle: { color: t.ink },
  },
  tooltip: { enabled: false },
  plotOptions: {
    series: { animation: false },
    line: { enableMouseTracking: false, marker: { enabled: false } },
    scatter: {
      // Small — the arrowhead tip drawn by drawArrowhead() already marks the
      // point; this just keeps the point visible under the triangle.
      marker: { radius: 3, lineWidth: 0 },
      dataLabels: {
        enabled: true,
        useHTML: false,
        style: { color: t.ink, fontSize: "13px", fontWeight: "400", textOutline: "none" },
        backgroundColor: t.elevatedBg,
        borderRadius: 4,
        padding: 4,
      },
    },
  },
  series: [
    {
      type: "line",
      name: "Unit circle",
      data: circlePoints,
      color: t.inkSoft,
      dashStyle: "Dash",
      lineWidth: 1.5,
      showInLegend: false,
      zIndex: 1,
    },
    {
      type: "line",
      name: "Roots-of-unity vectors",
      data: rootVectors,
      color: t.palette[0],
      lineWidth: 2,
      showInLegend: false,
      zIndex: 2,
    },
    {
      type: "line",
      name: "Arbitrary-point vectors",
      data: arbitraryVectors,
      color: t.palette[1],
      lineWidth: 2,
      dashStyle: "ShortDash",
      showInLegend: false,
      zIndex: 2,
    },
    {
      type: "scatter",
      name: "5th roots of unity",
      data: rootData,
      color: t.palette[0],
      zIndex: 3,
    },
    {
      type: "scatter",
      name: "Arbitrary points",
      data: arbitraryData,
      color: t.palette[1],
      zIndex: 3,
    },
  ],
});

Part of Complex Plane Visualization (Argand Diagram) on anyplot.ai.

Other implementations