Public
Edited
Feb 11, 2023
Insert cell
Insert cell
Insert cell
viewof selection = {
const svg = d3.create("svg")
.attr("viewBox", [-padding, 0, width, width]);

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 => z(d.species));

cell.call(brush, circle, svg);

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);

svg.property("value", [])
return svg.node();
}
Insert cell
selection
Insert cell
function brush(cell, circle, svg) {
const brush = d3.brush()
.extent([[padding / 2, padding / 2], [size - padding / 2, size - padding / 2]])
.on("start", brushstarted)
.on("brush", brushed)
.on("end", brushended);

cell.call(brush);

let brushCell;

// Clear the previously-active brush, if any.
function brushstarted() {
if (brushCell !== this) {
d3.select(brushCell).call(brush.move, null);
brushCell = this;
}
}

// Highlight the selected circles.
function brushed({selection}, [i, j]) {
let selected = [];
if (selection) {
const [[x0, y0], [x1, y1]] = selection;
circle.classed("hidden",
d => x0 > x[i](d[columns[i]])
|| x1 < x[i](d[columns[i]])
|| y0 > y[j](d[columns[j]])
|| y1 < y[j](d[columns[j]]));
selected = data.filter(
d => x0 < x[i](d[columns[i]])
&& x1 > x[i](d[columns[i]])
&& y0 < y[j](d[columns[j]])
&& y1 > y[j](d[columns[j]]));
}
svg.property("value", selected).dispatch("input");
}

// If the brush is empty, select all circles.
function brushended({selection}) {
if (selection) return;
svg.property("value", []).dispatch("input");
circle.classed("hidden", false);
}
}
Insert cell
x = columns.map(c => d3.scaleLinear()
.domain(d3.extent(data, d => d[c]))
.rangeRound([padding / 2, size - padding / 2]))
Insert cell
y = x.map(x => x.copy().range([size - padding / 2, padding / 2]))
Insert cell
z = d3.scaleOrdinal()
.domain(data.map(d => d.species))
.range(d3.schemeCategory10)
Insert cell
xAxis = {
const axis = d3.axisBottom()
.ticks(6)
.tickSize(size * columns.length);
return g => g.selectAll("g").data(x).join("g")
.attr("transform", (d, i) => `translate(${i * size},0)`)
.each(function(d) { return d3.select(this).call(axis.scale(d)); })
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick line").attr("stroke", "#ddd"));
}
Insert cell
yAxis = {
const axis = d3.axisLeft()
.ticks(6)
.tickSize(-size * columns.length);
return g => g.selectAll("g").data(y).join("g")
.attr("transform", (d, i) => `translate(0,${i * size})`)
.each(function(d) { return d3.select(this).call(axis.scale(d)); })
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick line").attr("stroke", "#ddd"));
}
Insert cell
data = d3.csvParse(await FileAttachment("penguins.csv").text(), d3.autoType)
Insert cell
columns = data.columns.filter(d => typeof data[0][d] === "number")
Insert cell
width = 954
Insert cell
size = (width - (columns.length + 1) * padding) / columns.length + padding
Insert cell
padding = 28
Insert cell
d3 = require("d3@6")
Insert cell
import {swatches} from "@d3/color-legend"
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