Published unlisted
Edited
Jan 5, 2021
Insert cell
Changed in fork
-
md`# Brushable Scatterplot Matrix
+
md`# Brushable Scatterplot Matrix - Rects
This [scatterplot matrix](/@d3/scatterplot-matrix) allows brushing to select data points in one cell, and highlight them across all other cells.`
Insert cell
Insert cell
Changed in fork
viewof selection = {
-
const svg = d3.create("svg") .attr("viewBox", [-padding, 0, width, width]);
+
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("style") .text( `rect.hidden { fill: #000; fill-opacity: 1; width: 1px; height: 1px;}` );
-
svg.append("g") .call(xAxis);
+
svg.append("g").call(xAxis);
-
svg.append("g") .call(yAxis);
+
svg.append("g").call(yAxis);
-
const cell = svg.append("g")
+
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})`);
+
.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 .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")
+
d3.select(this) .selectAll("rect")
.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]]));
+
.join("rect") .attr("x", d => x[i](d[columns[i]])) .attr("y", 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));
+
const circle = cell .selectAll("rect") .attr("width", 3.5) .attr("height", 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")
+
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);
+
.attr("transform", (d, i) => `translate(${i * size},${i * size})`) .attr("x", padding) .attr("y", padding) .attr("dy", ".71em") .text(d => d);
-
svg.property("value", [])
+
svg.property("value", []);
return svg.node(); }
Insert cell
selection
Insert cell
Changed in fork
-
function brush(cell, circle, svg) {
+
function brush(cell, rect, 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",
+
rect.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);
+
rect.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 = 20
Insert cell
d3 = require("d3@6")
Insert cell
import {swatches} from "@d3/color-legend"
Insert cell