Basic Sparkline — D3.js

A sparkline is a small, condensed line chart designed to be embedded inline with text or in dashboard cells. It shows trends at a glance without axes, labels, or detailed scales - pure data visualization in minimal space. The defining characteristic is extreme minimalism: a single continuous line that conveys the shape of data without any chart chrome.

Basic Sparkline rendered with D3.js

Renders

JavaScript source (D3.js)

// anyplot.ai
// sparkline-basic: Basic Sparkline
// Library: d3 7.9.0 | JavaScript 22.23.2
// Quality: 93/100 | Created: 2026-09-09

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

// --- Data (in-memory, deterministic) ----------------------------------------
// A tiny fixed-seed LCG — the browser has no seeded RNG.
const lcg = (seed) => {
  let s = seed;
  return () => {
    s = (s * 1103515245 + 12345) & 0x7fffffff;
    return s / 0x7fffffff;
  };
};

const buildSeries = (start, drift, volatility, seed, n = 40) => {
  const rng = lcg(seed);
  const values = [start];
  for (let i = 1; i < n; i += 1) {
    const step = drift + (rng() - 0.5) * volatility * 2;
    values.push(Math.max(1, values[i - 1] + step));
  }
  return values;
};

const stocks = [
  {
    name: "Nova Robotics",
    ticker: "NVRO",
    values: buildSeries(82, 0.55, 2.4, 11),
  },
  {
    name: "Bluewave Foods",
    ticker: "BLUW",
    values: buildSeries(54, -0.35, 1.6, 23),
  },
  {
    name: "Crestline Energy",
    ticker: "CRST",
    values: buildSeries(130, 0.9, 3.1, 37),
  },
  {
    name: "Halcyon Health",
    ticker: "HLCN",
    values: buildSeries(38, -0.5, 1.1, 53),
  },
];

// --- SVG mount ----------------------------------------------------------------
const svg = d3
  .select("#container")
  .append("svg")
  .attr("width", width)
  .attr("height", height);

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

// --- Layout: one table-style row per stock, sparkline embedded inline -------
const marginTop = 90;
const rowHeight = 190;
const labelX = 60;
const sparkX0 = 420;
const sparkX1 = width - 60;
const sparkHeight = 140;

// Pre-compute per-stock change so the standout mover can be highlighted.
const changes = stocks.map((stock) => {
  const values = stock.values;
  const first = values[0];
  const last = values[values.length - 1];
  return ((last - first) / first) * 100;
});
const standoutIndex = changes.reduce(
  (best, c, i) => (Math.abs(c) > Math.abs(changes[best]) ? i : best),
  0,
);

// Idiomatic D3 data-join: one <g class="row"> per stock, data-bound.
const rows = svg.selectAll("g.row").data(stocks).join("g").attr("class", "row");

rows.each(function (stock, i) {
  const row = d3.select(this);
  const y0 = marginTop + i * rowHeight;
  const values = stock.values;
  const n = values.length;
  const first = values[0];
  const last = values[n - 1];
  const change = changes[i];
  const up = change >= 0;
  const color = up ? t.palette[0] : t.palette[4];
  const arrow = up ? "▲" : "▼";
  const isStandout = i === standoutIndex;

  // Subtle full-row tint calling out the largest absolute mover.
  if (isStandout) {
    row
      .append("rect")
      .attr("x", 0)
      .attr("y", y0)
      .attr("width", sparkX1)
      .attr("height", rowHeight)
      .attr("fill", color)
      .attr("opacity", 0.05);
  }

  // Divider between rows (table feel — not an axis of the sparkline itself)
  if (i > 0) {
    row
      .append("line")
      .attr("x1", labelX)
      .attr("x2", sparkX1)
      .attr("y1", y0)
      .attr("y2", y0)
      .attr("stroke", t.grid)
      .attr("stroke-width", 1);
  }

  // Label block — the standout mover gets a bolder company name.
  row
    .append("text")
    .attr("x", labelX)
    .attr("y", y0 + 58)
    .attr("fill", t.ink)
    .style("font-size", "20px")
    .style("font-weight", isStandout ? "700" : "600")
    .text(stock.name);

  row
    .append("text")
    .attr("x", labelX)
    .attr("y", y0 + 82)
    .attr("fill", t.inkSoft)
    .style("font-size", "14px")
    .style("letter-spacing", "0.05em")
    .text(stock.ticker);

  const priceLabel = row
    .append("text")
    .attr("x", labelX)
    .attr("y", y0 + 120)
    .attr("fill", t.ink)
    .style("font-size", "22px")
    .style("font-weight", "700");
  priceLabel.append("tspan").text(`$${last.toFixed(2)}`);
  priceLabel
    .append("tspan")
    .attr("dx", "12")
    .attr("fill", color)
    .style("font-size", "16px")
    .style("font-weight", "600")
    .text(`${arrow} ${Math.abs(change).toFixed(1)}%`);

  // Sparkline — pure line, no axes/ticks/gridlines
  const sparkY0 = y0 + (rowHeight - sparkHeight) / 2;
  const sparkY1 = sparkY0 + sparkHeight;
  const x = d3
    .scaleLinear()
    .domain([0, n - 1])
    .range([sparkX0, sparkX1]);
  const [vMin, vMax] = d3.extent(values);
  const pad = (vMax - vMin) * 0.1 || 1;
  const y = d3
    .scaleLinear()
    .domain([vMin - pad, vMax + pad])
    .range([sparkY1, sparkY0]);

  const area = d3
    .area()
    .x((d, i2) => x(i2))
    .y0(sparkY1)
    .y1((d) => y(d))
    .curve(d3.curveMonotoneX);
  const line = d3
    .line()
    .x((d, i2) => x(i2))
    .y((d) => y(d))
    .curve(d3.curveMonotoneX);

  row
    .append("path")
    .datum(values)
    .attr("d", area)
    .attr("fill", color)
    .attr("opacity", 0.12);
  row
    .append("path")
    .datum(values)
    .attr("d", line)
    .attr("fill", "none")
    .attr("stroke", color)
    .attr("stroke-width", 2.5)
    .attr("stroke-linejoin", "round")
    .attr("stroke-linecap", "round");

  // First point — subtle hollow reference marker
  row
    .append("circle")
    .attr("cx", x(0))
    .attr("cy", y(first))
    .attr("r", 3.5)
    .attr("fill", t.pageBg)
    .attr("stroke", color)
    .attr("stroke-width", 1.5);

  // Last point — filled marker, the current value
  row
    .append("circle")
    .attr("cx", x(n - 1))
    .attr("cy", y(last))
    .attr("r", 5)
    .attr("fill", color)
    .attr("stroke", t.pageBg)
    .attr("stroke-width", 1.5);

  // Min/max markers — small faded dots, skipped where they'd sit on top of
  // the first/last markers already drawn above.
  const maxIdx = values.indexOf(vMax);
  const minIdx = values.indexOf(vMin);
  for (const idx of new Set([minIdx, maxIdx])) {
    if (idx === 0 || idx === n - 1) continue;
    row
      .append("circle")
      .attr("cx", x(idx))
      .attr("cy", y(values[idx]))
      .attr("r", 2.5)
      .attr("fill", color)
      .attr("opacity", 0.55);
  }
});

Retrieve this implementation

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

Part of Basic Sparkline on anyplot.ai.

Other implementations