Win Probability Chart — Highcharts

A win probability chart shows how each team's likelihood of winning evolves over the course of a game. The line starts near 50% and fluctuates based on scoring events, ultimately reaching 100% or 0% at game end. The area above and below the 50% baseline is filled with team colors to convey momentum at a glance. This visualization is widely used across major sports for post-game analysis and live broadcasting.

Win Probability Chart rendered with Highcharts

Renders

JavaScript source (Highcharts)

// anyplot.ai
// line-win-probability: Win Probability Chart
// Library: highcharts 12.6.0 | JavaScript 22.22.3
// Quality: 87/100 | Created: 2026-06-21

//# anyplot-orientation: landscape

const t = window.ANYPLOT_TOKENS;
const THEME = window.ANYPLOT_THEME;

// Deterministic LCG for reproducible play-by-play data
function lcg(seed) {
  let s = seed >>> 0;
  return () => {
    s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
    return s / 0x100000000;
  };
}

const rand = lcg(42);

// Key scoring plays: [playNumber, wpDelta, shortLabel]
const keyPlays = [
  [18,  0.22, "Home TD"],
  [36, -0.20, "Away TD"],
  [55, -0.14, "Away FG"],
  [72,  0.21, "Home TD"],
  [89, -0.27, "Pick-six"],
  [113, 0.38, "Game-winning TD"],
];

// Build play-by-play win probability (120 plays, NFL AFC Wild Card)
const TOTAL_PLAYS = 120;
let wp = 0.50;
const seriesData = Array.from({ length: TOTAL_PLAYS + 1 }, (_, i) => ({ x: i, y: 0 }));

for (let i = 0; i <= TOTAL_PLAYS; i++) {
  seriesData[i].y = parseFloat((wp * 100).toFixed(1));
  if (i < TOTAL_PLAYS) {
    const play = keyPlays.find(p => p[0] === i);
    const eventDelta = play ? play[1] : 0;
    const noise = (rand() - 0.5) * 0.036;
    const meanRevert = (0.50 - wp) * 0.018;
    wp = Math.min(0.99, Math.max(0.01, wp + eventDelta + noise + meanRevert));
  }
}
// Home team wins: drive to 99%
seriesData[TOTAL_PLAYS].y = 99.2;

// Annotate key plays: marker at play+1 so wp has already updated
for (const [playNum, , label] of keyPlays) {
  const markerIdx = Math.min(playNum + 1, TOTAL_PLAYS);
  const point = seriesData[markerIdx];
  point.marker = {
    enabled: true,
    radius: 5,
    fillColor: t.ink,
    lineWidth: 2,
    lineColor: t.pageBg,
  };
  point.dataLabels = {
    enabled: true,
    useHTML: false,
    format: label,
    style: {
      color: t.inkSoft,
      fontSize: "12px",
      fontWeight: "normal",
      textOutline: "none",
    },
    y: point.y > 50 ? -16 : 18,
    align: "center",
  };
}

// Quarter boundary dotted lines (no label — x-axis formatter already shows Q2/Q3/Q4)
const quarterLines = [30, 60, 90].map((q) => ({
  value: q,
  color: t.grid,
  dashStyle: "dot",
  width: 1,
  zIndex: 2,
}));

Highcharts.chart("container", {
  chart: {
    type: "area",
    backgroundColor: "transparent",
    animation: false,
    style: { fontFamily: "inherit" },
    marginTop: 90,
    marginBottom: 72,
    marginLeft: 88,
    marginRight: 32,
  },
  credits: { enabled: false },
  colors: t.palette,
  title: {
    text: "line-win-probability · javascript · highcharts · anyplot.ai",
    style: { color: t.ink, fontSize: "22px", fontWeight: "600" },
  },
  subtitle: {
    text: "Home 21 – Away 17  |  NFL AFC Wild Card",
    style: { color: t.inkSoft, fontSize: "14px" },
  },
  xAxis: {
    min: 0,
    max: TOTAL_PLAYS,
    tickPositions: [0, 30, 60, 90, 120],
    title: {
      text: "Play Number",
      style: { color: t.inkSoft, fontSize: "16px" },
    },
    lineColor: t.inkSoft,
    tickColor: t.inkSoft,
    labels: {
      style: { color: t.inkSoft, fontSize: "13px" },
      formatter() {
        const map = { 0: "Start", 30: "Q2", 60: "Q3", 90: "Q4", 120: "Final" };
        return map[this.value] !== undefined ? map[this.value] : String(this.value);
      },
    },
    plotLines: quarterLines,
  },
  yAxis: {
    min: 0,
    max: 100,
    tickInterval: 25,
    title: {
      text: "Home Win Probability",
      style: { color: t.inkSoft, fontSize: "16px" },
    },
    gridLineColor: t.grid,
    labels: {
      format: "{value}%",
      style: { color: t.inkSoft, fontSize: "13px" },
    },
    plotLines: [{
      value: 50,
      color: t.inkSoft,
      width: 1.5,
      zIndex: 5,
      label: {
        text: "50%",
        align: "right",
        x: -8,
        style: { color: t.inkSoft, fontSize: "12px" },
      },
    }],
  },
  legend: { enabled: false },
  tooltip: { enabled: false },
  plotOptions: {
    series: { animation: false },
    area: {
      threshold: 50,
      color: t.palette[0],          // Brand green — home team leading
      negativeColor: t.palette[4],  // Matte red — home team trailing
      fillOpacity: THEME === "dark" ? 0.32 : 0.38,
      lineWidth: 2.5,
      dataLabels: { enabled: false },
      marker: {
        enabled: false,
        states: { hover: { enabled: false } },
      },
    },
  },
  series: [{
    name: "Home Win Probability",
    data: seriesData,
  }],
});

Part of Win Probability Chart on anyplot.ai.

Other implementations