Public
Edited
Mar 11
Insert cell
Insert cell
Insert cell
playerMetrics = d3.rollup(
data,
(v) => ({
count: v.length,
avgAge: d3.mean(v, (d) => d.age),
avgValue: d3.mean(v, (d) => d.value_eur)
}),
(d) => d.nationality
)
Insert cell
data = FileAttachment("players_20.csv").csv()
Insert cell
playerArray = Array.from(playerMetrics, ([nationality, values]) => ({
nationality,
count: values.count,
avgAge: values.avgAge,
avgValue: values.avgValue
}))
Insert cell
// hit play for animation to start
viewof i = Scrubber(
playerArray.map((d) => d.avgAge).sort((a, b) => a - b),
{ loop: false, delay: 100 }
)
Insert cell
svg = {
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);

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

// X-axis
g.append("g")
.attr("class", "x--axis")
.attr("transform", `translate(0, ${iheight})`)
.call(d3.axisBottom(x))
.call((axis) =>
axis
.append("text")
.text(xAttr)
.style("fill", "black")
.attr("transform", `translate(${iwidth}, -10)`)
.style("text-anchor", "end")
);

// Y-axis
g.append("g")
.attr("class", "y--axis")
.call(d3.axisLeft(y))
.call((axis) =>
axis
.append("text")
.text(yAttr)
.style("fill", "black")
.style("text-anchor", "middle")
.attr("y", -15)
);
const legendValues = d3.ticks(
d3.min(playerArray, (d) => d.avgAge),
d3.max(playerArray, (d) => d.avgAge),
5
);
const legend = svg
.append("g")
.attr("transform", `translate(${iwidth + margin.right}, ${margin.top})`);

const legendSizes = [20, 30, 40];

legend
.selectAll("legendCircles")
.data(legendValues)
.join("circle")
.attr("cx", 30)
.attr("cy", (d, i) => i * 30)
.attr("r", 10)
.attr("fill", (d) => colorScale(d))
.attr("stroke", "black")
.attr("opacity", 0.7);

legend
.selectAll("legendLabels")
.data(legendValues)
.join("text")
.attr("x", -55)
.attr("y", (d, i) => i * 30)
.text((d) => `Avg Age: ${Math.round(d)}`)
.style("fill", "black")
.style("font-size", "14px")
.attr("alignment-baseline", "middle");

// Title
svg
.append("text")
.attr("x", width / 2)
.attr("y", 20)
.attr("text-anchor", "middle")
.style("font-size", "20px")
.style("font-weight", "bold")
.text("Scatter Plot of Player Data (Size by Avg Age)");

return svg.node();
}
Insert cell
update = {
const g = d3.select(svg).select(".gDrawing");

const filteredData = playerArray.filter((d) => d.avgAge <= i);

const points = g.selectAll(".point").data(filteredData, (d) => d.nationality);

points
.enter()
.append("circle")
.attr("class", "point")
.attr("cx", (d) => x(d[xAttr]))
.attr("cy", (d) => y(d[yAttr]))
.attr("r", 5) // Start at 0 radius
.attr("fill", (d) => colorScale(d.avgAge))
.attr("opacity", 0.7);

points
.transition()
.duration(500)
.attr("fill", (d) => colorScale(d.avgAge));

return svg;
}
Insert cell
margin = ({ left: 50, top: 30, right: 20, bottom: 20 })
Insert cell
height = 400
Insert cell
iheight = height - margin.top - margin.bottom
Insert cell
iwidth = width - margin.left - margin.right
Insert cell
xAttr = "count"
Insert cell
yAttr = "avgValue"
Insert cell
x = d3
.scaleSqrt()
.domain(d3.extent(playerArray, (d) => d[xAttr]))
.range([0, iwidth])
.nice()
Insert cell
y = d3
.scaleLinear()
.domain(d3.extent(playerArray, (d) => d[yAttr]))
.range([iheight, 0])
.nice()
Insert cell
colorScale = d3
.scaleSequential(d3.interpolateTurbo)
.domain(d3.extent(playerArray, (d) => d.avgAge))
Insert cell
import { Scrubber } from "@mbostock/scrubber"
Insert cell
d3 = require("d3@6")
Insert cell
Insert cell
Insert cell
Insert cell
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