Basic Span Plot (Highlighted Region) — D3.js

A span plot highlights a specific region of interest on a chart using a shaded rectangular area that spans the full height or width of the plot. Vertical spans mark ranges along the x-axis (e.g., time periods), while horizontal spans mark ranges along the y-axis (e.g., value thresholds). The semi-transparent fill allows underlying data to remain visible while drawing attention to the highlighted region.

Basic Span Plot (Highlighted Region) rendered with D3.js

Renders

JavaScript source (D3.js)

// anyplot.ai
// span-basic: Basic Span Plot (Highlighted Region)
// Library: d3 7.9.0 | JavaScript 22.23.1
// Quality: 91/100 | Created: 2026-07-25

const t = window.ANYPLOT_TOKENS;
const { width, height } = window.ANYPLOT_SIZE;
const margin = { top: 110, right: 60, bottom: 90, left: 110 };
const iw = width - margin.left - margin.right;
const ih = height - margin.top - margin.bottom;

// --- Data (in-memory, deterministic) ----------------------------------------
// Quarterly stock index level, 2006–2011, with two recession periods marked.
function lcg(seed) {
  let s = seed;
  return () => {
    s = (s * 1103515245 + 12345) & 0x7fffffff;
    return s / 0x7fffffff;
  };
}
const rand = lcg(42);

const quarters = [];
for (let year = 2006; year <= 2011; year++) {
  for (let q = 0; q < 4; q++) quarters.push(new Date(year, q * 3, 1));
}

let level = 100;
const series = quarters.map((date, i) => {
  const inRecession =
    (date >= new Date(2007, 9, 1) && date < new Date(2009, 3, 1)) ||
    (date >= new Date(2011, 6, 1) && date < new Date(2011, 12, 1));
  const drift = inRecession ? -3.2 : 1.8;
  level += drift + (rand() - 0.5) * 4;
  level = Math.max(level, 55);
  return { date, index: level };
});

const verticalSpans = [
  { start: new Date(2007, 9, 1), end: new Date(2009, 3, 1), label: "2007–09 recession" },
  { start: new Date(2011, 6, 1), end: new Date(2011, 12, 1), label: "2011 slowdown" },
];

// A horizontal span demonstrates the spec's other direction: a fixed value-threshold
// "correction zone" band, independent of x, spanning the full plot width.
const horizontalSpans = [{ start: 60, end: 97, label: "Correction zone (index < 97)" }];

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

// --- Scales --------------------------------------------------------------------
// Domain covers both the series dates and every vertical span's start/end so a span
// that extends past the last data point never extrapolates past the plotted x-axis.
const x = d3
  .scaleTime()
  .domain(d3.extent([...series.map((d) => d.date), ...verticalSpans.flatMap((s) => [s.start, s.end])]))
  .range([0, iw]);
const y = d3
  .scaleLinear()
  .domain([d3.min(series, (d) => d.index) * 0.95, d3.max(series, (d) => d.index) * 1.05])
  .nice()
  .range([ih, 0]);

// --- Gridlines (y-axis only) ---------------------------------------------------
g.append("g")
  .selectAll("line")
  .data(y.ticks(6))
  .join("line")
  .attr("x1", 0)
  .attr("x2", iw)
  .attr("y1", (d) => y(d))
  .attr("y2", (d) => y(d))
  .attr("stroke", t.grid)
  .attr("stroke-width", 1);

// --- Horizontal span (drawn beneath everything) — value-threshold band ----------
const hSpanColor = t.amber; // amber — semantic anchor for warning/caution thresholds
g.selectAll(".hspan")
  .data(horizontalSpans)
  .join("rect")
  .attr("class", "hspan")
  .attr("x", 0)
  .attr("y", (d) => Math.max(y(d.end), 0))
  .attr("width", iw)
  .attr("height", (d) => Math.min(y(d.start), ih) - Math.max(y(d.end), 0))
  .attr("fill", hSpanColor)
  .attr("opacity", 0.22);

g.selectAll(".hspan-label")
  .data(horizontalSpans)
  .join("text")
  .attr("class", "hspan-label")
  .attr("x", 12)
  .attr("y", (d) => Math.max(y(d.end), 0) + 20)
  .attr("text-anchor", "start")
  .attr("fill", t.inkSoft)
  .style("font-size", "13px")
  .style("font-weight", "500")
  .text((d) => d.label);

// --- Vertical spans (drawn beneath the line) ------------------------------------
const spanColor = t.palette[4]; // matte red — semantic anchor for downturn periods
g.selectAll(".span")
  .data(verticalSpans)
  .join("rect")
  .attr("class", "span")
  .attr("x", (d) => x(d.start))
  .attr("y", 0)
  .attr("width", (d) => x(d.end) - x(d.start))
  .attr("height", ih)
  .attr("fill", spanColor)
  .attr("opacity", 0.22);

g.selectAll(".span-label")
  .data(verticalSpans)
  .join("text")
  .attr("class", "span-label")
  .attr("x", (d) => x(d.start) + (x(d.end) - x(d.start)) / 2)
  .attr("y", 22)
  .attr("text-anchor", "middle")
  .attr("fill", t.inkSoft)
  .style("font-size", "13px")
  .style("font-weight", "500")
  .text((d) => d.label);

// --- Line (drawn above the spans) -----------------------------------------------
const line = d3
  .line()
  .x((d) => x(d.date))
  .y((d) => y(d.index));

g.append("path")
  .datum(series)
  .attr("fill", "none")
  .attr("stroke", t.palette[0])
  .attr("stroke-width", 3)
  .attr("stroke-linejoin", "round")
  .attr("stroke-linecap", "round")
  .attr("d", line);

// --- Axes -------------------------------------------------------------------
const xAxis = g
  .append("g")
  .attr("transform", `translate(0,${ih})`)
  .call(d3.axisBottom(x).ticks(d3.timeYear.every(1)).tickFormat(d3.timeFormat("%Y")).tickSizeOuter(0));
const yAxis = g.append("g").call(d3.axisLeft(y).ticks(6).tickSizeOuter(0));

for (const ax of [xAxis, yAxis]) {
  ax.selectAll("text").attr("fill", t.inkSoft).style("font-size", "15px");
  ax.selectAll("line").attr("stroke", t.inkSoft);
  ax.select(".domain").attr("stroke", t.inkSoft);
}
xAxis.selectAll("line").remove();
yAxis.selectAll("line").remove();

// --- Axis titles --------------------------------------------------------------
g.append("text")
  .attr("x", iw / 2)
  .attr("y", ih + 56)
  .attr("text-anchor", "middle")
  .attr("fill", t.ink)
  .style("font-size", "17px")
  .text("Year");

g.append("text")
  .attr("transform", "rotate(-90)")
  .attr("x", -ih / 2)
  .attr("y", -80)
  .attr("text-anchor", "middle")
  .attr("fill", t.ink)
  .style("font-size", "17px")
  .text("Market Index");

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

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/span-basic/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": "span-basic",
  "language": "javascript",
  "library": "d3",
  "page": "https://anyplot.ai/span-basic/javascript/d3",
  "hub": "https://anyplot.ai/span-basic",
  "code_json": "https://api.anyplot.ai/specs/span-basic/d3/code",
  "spec_json": "https://api.anyplot.ai/specs/span-basic",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/span-basic/javascript/d3/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/span-basic/javascript/d3/plot-dark.png",
  "interactive_light_html": "https://storage.googleapis.com/anyplot-images/plots/span-basic/javascript/d3/plot-light.html",
  "interactive_dark_html": "https://storage.googleapis.com/anyplot-images/plots/span-basic/javascript/d3/plot-dark.html",
  "quality_score": 91.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Basic Span Plot (Highlighted Region) on anyplot.ai.

Other implementations