Line Plot with Markers — D3.js

A line plot with visible markers at each data point, combining line and scatter plot features. This is particularly useful for sparse data where individual observations are significant.

Line Plot with Markers rendered with D3.js

Renders

JavaScript source (D3.js)

// anyplot.ai
// line-markers: Line Plot with Markers
// Library: d3 7.9.0 | JavaScript 22.23.2
// Quality: 88/100 | Created: 2026-09-05

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

// --- Data: enzyme activity across a pH gradient, three variants -----------
// Sparse discrete measurements (13 pH readings per enzyme) where each point
// is a meaningful lab observation, not a dense sampled curve.
function lcg(seed) {
  let state = seed;
  return () => {
    state = (state * 1664525 + 1013904223) % 4294967296;
    return state / 4294967296;
  };
}
const rand = lcg(42);

const phLevels = [4.0, 4.5, 5.0, 5.5, 6.0, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5, 10.0];

function bellActivity(ph, optimum, sigma, peak) {
  const gaussian = peak * Math.exp(-((ph - optimum) ** 2) / (2 * sigma ** 2));
  const noise = (rand() - 0.5) * 4;
  return Math.max(0, gaussian + noise);
}

const enzymes = [
  {
    name: "Pepsin",
    optimum: 5.0,
    sigma: 1.3,
    peak: 92,
    color: t.palette[0],
    symbol: d3.symbolCircle,
  },
  {
    name: "Trypsin",
    optimum: 7.5,
    sigma: 1.1,
    peak: 88,
    color: t.palette[1],
    symbol: d3.symbolSquare,
  },
  {
    name: "Alkaline phosphatase",
    optimum: 9.5,
    sigma: 1.4,
    peak: 96,
    color: t.palette[2],
    symbol: d3.symbolTriangle,
  },
];

const series = enzymes.map((e) => ({
  ...e,
  values: phLevels.map((ph) => ({ ph, activity: bellActivity(ph, e.optimum, e.sigma, e.peak) })),
}));

// --- 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 -------------------------------------------------------------------
const x = d3.scaleLinear().domain([4.0, 10.0]).range([0, iw]);
const y = d3
  .scaleLinear()
  .domain([0, d3.max(series, (s) => d3.max(s.values, (v) => v.activity)) * 1.1])
  .nice()
  .range([ih, 0]);

// --- Gridlines (y-axis only, subtle) -------------------------------------------
g.append("g")
  .attr("class", "grid")
  .call(d3.axisLeft(y).tickSize(-iw).tickFormat(""))
  .call((sel) => sel.select(".domain").remove())
  .selectAll("line")
  .attr("stroke", t.grid)
  .attr("stroke-opacity", 0.15);

// --- Axes -----------------------------------------------------------------
const xAxis = g
  .append("g")
  .attr("transform", `translate(0,${ih})`)
  .call(d3.axisBottom(x).tickValues([4, 5, 6, 7, 8, 9, 10]).tickFormat((d) => d.toFixed(0)));
const yAxis = g.append("g").call(d3.axisLeft(y));
for (const ax of [xAxis, yAxis]) {
  ax.selectAll("text").attr("fill", t.inkSoft).style("font-size", "16px");
  ax.selectAll("line").attr("stroke", t.inkSoft);
  ax.select(".domain").attr("stroke", t.inkSoft);
}

// --- Lines + markers per series -----------------------------------------------
const line = d3
  .line()
  .x((d) => x(d.ph))
  .y((d) => y(d.activity));

for (const s of series) {
  const peak = s.values.reduce((a, b) => (b.activity > a.activity ? b : a));

  // Soft halo behind the optimum point gives each curve a clear focal point.
  g.append("circle")
    .attr("cx", x(peak.ph))
    .attr("cy", y(peak.activity))
    .attr("r", 26)
    .attr("fill", s.color)
    .attr("opacity", 0.16);

  g.append("path")
    .datum(s.values)
    .attr("fill", "none")
    .attr("stroke", s.color)
    .attr("stroke-width", 3)
    .attr("d", line);

  g.selectAll(`.marker-${s.name.replace(/\s/g, "")}`)
    .data(s.values)
    .join("path")
    .attr("d", (d) => d3.symbol().type(s.symbol).size(d === peak ? 460 : 260)())
    .attr("transform", (d) => `translate(${x(d.ph)},${y(d.activity)})`)
    .attr("fill", s.color)
    .attr("stroke", t.pageBg)
    .attr("stroke-width", 2);
}

// --- Axis labels ------------------------------------------------------------
g.append("text")
  .attr("x", iw / 2)
  .attr("y", ih + 64)
  .attr("text-anchor", "middle")
  .attr("fill", t.ink)
  .style("font-size", "18px")
  .text("pH Level");

g.append("text")
  .attr("transform", "rotate(-90)")
  .attr("x", -ih / 2)
  .attr("y", -84)
  .attr("text-anchor", "middle")
  .attr("fill", t.ink)
  .style("font-size", "18px")
  .text("Enzyme Activity (%)");

// --- Legend (symbol + color + line swatch per series) -------------------------
const legend = svg
  .append("g")
  .attr("transform", `translate(${margin.left + iw + 40},${margin.top + 20})`);

series.forEach((s, i) => {
  const row = legend.append("g").attr("transform", `translate(0,${i * 56})`);
  row
    .append("line")
    .attr("x1", 0)
    .attr("x2", 60)
    .attr("y1", 0)
    .attr("y2", 0)
    .attr("stroke", s.color)
    .attr("stroke-width", 3);
  row
    .append("path")
    .attr("d", d3.symbol().type(s.symbol).size(260)())
    .attr("transform", "translate(30,0)")
    .attr("fill", s.color)
    .attr("stroke", t.pageBg)
    .attr("stroke-width", 2);
  row
    .append("text")
    .attr("x", 76)
    .attr("y", 0)
    .attr("dominant-baseline", "middle")
    .attr("fill", t.inkSoft)
    .style("font-size", "16px")
    .text(s.name);
});

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

Retrieve this implementation

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

Part of Line Plot with Markers on anyplot.ai.

Other implementations