D3
With D3 you own the scales, axes and layout, so @sportsdataverse/sdvplot/d3 gives you only the parts that need the
league data: images placed through your scales, a colour scale of team colours, and a team-painted surface. Each
helper appends to a selection you made and returns what it appended, so it fits into any existing D3 chart.
Logos, wordmarks and headshots through your scales
appendLogos(selection, xs, ys, teams, { league, x, y, frameHeight }) places one logo per value through your x and
y functions; height is a fraction of frameHeight. Start from d3.select(element).append("svg"): the helpers
take a selection whose datum is unknown, which d3.create does not give.
import { loadLeague } from "@sportsdataverse/sdvplot";
import { appendLogos } from "@sportsdataverse/sdvplot/d3";
import * as d3 from "d3";
await loadLeague("nfl");
const x = d3.scaleLinear([250, 550], [0, 480]);
const y = d3.scaleLinear([280, 420], [240, 0]);
const svg = d3.select(document.createElement("div")).append("svg").attr("viewBox", [0, 0, 480, 240]);
appendLogos(svg, [385, 525, 425], [326, 368, 311], ["KC", "BUF", "DEN"], {
league: "nfl",
x: (v) => x(Number(v)),
y: (v) => y(Number(v)),
frameHeight: 240,
height: 0.2,
});
svg.node();
import { STANDINGS } from "@sportsdataverse/examples/data";
import { loadLeague } from "@sportsdataverse/sdvplot";
import { appendLogos } from "@sportsdataverse/sdvplot/d3";
import * as d3 from "d3";
await loadLeague("nfl");
const [width, height] = [640, 400];
const x = d3.scaleLinear([280, 540], [40, width - 20]);
const y = d3.scaleLinear([280, 450], [height - 30, 20]);
// appendLogos takes a selection whose datum is `unknown`: select a fresh element rather than d3.create.
const svg = d3.select(document.createElement("div")).append("svg").attr("viewBox", [0, 0, width, height]);
svg
.append("g")
.attr("transform", `translate(0,${height - 30})`)
.call(d3.axisBottom(x));
svg.append("g").attr("transform", "translate(40,0)").call(d3.axisLeft(y));
appendLogos(
svg,
STANDINGS.map((s) => s.pf),
STANDINGS.map((s) => s.pa),
STANDINGS.map((s) => s.team),
{ league: "nfl", x: (v) => x(Number(v)), y: (v) => y(Number(v)), frameHeight: height, height: 0.1 },
);
svg.node();
appendWordmarks and appendHeadshots take the same arguments; headshots read ESPN player ids.
import { STANDINGS } from "@sportsdataverse/examples/data";
import { loadLeague } from "@sportsdataverse/sdvplot";
import { appendWordmarks } from "@sportsdataverse/sdvplot/d3";
import * as d3 from "d3";
await loadLeague("nfl");
const [width, height] = [640, 400];
const x = d3.scaleLinear([280, 540], [40, width - 20]);
const y = d3.scaleLinear([280, 450], [height - 30, 20]);
const svg = d3.select(document.createElement("div")).append("svg").attr("viewBox", [0, 0, width, height]);
svg
.append("g")
.attr("transform", `translate(0,${height - 30})`)
.call(d3.axisBottom(x));
svg.append("g").attr("transform", "translate(40,0)").call(d3.axisLeft(y));
appendWordmarks(
svg,
STANDINGS.map((s) => s.pf),
STANDINGS.map((s) => s.pa),
STANDINGS.map((s) => s.team),
{ league: "nfl", x: (v) => x(Number(v)), y: (v) => y(Number(v)), frameHeight: height, height: 0.06 },
);
svg.node();
import { STANDINGS } from "@sportsdataverse/examples/data";
import { appendHeadshots } from "@sportsdataverse/sdvplot/d3";
import * as d3 from "d3";
const [width, height] = [640, 400];
const x = d3.scaleLinear([280, 540], [40, width - 20]);
const y = d3.scaleLinear([280, 450], [height - 30, 20]);
const svg = d3.select(document.createElement("div")).append("svg").attr("viewBox", [0, 0, width, height]);
svg
.append("g")
.attr("transform", `translate(0,${height - 30})`)
.call(d3.axisBottom(x));
svg.append("g").attr("transform", "translate(40,0)").call(d3.axisLeft(y));
// ESPN player ids need no league data (gsis ids need `await loadGsis()` and idSystem: "gsis").
appendHeadshots(
svg,
STANDINGS.map((s) => s.pf),
STANDINGS.map((s) => s.pa),
STANDINGS.map((s) => s.qb_espn_id),
{ league: "nfl", x: (v) => x(Number(v)), y: (v) => y(Number(v)), frameHeight: height, height: 0.14 },
);
svg.node();
A team colour scale
teamColorScale(league, { values }) is a d3.scaleOrdinal from team to colour; a value that is no team gets grey.
[ "#552583", "#008348", "grey" ]
import { loadLeague } from "@sportsdataverse/sdvplot";
import { teamColorScale } from "@sportsdataverse/sdvplot/d3";
await loadLeague("nba");
const color = teamColorScale("nba", { values: ["LAL", "BOS"] });
["LAL", "BOS", "not a team"].map(color);
import { STANDINGS } from "@sportsdataverse/examples/data";
import { loadLeague } from "@sportsdataverse/sdvplot";
import { teamColorScale } from "@sportsdataverse/sdvplot/d3";
import * as d3 from "d3";
await loadLeague("nfl");
const [width, height] = [640, 400];
const teams = STANDINGS.map((s) => s.team);
const color = teamColorScale("nfl", { values: teams }); // team -> primary colour; unknown -> "grey"
const x = d3.scaleBand(teams, [40, width - 20]).padding(0.2);
const y = d3.scaleLinear([0, 17], [height - 30, 20]);
const svg = d3.select(document.createElement("div")).append("svg").attr("viewBox", [0, 0, width, height]);
svg
.append("g")
.selectAll("rect")
.data(STANDINGS)
.join("rect")
.attr("x", (s) => x(s.team) ?? 0)
.attr("y", (s) => y(s.wins))
.attr("width", x.bandwidth())
.attr("height", (s) => y(0) - y(s.wins))
.attr("fill", (s) => color(s.team));
svg
.append("g")
.attr("transform", `translate(0,${height - 30})`)
.call(d3.axisBottom(x));
svg.append("g").attr("transform", "translate(40,0)").call(d3.axisLeft(y));
svg.node();
A court in team colours
appendSurface(selection, league, { team, x, y }) draws the league's surface in the team's colours through your
scales, so your data and the court share one coordinate system (feet from centre court here).
import { loadLeague } from "@sportsdataverse/sdvplot";
import { appendSurface } from "@sportsdataverse/sdvplot/d3";
import * as d3 from "d3";
await loadLeague("nba");
// An NBA court with its apron spans x -55..55 ft and y -30..30 ft; 6 px per foot.
const [width, height] = [660, 360];
const x = d3.scaleLinear([-55, 55], [0, width]);
const y = d3.scaleLinear([-30, 30], [height, 0]);
const svg = d3.select(document.createElement("div")).append("svg").attr("viewBox", [0, 0, width, height]);
appendSurface(svg, "nba", { team: "LAL", x, y });
svg.node();