Public
Edited
May 13, 2024
Insert cell
Insert cell
chart = {
const svgCanvas = plotCanvas(data.length);
plotContent(svgCanvas, data);
horizontalAxis.draw({ data, svgCanvas });
verticalAxis.draw({ data, svgCanvas });
return svgCanvas.node();
}
Insert cell
// 5. Plot content
function plotContent(svgCanvas, data) {
const { yScale } = verticalAxis.getScale(data);
const { xScale } = horizontalAxis.getScale(data);

svgCanvas
.append("g")
.classed("gf-rects", true)
.attr("fill", "hsl(96, 44%, 40%)")
.selectAll()
.data(data)
.join("rect")
.attr("height", yScale.bandwidth())
.attr("x", xScale(0))
.attr("width", (d) => Math.abs(xScale(d.gf) - xScale(0)))
.attr("y", (d) => yScale(d.team));

svgCanvas
.append("g")
.classed("ga-rects", true)
.attr("fill", "hsl(359.4, 87%, 40%)")
.selectAll()
.data(data)
.join("rect")
.attr("height", yScale.bandwidth())
.attr("width", (d) => Math.abs(xScale(d.ga) - xScale(0)))
.attr("y", (d) => yScale(d.team))
.attr("x", (d) => xScale(-d.ga));

svgCanvas
.append("g")
.classed("labels", true)
.attr("fill", "white")
.selectAll("text")
.data(data)
.join("text")
.attr("text-anchor", "start")
.attr("x", (d) => xScale(-d.ga))
.attr("y", (d) => yScale(d.team) + yScale.bandwidth() / 2)
.attr("dy", "0.35em")
.attr("dx", 6)
.text((d) => `-${d.ga.toLocaleString()}`);

svgCanvas
.append("g")
.classed("labels", true)
.attr("fill", "white")
.selectAll("text")
.data(data)
.join("text")
.attr("text-anchor", "end")
.attr("x", (d) => xScale(d.gf))
.attr("y", (d) => yScale(d.team) + yScale.bandwidth() / 2)
.attr("dy", "0.35em")
.attr("dx", -6)
.text((d) => `+${d.gf.toLocaleString()}`);
}
Insert cell
// 5. Plot vertical axis
verticalAxis = ({
getScale(data) {
const { marginTop, marginBottom, height } = getDimensions(data.length);
const yDomain = d3.map(data, (d) => d.team);
const yRange = [marginTop, height - marginBottom];
const yScale = d3
.scaleBand(yDomain, yRange)
.domain(yDomain)
.rangeRound(yRange)
.padding(0.1);
return { yDomain, yRange, yScale };
},
draw({ data, svgCanvas }) {
const { yScale } = this.getScale(data);
const { xScale } = horizontalAxis.getScale(data);
const yAxis = d3.axisLeft(yScale).tickSizeOuter(0);

svgCanvas
.append("g")
.classed("y-axis", true)
.attr("transform", `translate(${xScale(0)},0)`)
.call(yAxis)
.call((g) => g.selectAll(".tick text").attr("fill", "white"))
.call((g) =>
g
.selectAll(".tick text")
.filter((_, i) => data[i].value > 0)
.attr("text-anchor", "start")
.attr("x", 6)
);
}
})
Insert cell
// 5. Plot horizontal axis
horizontalAxis = ({
getScale(data) {
const { marginLeft, marginRight, width } = getDimensions(data.length);
const xDomain = d3.extent(data, (d) => d.value);
const xRange = [marginLeft, width - marginRight];
const xScale = d3.scaleLinear(xDomain, xRange);
return { xDomain, xRange, xScale };
},
draw({ data, svgCanvas }) {
const { marginBottom, width, height } = getDimensions(data.length);
const { xScale } = this.getScale(data);
const xAxis = d3.axisBottom(xScale).ticks(width / (data.length * 4));
svgCanvas
.append("g")
.classed("x-axis", true)
.call(xAxis)
.call((g) => g.select(".domain").remove())
.attr("transform", `translate(0,${height - marginBottom})`);
}
})
Insert cell
// 4. Plot canvas
function plotCanvas(dataLength) {
const { width, height } = getDimensions(dataLength);
const svgEl = d3
.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height]);

return svgEl;
}
Insert cell
// 3. Define dimensions
function getDimensions(dataLength) {
const width = 1228;
const marginTop = 0;
const marginBottom = 25;
const marginRight = 10;
const marginLeft = 10;
const barHeight = 40;

return {
marginTop,
marginRight,
marginBottom,
marginLeft,
barHeight,
width,
height: Math.ceil((dataLength + 0.1) * barHeight) + marginTop + marginBottom
};
}
Insert cell
// 2. Normalize data
data = d3
.sort(rawData, (d) => -Number(d.pts))
.map((d) => {
const gf = +d.gf;
const ga = +d.ga;

return {
team: d.team,
gf: gf,
ga: ga,
value: gf > ga ? gf : -ga
};
})
Insert cell
// 1. Fetch data
rawData = FileAttachment("epl-standings-2022-23.csv").csv()
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