Polar Line Plot — D3.js

A line plot rendered in polar coordinates, where data points are connected with lines around a circular axis. The angle (theta) typically represents a cyclical variable while the radius shows magnitude.

Polar Line Plot rendered with D3.js

Renders

JavaScript source (D3.js)

// anyplot.ai
// polar-line: Polar Line Plot
// Library: d3 7.9.0 | JavaScript 22.23.2
// Quality: 90/100 | Created: 2026-09-05

//# anyplot-orientation: square

const t = window.ANYPLOT_TOKENS;
const { width, height } = window.ANYPLOT_SIZE;

// --- Data (in-memory, deterministic) ----------------------------------------
// Average wind speed (km/h) by compass direction at a coastal weather station,
// summer vs. winter — prevailing westerlies, stronger in winter.
const directions = [
  "N", "NNE", "NE", "ENE", "E", "ESE", "SE", "SSE",
  "S", "SSW", "SW", "WSW", "W", "WNW", "NW", "NNW",
];
const summerSpeed = [12, 10, 9, 8, 7, 6, 7, 9, 12, 15, 18, 22, 25, 23, 19, 15];
const winterSpeed = [18, 16, 14, 12, 10, 9, 10, 13, 17, 22, 27, 32, 36, 33, 28, 22];

// Winter intentionally uses palette[2] (blue) rather than the canonical
// palette[1] (lavender) — a deliberate warm/cold semantic pairing with the
// brand-green Summer series, not an arbitrary skip.
const series = [
  { name: "Summer", values: summerSpeed, color: t.palette[0] },
  { name: "Winter", values: winterSpeed, color: t.palette[2] },
];

// --- Layout -------------------------------------------------------------------
const centerX = width / 2;
const centerY = 620;
const outerRadius = 420;
const labelOffset = 34;
const maxSpeed = Math.max(...summerSpeed, ...winterSpeed);

const angle = d3.scaleLinear().domain([0, directions.length]).range([0, 2 * Math.PI]);
const radius = d3.scaleLinear().domain([0, maxSpeed]).nice().range([0, outerRadius]);
const radiusTicks = radius.ticks(4).filter((v) => v > 0);

// --- SVG mount ------------------------------------------------------------
const svg = d3.select("#container").append("svg").attr("width", width).attr("height", height);
const g = svg.append("g").attr("transform", `translate(${centerX},${centerY})`);

// --- Grid: concentric circles + radial spokes ------------------------------
const gridGroup = g.append("g");
gridGroup
  .selectAll("circle")
  .data(radiusTicks)
  .join("circle")
  .attr("r", (d) => radius(d))
  .attr("fill", "none")
  .attr("stroke", t.grid)
  .attr("stroke-width", 1);

gridGroup
  .selectAll("line")
  .data(d3.range(directions.length))
  .join("line")
  .attr("x1", 0)
  .attr("y1", 0)
  .attr("x2", (i) => outerRadius * Math.sin(angle(i)))
  .attr("y2", (i) => -outerRadius * Math.cos(angle(i)))
  .attr("stroke", t.grid)
  .attr("stroke-width", 1);

// --- Radial tick labels (placed along a spoke clear of the data lines) ------
const tickAngle = angle(0.5); // halfway between N and NNE, away from the peak lobe
g.selectAll(".radius-tick")
  .data(radiusTicks)
  .join("text")
  .attr("class", "radius-tick")
  .attr("x", (d) => radius(d) * Math.sin(tickAngle) + 6)
  .attr("y", (d) => -radius(d) * Math.cos(tickAngle))
  .attr("fill", t.inkSoft)
  .style("font-size", "15px")
  .text((d) => `${d} km/h`);

// --- Direction labels ---------------------------------------------------------
g.selectAll(".direction-label")
  .data(d3.range(directions.length))
  .join("text")
  .attr("class", "direction-label")
  .attr("x", (i) => (outerRadius + labelOffset) * Math.sin(angle(i)))
  .attr("y", (i) => -(outerRadius + labelOffset) * Math.cos(angle(i)))
  .attr("text-anchor", (i) => {
    const s = Math.sin(angle(i));
    if (s > 0.15) return "start";
    if (s < -0.15) return "end";
    return "middle";
  })
  .attr("dominant-baseline", "middle")
  .attr("fill", t.inkSoft)
  .style("font-size", "15px")
  .text((i) => directions[i]);

// --- Radial fill between the two series — spotlights the seasonal gap -------
const seasonGap = d3
  .areaRadial()
  .angle((d, i) => angle(i))
  .innerRadius((d, i) => radius(summerSpeed[i]))
  .outerRadius((d, i) => radius(winterSpeed[i]))
  .curve(d3.curveCardinalClosed);

g.append("path")
  .datum(d3.range(directions.length))
  .attr("d", seasonGap)
  .attr("fill", series[1].color)
  .attr("fill-opacity", 0.12)
  .attr("stroke", "none");

// --- Lines (closed loop — cyclical data) + markers ---------------------------
const lineRadial = d3
  .lineRadial()
  .angle((d, i) => angle(i))
  .radius((d) => radius(d))
  .curve(d3.curveCardinalClosed);

for (const s of series) {
  g.append("path")
    .datum(s.values)
    .attr("d", lineRadial)
    .attr("fill", "none")
    .attr("stroke", s.color)
    .attr("stroke-width", 3)
    .attr("stroke-linejoin", "round");

  g.selectAll(`.marker-${s.name}`)
    .data(s.values)
    .join("circle")
    .attr("class", `marker-${s.name}`)
    .attr("cx", (d, i) => radius(d) * Math.sin(angle(i)))
    .attr("cy", (d, i) => -radius(d) * Math.cos(angle(i)))
    .attr("r", 5)
    .attr("fill", s.color)
    .attr("stroke", t.pageBg)
    .attr("stroke-width", 1.5);
}

// --- Legend -------------------------------------------------------------------
const legend = svg.append("g").attr("transform", `translate(${centerX - 110},${height - 46})`);
series.forEach((s, i) => {
  const item = legend.append("g").attr("transform", `translate(${i * 130},0)`);
  item
    .append("line")
    .attr("x1", 0)
    .attr("x2", 24)
    .attr("y1", 0)
    .attr("y2", 0)
    .attr("stroke", s.color)
    .attr("stroke-width", 3);
  item
    .append("circle")
    .attr("cx", 12)
    .attr("cy", 0)
    .attr("r", 5)
    .attr("fill", s.color)
    .attr("stroke", t.pageBg)
    .attr("stroke-width", 1.5);
  item
    .append("text")
    .attr("x", 32)
    .attr("y", 0)
    .attr("dominant-baseline", "middle")
    .attr("fill", t.inkSoft)
    .style("font-size", "15px")
    .text(s.name);
});

// --- Title ----------------------------------------------------------------
svg
  .append("text")
  .attr("x", width / 2)
  .attr("y", 55)
  .attr("text-anchor", "middle")
  .attr("fill", t.ink)
  .style("font-size", "30px")
  .style("font-weight", "600")
  .text("polar-line · javascript · d3 · anyplot.ai");

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/polar-line/d3/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.

{
  "spec_id": "polar-line",
  "language": "javascript",
  "library": "d3",
  "page": "https://anyplot.ai/polar-line/javascript/d3",
  "hub": "https://anyplot.ai/polar-line",
  "code_json": "https://api.anyplot.ai/specs/polar-line/d3/code",
  "spec_json": "https://api.anyplot.ai/specs/polar-line",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/polar-line/javascript/d3/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/polar-line/javascript/d3/plot-dark.png",
  "interactive_light_html": "https://storage.googleapis.com/anyplot-images/plots/polar-line/javascript/d3/plot-light.html",
  "interactive_dark_html": "https://storage.googleapis.com/anyplot-images/plots/polar-line/javascript/d3/plot-dark.html",
  "quality_score": 90.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Polar Line Plot on anyplot.ai.

Other implementations