Lag Plot for Time Series Autocorrelation Diagnosis — Chart.js

A lag plot is a scatter plot of a time series against a lagged version of itself, plotting y(t) on the x-axis versus y(t+k) on the y-axis for a given lag order k. If the data is purely random, points scatter uniformly with no visible structure; if autocorrelation is present, distinctive patterns emerge — linear clusters for autoregressive processes, elliptical shapes for seasonal data. This provides a quick visual diagnostic for time series dependence, complementing numerical tools like ACF/PACF.

Lag Plot for Time Series Autocorrelation Diagnosis rendered with Chart.js

Renders

JavaScript source (Chart.js)

// anyplot.ai
// scatter-lag: Lag Plot for Time Series Autocorrelation Diagnosis
// Library: chartjs 4.4.7 | JavaScript 22.23.0
// Quality: 88/100 | Created: 2026-06-24

const t = window.ANYPLOT_TOKENS;

// Deterministic LCG pseudo-random (seed 42)
let _s = 42;
function rand() {
  _s = (Math.imul(1664525, _s) + 1013904223) >>> 0;
  return _s / 0x100000000;
}

// AR(1) time series: y(t) = 0.85 * y(t-1) + noise
const N = 300;
const PHI = 0.85;
const LAG = 1; // configurable: try 7 or 12 for seasonal lag
const series = new Array(N);
series[0] = 0.0;
for (let i = 1; i < N; i++) {
  series[i] = PHI * series[i - 1] + (rand() - 0.5) * 2.0;
}

// Lag scatter pairs: x = y(t), y = y(t + LAG)
const lagData = [];
for (let i = 0; i < N - LAG; i++) {
  lagData.push({ x: series[i], y: series[i + LAG] });
}

// Interpolate color along imprint_seq by time index (early: green → late: blue)
function hexRgb(h) {
  return [parseInt(h.slice(1, 3), 16), parseInt(h.slice(3, 5), 16), parseInt(h.slice(5, 7), 16)];
}
function lerpColor(c1, c2, frac) {
  const [r1, g1, b1] = hexRgb(c1);
  const [r2, g2, b2] = hexRgb(c2);
  return `rgba(${Math.round(r1 + (r2 - r1) * frac)},${Math.round(g1 + (g2 - g1) * frac)},${Math.round(b1 + (b2 - b1) * frac)},0.65)`;
}
const pointColors = lagData.map((_, i) => lerpColor(t.seq[0], t.seq[1], i / (lagData.length - 1)));

// Axis range: equal scale on both axes so y=x is truly at 45°
const allVals = series;
const vMin = Math.min(...allVals);
const vMax = Math.max(...allVals);
const pad = (vMax - vMin) * 0.06;
const axisMin = Math.floor((vMin - pad) * 10) / 10;
const axisMax = Math.ceil((vMax + pad) * 10) / 10;

// Diagonal reference line (y = x)
const diagData = [
  { x: axisMin, y: axisMin },
  { x: axisMax, y: axisMax },
];

// Pearson r at lag-1
const xs = lagData.map(d => d.x);
const ys = lagData.map(d => d.y);
const n = lagData.length;
const mX = xs.reduce((a, b) => a + b, 0) / n;
const mY = ys.reduce((a, b) => a + b, 0) / n;
let num = 0, ssX = 0, ssY = 0;
for (let i = 0; i < n; i++) {
  const dx = xs[i] - mX;
  const dy = ys[i] - mY;
  num += dx * dy;
  ssX += dx * dx;
  ssY += dy * dy;
}
const rVal = (num / Math.sqrt(ssX * ssY)).toFixed(3);

// Background fill plugin
const bgPlugin = {
  id: "bg",
  beforeDraw(chart) {
    chart.ctx.fillStyle = t.pageBg;
    chart.ctx.fillRect(0, 0, chart.width, chart.height);
  },
};

// Correlation coefficient annotation drawn in the upper-left of the plot area
const rAnnotation = {
  id: "rAnnotation",
  afterDatasetsDraw(chart) {
    const { ctx, chartArea: { left, top } } = chart;
    ctx.save();
    ctx.font = "600 15px sans-serif";
    ctx.fillStyle = t.ink;
    ctx.fillText(`r = ${rVal}`, left + 14, top + 22);
    ctx.restore();
  },
};

// Compact horizontal gradient colorbar drawn above the bottom legend
const colorbarPlugin = {
  id: "colorbar",
  afterDraw(chart) {
    const { ctx, chartArea, width } = chart;
    const barW = 120;
    const barH = 10;
    const barX = (width - barW) / 2;
    const barY = chartArea.bottom + 28;
    ctx.save();
    const grad = ctx.createLinearGradient(barX, 0, barX + barW, 0);
    grad.addColorStop(0, t.seq[0]);
    grad.addColorStop(1, t.seq[1]);
    ctx.fillStyle = grad;
    ctx.beginPath();
    ctx.roundRect(barX, barY, barW, barH, 3);
    ctx.fill();
    ctx.font = "12px sans-serif";
    ctx.fillStyle = t.inkSoft;
    ctx.textAlign = "right";
    ctx.fillText("early", barX - 5, barY + barH - 1);
    ctx.textAlign = "left";
    ctx.fillText("late", barX + barW + 5, barY + barH - 1);
    ctx.restore();
  },
};

// Mount canvas
const canvas = document.createElement("canvas");
document.getElementById("container").appendChild(canvas);

// Chart
new Chart(canvas, {
  type: "scatter",
  plugins: [bgPlugin, rAnnotation, colorbarPlugin],
  data: {
    datasets: [
      {
        label: `y(t) vs y(t+${LAG})`,
        data: lagData,
        backgroundColor: pointColors,
        borderColor: "transparent",
        pointRadius: 4,
        pointHoverRadius: 6,
      },
      {
        label: "y = x  (no autocorrelation)",
        type: "line",
        data: diagData,
        borderColor: t.inkSoft,
        borderWidth: 2,
        borderDash: [8, 5],
        pointRadius: 0,
        fill: false,
        tension: 0,
      },
    ],
  },
  options: {
    responsive: true,
    maintainAspectRatio: false,
    animation: false,
    plugins: {
      title: {
        display: true,
        text: "AR(1) Process · scatter-lag · javascript · chartjs · anyplot.ai",
        color: t.ink,
        font: { size: 20, weight: "600" },
        padding: { top: 8, bottom: 4 },
      },
      subtitle: {
        display: true,
        text: "Points colored by time index (early: green → late: blue)",
        color: t.inkSoft,
        font: { size: 13 },
        padding: { bottom: 12 },
      },
      legend: {
        position: "bottom",
        labels: {
          color: t.ink,
          font: { size: 14 },
          padding: 20,
          filter(item) {
            return item.datasetIndex === 1;
          },
        },
      },
      tooltip: {
        callbacks: {
          label(ctx) {
            return `y(t) = ${ctx.parsed.x.toFixed(3)},  y(t+${LAG}) = ${ctx.parsed.y.toFixed(3)}`;
          },
        },
      },
    },
    scales: {
      x: {
        min: axisMin,
        max: axisMax,
        title: {
          display: true,
          text: "y(t)",
          color: t.ink,
          font: { size: 16 },
          padding: { top: 6 },
        },
        border: { display: false },
        ticks: { color: t.inkSoft, font: { size: 13 } },
        grid: { color: t.grid },
      },
      y: {
        min: axisMin,
        max: axisMax,
        title: {
          display: true,
          text: `y(t + ${LAG})`,
          color: t.ink,
          font: { size: 16 },
          padding: { bottom: 6 },
        },
        border: { display: false },
        ticks: { color: t.inkSoft, font: { size: 13 } },
        grid: { color: t.grid },
      },
    },
  },
});

Part of Lag Plot for Time Series Autocorrelation Diagnosis on anyplot.ai.

Other implementations