Skip to content

WNBA Shot Chart

Every field goal attempt in the 2023 WNBA regular season. Shots are grouped into hexagonal bins, with color indicating shot potency (average score) and size indicating the total count of shots per location. The menu filters isolate shots by team or athlete.

Loading Example...

Credit: Data from Wehoop. Design inspired by Kirk Goldsberry and a UW CSE 512 project by Mackenzie Pitts and Madeline Brown.

Specification

js
import * as vg from "@uwdata/vgplot";

await vg.coordinator().exec([
  vg.loadParquet("shots", "data/wnba-shots-2023.parquet", {where: "NOT starts_with(type, 'Free Throw') AND season_type = 2"}),
  vg.loadParquet("court", "data/wnba-half-court.parquet")
]);

const $filter = vg.Selection.crossfilter();
const $binWidth = vg.Param.value(18);

export default vg.vconcat(
  vg.hconcat(
    vg.menu({from: "shots", column: "team_name", as: $filter, label: "Team"}),
    vg.menu({
      from: "shots",
      column: "athlete_name",
      filterBy: $filter,
      as: $filter,
      label: "Athlete"
    })
  ),
  vg.vspace(5),
  vg.plot(
    vg.frame({strokeOpacity: 0.5}),
    vg.hexgrid({binWidth: $binWidth, strokeOpacity: 0.05}),
    vg.hexbin(
      vg.from("shots", {filterBy: $filter}),
      {
        binWidth: $binWidth,
        x: "x_position",
        y: "y_position",
        fill: vg.avg("score_value"),
        r: vg.count(),
        tip: {format: {x: false, y: false}}
      }
    ),
    vg.line(
      vg.from("court"),
      {strokeLinecap: "butt", strokeOpacity: 0.5, x: "x", y: "y", z: "z"}
    ),
    vg.name("shot-chart"),
    vg.xAxis(null),
    vg.yAxis(null),
    vg.margin(5),
    vg.xDomain([0, 50]),
    vg.yDomain([0, 40]),
    vg.colorDomain(vg.Fixed),
    vg.colorScheme("YlOrRd"),
    vg.colorScale("linear"),
    vg.colorLabel("Avg. Shot Value"),
    vg.rScale("log"),
    vg.rRange([3, 9]),
    vg.rLabel("Shot Count"),
    vg.aspectRatio(1),
    vg.width(510)
  ),
  vg.colorLegend({for: "shot-chart"})
);
yaml
meta:
  title: WNBA Shot Chart
  description: >
    Every field goal attempt in the 2023 WNBA regular season.
    Shots are grouped into hexagonal bins, with color indicating shot potency
    (average score) and size indicating the total count of shots per location.
    The menu filters isolate shots by team or athlete.
  credit: >
    Data from [Wehoop](https://wehoop.sportsdataverse.org/). Design inspired by
    [Kirk Goldsberry](https://en.wikipedia.org/wiki/Kirk_Goldsberry) and a
    [UW CSE 512](https://courses.cs.washington.edu/courses/cse512/24sp/)
    project by Mackenzie Pitts and Madeline Brown.
data:
  shots:
    file: data/wnba-shots-2023.parquet
    where: NOT starts_with(type, 'Free Throw') AND season_type = 2
  court:
    file: data/wnba-half-court.parquet
params:
  filter: { select: crossfilter }
  binWidth: 18
vconcat:
- hconcat:
  - input: menu
    from: shots
    column: team_name
    as: $filter
    label: Team
  - input: menu
    from: shots
    column: athlete_name
    filterBy: $filter
    as: $filter
    label: Athlete
- vspace: 5
- plot:
  - mark: frame
    strokeOpacity: 0.5
  - mark: hexgrid
    binWidth: $binWidth
    strokeOpacity: 0.05
  - mark: hexbin
    data: { from: shots, filterBy: $filter }
    binWidth: $binWidth
    x: x_position
    y: y_position
    fill: { avg: score_value }
    r: { count: }
    tip: { format: { x: false, y: false } }
  - mark: line
    data: { from: court }
    strokeLinecap: butt
    strokeOpacity: 0.5
    x: x
    y: y
    z: z
  name: shot-chart
  xAxis: null
  yAxis: null
  margin: 5
  xDomain: [0, 50]
  yDomain: [0, 40]
  colorDomain: Fixed
  colorScheme: YlOrRd
  colorScale: linear
  colorLabel: Avg. Shot Value
  rScale: log
  rRange: [3, 9]
  rLabel: Shot Count
  aspectRatio: 1
  width: 510
- legend: color
  for: shot-chart
json
{
  "meta": {
    "title": "WNBA Shot Chart",
    "description": "Every field goal attempt in the 2023 WNBA regular season. Shots are grouped into hexagonal bins, with color indicating shot potency (average score) and size indicating the total count of shots per location. The menu filters isolate shots by team or athlete.\n",
    "credit": "Data from [Wehoop](https://wehoop.sportsdataverse.org/). Design inspired by [Kirk Goldsberry](https://en.wikipedia.org/wiki/Kirk_Goldsberry) and a [UW CSE 512](https://courses.cs.washington.edu/courses/cse512/24sp/) project by Mackenzie Pitts and Madeline Brown.\n"
  },
  "data": {
    "shots": {
      "file": "data/wnba-shots-2023.parquet",
      "where": "NOT starts_with(type, 'Free Throw') AND season_type = 2"
    },
    "court": {
      "file": "data/wnba-half-court.parquet"
    }
  },
  "params": {
    "filter": {
      "select": "crossfilter"
    },
    "binWidth": 18
  },
  "vconcat": [
    {
      "hconcat": [
        {
          "input": "menu",
          "from": "shots",
          "column": "team_name",
          "as": "$filter",
          "label": "Team"
        },
        {
          "input": "menu",
          "from": "shots",
          "column": "athlete_name",
          "filterBy": "$filter",
          "as": "$filter",
          "label": "Athlete"
        }
      ]
    },
    {
      "vspace": 5
    },
    {
      "plot": [
        {
          "mark": "frame",
          "strokeOpacity": 0.5
        },
        {
          "mark": "hexgrid",
          "binWidth": "$binWidth",
          "strokeOpacity": 0.05
        },
        {
          "mark": "hexbin",
          "data": {
            "from": "shots",
            "filterBy": "$filter"
          },
          "binWidth": "$binWidth",
          "x": "x_position",
          "y": "y_position",
          "fill": {
            "avg": "score_value"
          },
          "r": {
            "count": null
          },
          "tip": {
            "format": {
              "x": false,
              "y": false
            }
          }
        },
        {
          "mark": "line",
          "data": {
            "from": "court"
          },
          "strokeLinecap": "butt",
          "strokeOpacity": 0.5,
          "x": "x",
          "y": "y",
          "z": "z"
        }
      ],
      "name": "shot-chart",
      "xAxis": null,
      "yAxis": null,
      "margin": 5,
      "xDomain": [
        0,
        50
      ],
      "yDomain": [
        0,
        40
      ],
      "colorDomain": "Fixed",
      "colorScheme": "YlOrRd",
      "colorScale": "linear",
      "colorLabel": "Avg. Shot Value",
      "rScale": "log",
      "rRange": [
        3,
        9
      ],
      "rLabel": "Shot Count",
      "aspectRatio": 1,
      "width": 510
    },
    {
      "legend": "color",
      "for": "shot-chart"
    }
  ]
}