Public
Edited
Jun 7, 2023
Paused
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
// replay;

const svg = d3.select(DOM.svg(width, height));

const bounds = svg
.append("g")
.attr(
"transform",
`translate(${dimensions.margin.left}, ${dimensions.margin.top})`
);

// const data = playerCum.filter(d => playerCareer.includes(d.player));

const yScale = d3
.scaleLinear()
.domain([
0,
d3.max(playerCum, d => d3.max(d.data, dd => dd.cumAcutalPoints))
])
.range([dimensions.boundedHeight, 0])
.nice();

const xScale = d3
.scaleLinear()
.domain([0, d3.max(playerCum, d => d3.max(d.data, dd => dd.cumGP))])
.range([0, dimensions.boundedWidth])
.nice();

const xAxisGenerator = d3.axisBottom().scale(xScale);
const yAxisGenerator = d3.axisLeft().scale(yScale);

let lineGenerator = d3
.line()
.x(d => xScale(d.cumGP))
.y(d => yScale(d.cumPoints));

// Draw data
const lines = bounds.append("g");

lines
.selectAll(".line")
.data(playerCum)
.enter()
.append("path")
.attr("d", d => lineGenerator(d.data))
.style("fill", "none")
.attr("class", "lines")
.style("stroke", d => color1(d.player))
.attr("stroke-opacity", d => {
return highlights.includes(d.player) ? 1 : .1;
})
.style("stroke-width", 2)
.style("stroke-linejoin", "round");
// .on("mouseenter", mouseEnter)
// .on("mouseout", mouseOut);

// console.log(bounds);
// // Draw peripherals
const xAxis = bounds
.append("g")
.attr("class", "x-axis")
.call(xAxisGenerator)
.style("transform", `translateY(${dimensions.boundedHeight}px)`)
.style('opacity', .5)
.call(g => g.select(".domain").remove())

.call(g =>
g
.append("text")
.attr("x", 0)
.attr("y", 30)
.attr("fill", "black")
.attr("text-anchor", "start")
.text("Games Played")
);

const yAxis = bounds
.append("g")
.call(yAxisGenerator)
.call(g => g.select(".domain").remove())
.style('opacity', .5)
.call(g =>
g
.select(".tick:last-of-type text")
.clone()
// .append("text")
.attr("x", 5)
// .attr("y", dimensions.margin.top + 30)
.attr("fill", "black")
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text("Adj. Points")
);

// bounds.append("g").call(grid);

// set initial domain for xAxis
// xScale.domain([0, yDomGames]);

// add label and circle
const playerLabel = bounds
.selectAll(".label")
.data(playerCum)
.enter()
.append("g")
.attr("transform", d => {
return `translate(${xScale(last(d.data).cumGP)}, ${yScale(
last(d.data).cumPoints
)})`;
})
.attr("class", "names")
.attr("opacity", d => {
return highlights.includes(d.player) ? 1 : .1;
});

// .on("mouseenter", mouseEnter)
// .on("mouseout", mouseOut);

const dots = playerLabel
.append("circle")
.attr("r", 4)
.style("stroke", "white")
.style("fill", d => d.colour);

const playerName = playerLabel
.append("text")
.attr("x", 10)
.attr("y", 5)
.style("font-size", ".8em")
.attr("fill", d => d.colour)
.text(d => d.player);

const playerGoals = playerLabel
.append("text")
.attr("x", 10)
.attr("y", 18)
.style("font-size", ".8em")
.attr("fill", d => d.colour)
.text(d => last(d.data).cumgoals);

// function mouseEnter(datum) {
// // console.log(datum);
// // let out = [];
// // out.push(datum);
// // console.log(out);
// //UPDATE FIRST CHART
// d3.selectAll(".lines").style('stroke-opacity', e => {
// return datum.player === e.player ? 1 : .1;
// });
// d3.selectAll(".names").style('opacity', e => {
// // console.log(e);
// return datum.player === e.player ? 1 : .1;
// });
// }

// function mouseOut(datum) {
// //UPDATE FIRST CHART
// d3.selectAll(".lines").style("stroke-opacity", d => {
// // console.log(d.player, highlights.includes(d.player));
// if (highlights.includes(d.player)) {
// return 1;
// } else {
// return .1;
// }
// });
// d3.selectAll(".names").style('opacity', e => {
// // console.log(e);
// return highlights.includes(e.player) ? 1 : .1;
// });
// }

return svg.node();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
playerCareer = [...new Set(playerCum.map(d => d.player))]
Insert cell
playerCum = {
const out = [];
playerG.map(d => {
const data = d[1].sort((a, b) => +a.Age - +b.Age);
// console.log(data);
const cumPoints = d3.cumsum(data, d => d["Adj.PTS"]);
const cumAcutalPoints = d3.cumsum(data, d => d["PTS"]);
const cumGP = d3.cumsum(data, d => d["GP"]);
d[1].forEach((dd, i) => {
dd.cumPoints = cumPoints[i];
dd.cumAcutalPoints = cumAcutalPoints[i];
dd.cumGP = cumGP[i];
});

d[1].unshift({ cumGP: 0, cumPoints: 0, cumAcutalPoints: 0 });
out.push({ player: d[0], data: data });
// out.data.unshift({ cumGP: 0, cumPoints: 0 });
return d;
// console.log(d);
});

return out.filter(d => topAlltime.includes(d.player));
}
Insert cell
Insert cell
transition = {
const lines = d3.selectAll(".lines");
const playerLabel = d3.selectAll(".names");
const xScale = d3
.scaleLinear()
.domain([0, d3.max(playerCum, d => d3.max(d.data, dd => dd.cumGP))])
.range([0, dimensions.boundedWidth])
.nice();
const yScale = d3
.scaleLinear()
.domain([
0,
d3.max(playerCum, d => d3.max(d.data, dd => dd.cumAcutalPoints))
])
.range([dimensions.boundedHeight, 0])
.nice();
if (r === "Actual") {
const newline = d3
.line()
.x(d => xScale(d.cumGP))
.y(d => {
// console.log(d.cumAcutalPoints, yScale(+d.cumAcutalPoints));
return yScale(d.cumAcutalPoints);
});

// transition lines
lines
.transition()
.duration(1200)
.ease(d3.easeLinear)
.attr("d", d => {
return newline(d.data);
});

// transition dots
playerLabel
// .selectAll("g")
.transition()
.duration(1200)
.ease(d3.easeLinear)
.attr("transform", d => {
return `translate(${xScale(last(d.data).cumGP)}, ${yScale(
last(d.data).cumAcutalPoints
)})`;
});
} else if (r === "Adjusted") {
const lineGenerator = d3
.line()
.x(d => xScale(d.cumGP))
.y(d => yScale(d.cumPoints));
// playerLabel
// .selectAll("g")
// lines
// .selectAll("path")
// .transition()
// .duration(1200)
// .attr("d", d => lineGenerator(d.data));

lines
.transition()
.duration(1200)
.ease(d3.easeLinear)
.attr("d", d => {
return lineGenerator(d.data);
});
//
// .attr("transform", d => {
// return `translate(${xScale(last(d.data).cumGP)}, ${yScale(
// last(d.data).cumPoints
// )})`;
// });

// transition dots
playerLabel
// .selectAll("g")
.transition()
.duration(1200)
.ease(d3.easeLinear)
.ease(d3.easeLinear)
.attr("transform", d => {
return `translate(${xScale(last(d.data).cumGP)}, ${yScale(
last(d.data).cumPoints
)})`;
});
}
}
Insert cell
dimensions
Insert cell
last = array => array[array.length - 1]
Insert cell
cumPoints = d3.cumsum(tp, d => d["Adj.PTS"])
Insert cell
playerG = d3.groups(tp, d => d.Name)
Insert cell
Insert cell
Insert cell
// careerStats.filter(d => careerGP.find(g => g[0] === d[0])[1] > 100)
Insert cell
careerGP = d3
.rollups(tp, v => d3.sum(v, d => d.GP), d => d.Name)
.sort((a, b) => b[1] - a[1])
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
tp
Insert cell
color1 = d3.scaleOrdinal(d3.range([...new Set(tp.map(d => d.Name))]), [
'#a6cee3',
'#1f78b4',
'#b2df8a',
'#33a02c',
'#fb9a99',
'#e31a1c',
'#fdbf6f',
'#ff7f00',
'#cab2d6',
'#6a3d9a',
'#ffff99',
'#b15928'
])
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// import { vl } from "@vega/vega-lite-api"
Insert cell
Insert cell
tp = {
const out = allraw.map(({ ...d }) => {
d.Year = d.Season.slice(0, 4);
let g = 0;
d.Season === "2012-13"
? (d.PPG = d3.format(",.2f")(+d["Adj.PTS"] / ((+d.GP / 48) * 82)))
: d.Season === "1994-95"
? (d.PPG = d3.format(",.2f")(+d["Adj.PTS"] / ((+d.GP / 48) * 82)))
: (d.PPG = d3.format(",.2f")(+d["Adj.PTS"] / +d.GP));

d["Adj Points"] = +d["Adj.PTS"];
return d;
});
return out.filter(d => +d.GP > 20 && d.Lg === "NHL"); // only include seasons with this many GP
}
Insert cell
legendsraw = d3.csvParse(await FileAttachment("allstats@1.csv").text())
Insert cell
currentPlayers = d3.csvParse(await FileAttachment("allstatsCurrent.csv").text())
Insert cell
currentPlayers
Insert cell
Insert cell
tp.filter(d => d.Name === "Maurice Richard")
Insert cell
Insert cell
// nhl = require('https://bundle.run/statsapi-nhl@1.0.3')
Insert cell
Insert cell
_ = require("lodash")
Insert cell
Insert cell
import { table } from '@tmcw/tables/2'
Insert cell
import { select } from "@jashkenas/inputs"
Insert cell
import { autoSelect } from "@jashkenas/inputs"
Insert cell
import { radio, slider, checkbox } from "@jashkenas/inputs"
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