Public
Edited
May 30, 2024
Insert cell
Insert cell
viewof bin_width = Inputs.range([5, 50], {label: "Bin width", step: 5, value: 20})
Insert cell
chart = Plot.plot({
height: 640,
//axis: null,
x: {domain: [-250, 250], axis: null},
y: {domain: [-50, 450], axis: null},
color: {type: "log", scheme: "ylgnbu", legend: true, label: "Made shots"},
marks: [
Plot.rect(celtics_playoff_shots, Plot.bin({fill: "count"}, {x: "LOC_X", y: "LOC_Y", inset: 0, interval: bin_width})),
Plot.gridX({interval: bin_width, strokeOpacity: 0.05}),
Plot.gridY({interval: bin_width, strokeOpacity: 0.05}),
markings()
]
})
Insert cell
## Porzingis
Insert cell
chart2 = Plot.plot({
height: 640,
//axis: null,
x: {domain: [-250, 250], axis: null},
y: {domain: [-50, 450], axis: null},
color: {type: "log", scheme: "ylgnbu", legend: true, label: "Made shots"},
marks: [
Plot.rect(celtics_playoff_shots, Plot.bin({fill: "count"}, {x: "LOC_X", y: "LOC_Y", filter: (d) => d.PLAYER_NAME === "Kristaps Porzingis" && +d.SHOT_MADE_FLAG, inset: 0, interval: 25})),
Plot.gridX({interval: 25, strokeOpacity: 0.05}),
Plot.gridY({interval: 25, strokeOpacity: 0.05}),
markings()
]
})
Insert cell
Plot.plot({
y: {grid: true},
marks: [
Plot.rectY(celtics_playoff_shots, Plot.binX({y: "count"}, {x: "SHOT_DISTANCE", fill: celtics_green, filter: d => +d.SHOT_MADE_FLAG})),
Plot.ruleY([0])
]
})
Insert cell
Insert cell
function getOpponent(abv, htm, vtm){
if(abv == htm) {
return vtm }
else return htm
}
Insert cell
Insert cell
function markings({
stroke = "currentColor",
strokeWidth = 1,
strokeOpacity = 1
} = {}) {
// Ref. https://observablehq.com/@nor/nba-2018-19-shooting-effeciency
const angle = Math.atan(90 / 220);
const arc = d3.arc();
const lines = [
[-250, 420, 250, 420], // half
[-250, 450, -250, -50], // left
[250, 450, 250, -50], // right
[250, -50, -250, -50], // bottom
[-220, -50, -220, 90], // corner 3
[220, -50, 220, 90], // corner 3
[-80, -50, -80, 140], // paint
[80, -50, 80, 140], // paint
[-60, -50, -60, 140], // paint
[60, -50, 60, 140], // paint
[-80, 140, 80, 140], // free throw line
[-30, -10, 30, -10], // backboard
[0, -10, 0, -7.5], // rim
[-40, -10, -40, 0], // ra
[40, -10, 40, 0] // ra
];
const circles = [
[0, 0, 7.5], // rim
[0, 140, 60], // key
[0, 420, 20], // center court inner
[0, 420, 60] // center court outer
];
const arcs = [
[0, 0, 40, -Math.PI * 0.5, Math.PI * 0.5], // ra
[0, 0, 237.5, -Math.PI * 0.5 - angle, Math.PI * 0.5 + angle] // 3pt
];
return (index, {x, y}) => {
return htl.svg`<g fill=none stroke=${stroke} stroke-width=${strokeWidth} stroke-opacity=${strokeOpacity}>
${lines.map(([x1, y1, x2, y2]) => htl.svg`<line x1=${x(x1)} x2=${x(x2)} y1=${y(y1)} y2=${y(y2)}>`)}
${circles.map(([cx, cy, r]) => htl.svg`<ellipse cx=${x(cx)} cy=${y(cy)} rx=${Math.abs(x(r) - x(0))} ry=${Math.abs(y(r) - y(0))}>`)}
${arcs.map(([cx, cy, r, a1, a2]) => htl.svg`<path d="M${x(cx + r * Math.cos(a1 - Math.PI / 2))},${y(cy + r * Math.sin(a1 - Math.PI / 2))}A${Math.abs(x(r) - x(0))} ${Math.abs(y(r) - y(0))} 0 0 ${Math.sign(x(r) - x(0)) * Math.sign(y(r) - y(0)) > 0 ? 0 : 1} ${x(cx + r * Math.cos(a2 - Math.PI / 2))},${y(cy + r * Math.sin(a2 - Math.PI / 2))}">`)}
</g>`;
};
}
Insert cell
Insert cell
Insert cell
celtics_playoff_shots@1.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
import {celtics_green} from "@akraieski/celtics-2023-eastern-conference-finals-game-7-shots"
Insert cell
shots_df = aq.from(shots)
.derive({
team_abv: aq.escape(d => teamNameToAbbreviation(d.team_name))
})
.derive({
opponent: aq.escape( d => getOpponent(d.team_abv, d.htm, d.vtm))
})
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more