Scatterplot matrix
The scatterplot matrix (SPLOM) shows pairwise correlations for multivariate data; each cell is a scatterplot where x encodes the column’s dimension and y encodes the row’s dimension. This matrix shows Kristen Gorman’s data on penguins near Palmer Station in Antarctica. See also the brushable version.
// Specify the chart's dimensions.
const width = 928;
const height = width;
const padding = 28;
const columns = data.columns.filter((d) => typeof data[0][d] === "number");
const size = (width - (columns.length + 1) * padding) / columns.length + padding;
// Define the horizontal scales (one for each row).
const x = columns.map((c) => d3.scaleLinear()
.domain(d3.extent(data, (d) => d[c]))
.rangeRound([padding / 2, size - padding / 2]))
// Define the companion vertical scales (one for each column).
const y = x.map((x) => x.copy().range([size - padding / 2, padding / 2]));
// Define the color scale.
const color = d3.scaleOrdinal()
.domain(data.map((d) => d.species))
.range(d3.schemeObservable10);
// Define the horizontal axis (it will be applied separately for each column).
const axisx = d3.axisBottom()
.ticks(6)
.tickSize(size * columns.length);
const xAxis = (g) => g.selectAll("g").data(x).join("g")
.attr("transform", (d, i) => `translate(${i * size},0)`)
.each(function(d) { return d3.select(this).call(axisx.scale(d)); })
.call((g) => g.select(".domain").remove())
.call((g) => g.selectAll(".tick line").attr("stroke", "#ddd"));
// Define the vertical axis (it will be applied separately for each row).
const axisy = d3.axisLeft()
.ticks(6)
.tickSize(-size * columns.length);
const yAxis = (g) => g.selectAll("g").data(y).join("g")
.attr("transform", (d, i) => `translate(0,${i * size})`)
.each(function(d) { return d3.select(this).call(axisy.scale(d)); })
.call((g) => g.select(".domain").remove())
.call((g) => g.selectAll(".tick line").attr("stroke", "#ddd"));
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [-padding, 0, width, height]);
svg.append("style")
.text(`circle.hidden { fill: #000; fill-opacity: 1; r: 1px; }`);
svg.append("g")
.call(xAxis);
svg.append("g")
.call(yAxis);
const cell = svg.append("g")
.selectAll("g")
.data(d3.cross(d3.range(columns.length), d3.range(columns.length)))
.join("g")
.attr("transform", ([i, j]) => `translate(${i * size},${j * size})`);
cell.append("rect")
.attr("fill", "none")
.attr("stroke", "#aaa")
.attr("x", padding / 2 + 0.5)
.attr("y", padding / 2 + 0.5)
.attr("width", size - padding)
.attr("height", size - padding);
cell.each(function([i, j]) {
d3.select(this).selectAll("circle")
.data(data.filter((d) => !isNaN(d[columns[i]]) && !isNaN(d[columns[j]])))
.join("circle")
.attr("cx", (d) => x[i](d[columns[i]]))
.attr("cy", (d) => y[j](d[columns[j]]));
});
const circle = cell.selectAll("circle")
.attr("r", 3.5)
.attr("fill-opacity", 0.7)
.attr("fill", (d) => color(d.species));
svg.append("g")
.style("font", "bold 10px sans-serif")
.style("pointer-events", "none")
.selectAll("text")
.data(columns)
.join("text")
.attr("transform", (d, i) => `translate(${i * size},${i * size})`)
.attr("x", padding)
.attr("y", padding)
.attr("dy", ".71em")
.text((d) => d);
display(svg.node());
const data = FileAttachment("data/penguins.csv").csv({typed: true}).then(display);