Simple Moving Average (SMA) Indicator Chart — D3.js

A Simple Moving Average (SMA) chart displays price or value data with one or more SMA overlays, typically showing short, medium, and long-term periods (e.g., 20, 50, 200-day). Each SMA line smooths out price fluctuations by averaging the last N data points, revealing underlying trends at different time scales. This multi-period overlay is fundamental to technical analysis, helping traders identify trend direction, support/resistance levels, and potential crossover signals.

Simple Moving Average (SMA) Indicator Chart rendered with D3.js

Renders

JavaScript source (D3.js)

// anyplot.ai
// indicator-sma: Simple Moving Average (SMA) Indicator Chart
// Library: d3 7.9.0 | JavaScript 22.23.2
// Quality: 90/100 | Created: 2026-09-02

const t = window.ANYPLOT_TOKENS;
const { width, height } = window.ANYPLOT_SIZE;
const margin = { top: 130, right: 70, bottom: 70, left: 100 };
const iw = width - margin.left - margin.right;
const ih = height - margin.top - margin.bottom;

// --- Data: one year of daily closes (fixed-seed LCG random walk) + SMA overlays
function lcg(seed) {
  let state = seed;
  return () => {
    state = (state * 1664525 + 1013904223) % 4294967296;
    return state / 4294967296;
  };
}
const rand = lcg(42);
function gaussian() {
  const u1 = Math.max(rand(), 1e-9);
  const u2 = rand();
  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
}

const PERIODS = 252;
const dates = [];
const cursor = new Date(2024, 0, 2);
while (dates.length < PERIODS) {
  const weekday = cursor.getDay();
  if (weekday !== 0 && weekday !== 6) dates.push(new Date(cursor));
  cursor.setDate(cursor.getDate() + 1);
}

const closes = [];
let price = 148;
for (let i = 0; i < PERIODS; i++) {
  price *= 1 + 0.0006 + 0.014 * gaussian();
  closes.push(price);
}

function movingAverage(values, period) {
  return values.map((_, i) => {
    if (i < period - 1) return null;
    let sum = 0;
    for (let j = i - period + 1; j <= i; j++) sum += values[j];
    return sum / period;
  });
}

const series = [
  { key: "close", label: "Close", values: closes, color: t.palette[0], width: 3.5 },
  { key: "sma20", label: "SMA 20", values: movingAverage(closes, 20), color: t.palette[1], width: 2.5 },
  { key: "sma50", label: "SMA 50", values: movingAverage(closes, 50), color: t.palette[2], width: 2.5 },
  { key: "sma200", label: "SMA 200", values: movingAverage(closes, 200), color: t.palette[3], width: 2.5 },
];

// --- Crossover detection (SMA 20 x SMA 50, D3-computed golden/death cross) --
function findCrossover(a, b) {
  for (let i = 1; i < a.length; i++) {
    if (a[i - 1] === null || b[i - 1] === null || a[i] === null || b[i] === null) continue;
    const prevDiff = a[i - 1] - b[i - 1];
    const currDiff = a[i] - b[i];
    if (prevDiff === 0) continue;
    if ((prevDiff < 0 && currDiff >= 0) || (prevDiff > 0 && currDiff <= 0)) {
      const frac = prevDiff / (prevDiff - currDiff);
      return {
        date: new Date(dates[i - 1].getTime() + frac * (dates[i].getTime() - dates[i - 1].getTime())),
        value: a[i - 1] + frac * (a[i] - a[i - 1]),
        bullish: prevDiff < 0,
      };
    }
  }
  return null;
}
const crossover = findCrossover(series[1].values, series[2].values);

// --- Scales -------------------------------------------------------------
const x = d3.scaleTime().domain(d3.extent(dates)).range([0, iw]);
const allValues = series.flatMap((s) => s.values).filter((v) => v !== null);
const y = d3
  .scaleLinear()
  .domain([d3.min(allValues) * 0.97, d3.max(allValues) * 1.03])
  .nice()
  .range([ih, 0]);

// --- 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})`);

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

// --- Axes -------------------------------------------------------------------
const xAxis = g
  .append("g")
  .attr("transform", `translate(0,${ih})`)
  .call(d3.axisBottom(x).ticks(d3.timeMonth.every(1)).tickFormat(d3.timeFormat("%b %Y")));
const yAxis = g.append("g").call(d3.axisLeft(y).ticks(6).tickFormat(d3.format("$,.0f")));
for (const axis of [xAxis, yAxis]) {
  axis.selectAll("text").attr("fill", t.inkSoft).style("font-size", "14px");
  axis.selectAll("line").attr("stroke", t.inkSoft);
  axis.select(".domain").attr("stroke", t.inkSoft);
}
xAxis.selectAll("text").attr("transform", "rotate(-30)").style("text-anchor", "end");

g.append("text")
  .attr("x", -margin.left + 24)
  .attr("y", -30)
  .attr("fill", t.ink)
  .style("font-size", "16px")
  .text("Price (USD)");

// --- Lines ------------------------------------------------------------------
const line = d3
  .line()
  .defined((d) => d.value !== null)
  .x((d) => x(d.date))
  .y((d) => y(d.value))
  .curve(d3.curveMonotoneX);

for (const s of series) {
  const points = dates.map((date, i) => ({ date, value: s.values[i] }));
  g.append("path")
    .datum(points)
    .attr("fill", "none")
    .attr("stroke", s.color)
    .attr("stroke-width", s.width)
    .attr("stroke-linejoin", "round")
    .attr("stroke-linecap", "round")
    .attr("opacity", s.key === "close" ? 1 : 0.9)
    .attr("d", line);
}

// --- Crossover annotation (marks the golden/death cross with a dashed rule,
// a ringed marker, and a leader-line label) --------------------------------
if (crossover) {
  const cx = x(crossover.date);
  const cy = y(crossover.value);
  const label = crossover.bullish ? "Golden Cross" : "Death Cross";
  const labelBelow = cy < ih * 0.3;

  g.append("line")
    .attr("x1", cx)
    .attr("x2", cx)
    .attr("y1", 0)
    .attr("y2", ih)
    .attr("stroke", t.inkSoft)
    .attr("stroke-width", 1)
    .attr("stroke-dasharray", "4,4")
    .attr("opacity", 0.5);

  g.append("circle")
    .attr("cx", cx)
    .attr("cy", cy)
    .attr("r", 8)
    .attr("fill", "none")
    .attr("stroke", t.ink)
    .attr("stroke-width", 2);

  g.append("text")
    .attr("x", cx)
    .attr("y", labelBelow ? cy + 28 : cy - 18)
    .attr("text-anchor", "middle")
    .attr("fill", t.ink)
    .style("font-size", "15px")
    .style("font-weight", "600")
    .text(label);
}

// --- 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("indicator-sma · javascript · d3 · anyplot.ai");

// --- Legend (horizontal, below title) ---------------------------------------
const legend = svg.append("g").attr("transform", `translate(0, 90)`);
const legendWidth = series.reduce((acc, s) => acc + s.label.length * 11 + 60, 0);
let cursorX = width / 2 - legendWidth / 2;
for (const s of series) {
  const item = legend.append("g").attr("transform", `translate(${cursorX},0)`);
  item
    .append("line")
    .attr("x1", 0)
    .attr("x2", 28)
    .attr("y1", 0)
    .attr("y2", 0)
    .attr("stroke", s.color)
    .attr("stroke-width", s.width)
    .attr("stroke-linecap", "round");
  item
    .append("text")
    .attr("x", 38)
    .attr("y", 5)
    .attr("fill", t.inkSoft)
    .style("font-size", "15px")
    .text(s.label);
  cursorX += s.label.length * 11 + 60;
}

Retrieve this implementation

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

Part of Simple Moving Average (SMA) Indicator Chart on anyplot.ai.

Other implementations