Changelog
A record of how the Devils Performance Index formula has changed over time. Each entry explains what was added, removed, or modified — and why.
February 2026 — Shot Chart
Added an interactive shot chart visualization between the scoreboard and the player tables. It shows every shot attempt from the most recent game — goals, shots on goal, missed shots, and blocked shots — for both the Devils and their opponent, plotted on an SVG rink diagram.
What it shows
- Shot dots — Each dot is colored by type: red for goals, blue for shots on goal, grey for misses, and amber for blocked shots. Dot size scales with expected goals (xG), so high-danger chances from the slot are visually larger than point shots.
- Zone shading — A subtle density overlay highlights where each team's shots clustered. Red shading for the Devils, grey for the opponent.
- Split rink — Devils shots appear on the right half, opponent shots on the left, mirrored so both teams shoot toward the outside edges.
- Tooltips — Hover any dot to see the shooter name, shot type, xG value, and period.
Summary stats
Below the chart, a summary row shows shots on goal, total shot attempts (Corsi), and team xG for each side.
Technical details
- Shot coordinates come from the NHL play-by-play API, which was already being fetched for xG and Corsi calculations. The raw x/y data is now preserved and normalized (goal always at x=89) before being written to latest-game.json.
- Adds roughly 5–7 KB to the data file. The game cache is unaffected.
- The rink is a responsive SVG that scales to any screen width.
January 2026 — DPI v2
This was a big overhaul. The original formula relied mostly on box-score stats — goals, assists, shots on goal, hits, blocks, plus/minus. It worked, but it was blunt. A player who fired 10 wide shots looked the same as someone with 2 dangerous chances.
The NHL's play-by-play feed opened up a lot more. I rebuilt the formula around that data.
New data sources
The biggest change is that DPI now pulls from play-by-play event data, not just the box score. That means individual Corsi (every shot attempt, not just shots on goal), expected goals based on shot location, penalties drawn, and the ability to filter everything for 5-on-5 play. Before this, there was no way to separate even-strength performance from power-play noise.
What was dropped
- Shots on goal — Replaced by individual Corsi (iCF), which counts all shot attempts including blocks and misses. iCF is a better measure of shot volume because it captures intent, not just what got through.
- Goals vs. league average — Replaced by goals vs. expected goals (xG). Comparing a player's goals to a league-wide average was too coarse. Comparing to xG tells you whether they're finishing above or below what their shot quality suggests.
- Ice time — Was a standalone category. Now baked into per/60 normalization for hits, blocks, and takeaways, so ice time still matters but doesn't get double-counted.
- Shifts — Removed entirely. It didn't add meaningful signal beyond what ice time already captured.
What was added
- Individual Corsi (iCF) — +0.08 per attempt (x1.2 for defensemen). Measures total shot generation, rewarding players who create chances even if shots get blocked.
- Expected goals (xG) — +1.0 per xG (sum of shot-location probabilities). Credits players for getting shots from dangerous areas.
- Goals vs. xG — +2.0 per goal above expected. A finishing bonus for players who beat their expected output.
- Penalties drawn — +0.7 per penalty drawn. Drawing penalties creates power plays, and the old formula didn't account for that at all.
- 5-on-5 bonus — A composite of even-strength iCF, xG, takeaways, and giveaways. This rewards players whose underlying numbers hold up at 5v5, not just on the power play.
What was modified
- Hits, blocks, takeaways — Now normalized to per-60-minute rates instead of raw counts. A fourth-liner with 8 minutes of ice time shouldn't get the same credit for 3 hits as a top-liner with 3 hits in 20 minutes.
- Penalty weights — Reduced from -1.0 per PIM to -0.5. The old penalty was too harsh — a single roughing minor was wiping out the value of a full game's physical play.
Technical improvement
Added a persistent game cache so the fetch script doesn't re-download games it already has. This speeds up daily updates and reduces API calls.
Bar chart changes
The per-player bar charts were updated to reflect the new formula categories:
- Added: Shot Quality (iCF + xG), Discipline (penalties drawn + faceoffs), Finishing (goals vs. xG, shown as negative when below expected), Turnovers (giveaways)
- Removed: Ice Time, Goals vs. Avg, Shots