Public
Edited
Nov 13, 2024
1 fork
3 stars
Insert cell
Insert cell
{
// Select a match to plot
const data = tracking_data.filter(d => d.play == "Fulham 0 - [1] Liverpool");
// Create the pitch
const pitch = d3_soccer.pitch();
// Create an svg con
const chart = d3.create('div')
.style('font-family', 'Helvetica')
const svg = chart.append('svg')
.attr('width', pitch.width())
.attr('height', pitch.height() + 110);
// Create the header
const header = d3_soccer.matchHeader()
.hed("Premier League")
.score([0, 1])
.logoAway("https://upload.wikimedia.org/wikipedia/fr/thumb/5/54/Logo_FC_Liverpool.svg/langfr-130px-Logo_FC_Liverpool.svg.png")
.logoHome("https://upload.wikimedia.org/wikipedia/en/thumb/e/eb/Fulham_FC_%28shield%29.svg/150px-Fulham_FC_%28shield%29.svg.png");
svg.append('g').attr("transform", "translate(15, 20)").call(header);
svg.selectAll("image").attr("height", 35);
// Draw the pitch
svg.append('g').attr("transform", "translate(0, 90)").call(pitch);
svg.append('text')
.attr('x', pitch.width() - 10)
.attr('y', pitch.height() + 100)
.attr('text-anchor', 'end')
.style("fill", "grey")
.style("font-style", "italic")
.style("font-size", "11px")
.text("Data provided by LastRowView");

// Plotting
let plot_layer = chart.select(".above")
let voronoi_layer = chart.select(".below")

let x = d3.scaleLinear()
.domain([0, 100])
.range([0, 105]);
let y = d3.scaleLinear()
.domain([0, 100])
.range([0, 68]);

function updateData(frame) {
// Get all data for the current frame
let frameData = data.filter(d => d.frame == frame);
// Plot the locations of all players and the ball
let u = plot_layer.selectAll('circle')
.data(frameData, d => parseInt(d.player) || 0);
u.enter().append('circle')
.attr('cx', d => x(parseFloat(d.x)))
.attr('cy', d => y(parseFloat(d.y)))
.attr('r', 1)
.attr('fill', d => d.bgcolor || 'black')
.attr('stroke', d => d.edgecolor)
.attr('stroke-width', .2)
.attr('fill-opacity', 0.5);
u.attr('cx', d => x(parseFloat(d.x)))
.attr('cy', d => y(parseFloat(d.y)))
u.exit().remove();
// Add the Voronoi on top
let voronoiData = frameData.filter(d => parseInt(d.player)); // Remove the ball
let delaunay = d3.Delaunay.from(
voronoiData,
d => x(parseFloat(d.x)),
d => y(parseFloat(d.y))
), voronoi = delaunay.voronoi([ 0, 0, 105, 68 ])
let w = voronoi_layer.selectAll('path')
.data( voronoiData.map((d,i) => voronoi.renderCell(i)) )
w.enter()
.append('path')
.attr('d', d => d)
.style('fill', (_,i) => voronoiData[i].bgcolor)
.style('opacity', 0.1)
.style('stroke', 'black')
.attr('stroke-width', .2);
w.attr('d', d => d);
w.exit().remove();

}
let frames = d3.max(data, d => parseInt(d.frame));

// Data provided has 20 frames per second --> update every 50 ms.
var i = 0
setInterval(function(){
i = i === frames ? 0 : i + 1;
updateData(i)
}, 50);
return chart.node()
}
Insert cell
tracking_data = d3.csv('https://raw.githubusercontent.com/Friends-of-Tracking-Data-FoTD/Last-Row/master/datasets/positional_data/liverpool_2019.csv')
Insert cell
Insert cell
d3 = require("d3@7")
Insert cell
d3_soccer = require("d3-soccer@0.2.0")
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