Residual Plot in Highcharts (JavaScript)

The same plot in 14 other libraries — JavaScript: Chart.js, D3.js, Apache ECharts, MUI X Charts; Python: Altair, Bokeh, lets-plot, Matplotlib, Plotly, plotnine, Pygal, Seaborn; R: ggplot2; Julia: Makie.jl. Compare all 15 side by side: Residual Plot in Python, R, Julia and JavaScript.

A residual plot displays the difference between observed and predicted values (residuals) against fitted values in regression analysis. This diagnostic visualization helps identify violations of regression assumptions including non-linearity, heteroscedasticity (non-constant variance), and outliers. A well-fitting model shows residuals randomly scattered around zero with no discernible pattern.

Residual Plot rendered with Highcharts

Renders

JavaScript source (Highcharts)

// anyplot.ai
// residual-plot: Residual Plot
// Library: highcharts 12.6.0 | JavaScript 22.23.2
// Quality: 88/100 | Created: 2026-09-05

//# anyplot-orientation: landscape
const t = window.ANYPLOT_TOKENS;

// --- Data (in-memory, deterministic LCG) ------------------------------------
// Simulated house-price regression: fitted values (predicted price, $k) vs.
// residuals (observed - predicted), with mild heteroscedasticity so the
// diagnostic value of the plot is visible (funnel-shaped spread).
function lcg(seed) {
  let state = seed;
  return () => {
    state = (state * 1664525 + 1013904223) % 4294967296;
    return state / 4294967296;
  };
}
function gaussian(rand) {
  const u1 = Math.max(rand(), 1e-9);
  const u2 = rand();
  return Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
}

const rand = lcg(42);
const n = 220;
const fitted = [];
const residuals = [];
for (let i = 0; i < n; i++) {
  const price = 150 + rand() * 500; // fitted house price, $k
  const noiseScale = 8 + price * 0.08; // variance grows with fitted value
  const residual = gaussian(rand) * noiseScale;
  fitted.push(Number(price.toFixed(1)));
  residuals.push(Number(residual.toFixed(1)));
}

const mean = residuals.reduce((a, b) => a + b, 0) / n;
const variance = residuals.reduce((a, b) => a + (b - mean) ** 2, 0) / n;
const sd = Math.sqrt(variance);
const threshold = 2 * sd;

const inlierPoints = [];
const outlierPoints = [];
for (let i = 0; i < n; i++) {
  const point = { x: fitted[i], y: residuals[i] };
  if (Math.abs(residuals[i]) > threshold) {
    outlierPoints.push(point);
  } else {
    inlierPoints.push(point);
  }
}

const xMin = Math.min(...fitted);
const xMax = Math.max(...fitted);

// Binned-mean smoothing line: makes the heteroscedastic (funnel-shaped) trend
// explicit without pulling in a full LOWESS implementation.
const sortedPoints = fitted.map((x, i) => ({ x, y: residuals[i] })).sort((a, b) => a.x - b.x);
const binCount = 10;
const binWidth = (xMax - xMin) / binCount;
const trendPoints = [];
for (let b = 0; b < binCount; b++) {
  const lo = xMin + b * binWidth;
  const hi = lo + binWidth;
  const inBin = sortedPoints.filter((p) => p.x >= lo && (b === binCount - 1 ? p.x <= hi : p.x < hi));
  if (inBin.length === 0) continue;
  const meanX = inBin.reduce((a, p) => a + p.x, 0) / inBin.length;
  const meanY = inBin.reduce((a, p) => a + p.y, 0) / inBin.length;
  trendPoints.push([Number(meanX.toFixed(1)), Number(meanY.toFixed(2))]);
}

// --- Chart -------------------------------------------------------------------
Highcharts.chart("container", {
  chart: {
    type: "scatter",
    backgroundColor: "transparent",
    animation: false,
    style: { fontFamily: "inherit" },
  },
  credits: { enabled: false },
  colors: t.palette,
  title: {
    text: "residual-plot · javascript · highcharts · anyplot.ai",
    style: { color: t.ink, fontSize: "22px", fontWeight: "600" },
  },
  xAxis: {
    title: { text: "Fitted Value ($k)", style: { color: t.inkSoft, fontSize: "16px" } },
    lineColor: t.inkSoft,
    tickColor: t.inkSoft,
    gridLineColor: t.grid,
    gridLineWidth: 1,
    min: xMin - 20,
    max: xMax + 20,
    labels: { style: { color: t.inkSoft, fontSize: "14px" } },
  },
  yAxis: {
    title: { text: "Residual ($k)", style: { color: t.inkSoft, fontSize: "16px" } },
    lineColor: t.inkSoft,
    tickColor: t.inkSoft,
    gridLineColor: t.grid,
    gridLineWidth: 1,
    labels: { style: { color: t.inkSoft, fontSize: "14px" } },
    plotLines: [
      {
        value: 0,
        color: t.ink,
        width: 2,
        zIndex: 3,
      },
    ],
    plotBands: [
      {
        from: -threshold,
        to: threshold,
        color: Highcharts.color(t.inkSoft).setOpacity(0.08).get("rgba"),
        zIndex: 0,
      },
    ],
  },
  legend: {
    itemStyle: { color: t.inkSoft, fontSize: "14px" },
    itemHoverStyle: { color: t.ink },
  },
  tooltip: {
    formatter: function () {
      return `<b>Fitted:</b> $${this.x}k<br/><b>Residual:</b> $${this.y}k`;
    },
  },
  plotOptions: {
    series: { animation: false },
    scatter: {
      marker: { radius: 4, symbol: "circle" },
    },
  },
  series: [
    {
      name: "Residuals (within ±2 SD)",
      data: inlierPoints,
      color: t.palette[0],
      marker: { fillColor: Highcharts.color(t.palette[0]).setOpacity(0.5).get("rgba"), lineWidth: 0 },
    },
    {
      name: "Outliers (beyond ±2 SD)",
      data: outlierPoints,
      color: t.palette[4],
      marker: { radius: 6, lineColor: t.ink, lineWidth: 1 },
      dataLabels: {
        enabled: true,
        formatter: function () {
          return (this.y < 0 ? "-$" : "$") + Math.abs(this.y).toFixed(1) + "k";
        },
        y: -12,
        style: { color: t.ink, fontSize: "11px", fontWeight: "600", textOutline: "none" },
      },
    },
    {
      name: "Trend (binned mean)",
      type: "spline",
      data: trendPoints,
      color: t.palette[1],
      lineWidth: 2.5,
      dashStyle: "ShortDash",
      marker: { enabled: false },
      enableMouseTracking: false,
      zIndex: 2,
    },
  ],
});

Retrieve this implementation

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

Part of Residual Plot on anyplot.ai.

Other implementations