Skip to main content

Function: shotCells()

shotCells(cells, o): Markish

Defined in: packages/sdvplot/src/plot/shots.ts:166

Data-space hexagons or squares (the dual encoding: r from sizeCells, fill from diffScale) drawn as Plot.geo through the plot's x/y scales, so they sit on a sporty court at any orientation. Fewest attempts first, so busy cells stay on top (blazing-the-nets main lib/charts/hexShotChart.ts:79); r = 0 is not drawn, nor (by default, dropOutside) a cell centred outside the plot's frame. Each path carries data-sdv-id="x,y", the cell's legacy centre. Marks paint in array order: after ...court.marks the cells cover the court lines under them, as in main. tip: true shows attempts, FG%, league FG% and the shrunk difference (formats .1%, +.1%; a tip object's format overrides them key by key); every other Plot.geo option passes through, and the cells are the mark's data, so fx/fy, filter, sort (replacing the default order) and title read the caller's fields. A transform or initializer that makes new rows (Plot.group, Plot.hexbin) throws InputError.

Parameters​

cells​

readonly CellVsLeague[]

o​

ShotCellsOptions

Returns​

Markish

Example​

import * as Plot from "@observablehq/plot";
import { shotCells, surface } from "@sportsdataverse/sdvplot/plot";
import { cellsVsLeague, leagueIndex } from "@sportsdataverse/sdvplot/shots";

// Brooklyn shots from the sportsdataverse-data nba_stats_shots release, 2025-26
const shots = [
{ x_legacy: -1, y_legacy: 7, shot_distance: 1, shot_value: 2, shot_result: "Made" },
{ x_legacy: 0, y_legacy: 0, shot_distance: 0, shot_value: 2, shot_result: "Missed" },
{ x_legacy: -44, y_legacy: 252, shot_distance: 26, shot_value: 3, shot_result: "Made" },
];
// the three shots stand in for the league; the gallery's hex chart uses the 2025-26 league's index
const cells = cellsVsLeague(shots, leagueIndex(shots, 15));
const court = surface("nba", { displayRange: "defense" });
Plot.plot({ ...court.scales, marks: [...court.marks, shotCells(cells, { r: 15 })] });