Published
Edited
May 27, 2020
Insert cell
Insert cell
Insert cell
data = d3.csvParse(await FileAttachment("iris.csv").text(), d3.autoType)
Insert cell
attributes = data.columns.filter(d => d !== "species")
Insert cell
Insert cell
height = width // set equal to width for a square visualization
Insert cell
Insert cell
cellWidth = Math.floor((width - 5*padding) / 4)
Insert cell
cellHeight = Math.floor((height - 5*padding) / 4)
Insert cell
Insert cell
/*color = function(_) {
return "#263238";
}*/
color = d3.scaleOrdinal(d3.schemeCategory10)
//.domain(["setosa", "versicolor", "virginica"]); //dummy test but it works!!!
.domain(data.map(d => d.species));
Insert cell
Insert cell
Insert cell
scatterplotMatrix = {
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);

// set the style of hidden data items
svg.append("style").text("circle.hidden { fill: #000; fill-opacity: 0.1;}");

// setup a group-element for each cell with the respective transform
const cell = svg
.append("g")
.selectAll("g")
.data(d3.cross(d3.range(attributes.length), d3.range(attributes.length)))
.join("g")
.attr(
"transform",
([i, j]) =>
`translate(${padding + i * (padding + cellWidth)},${padding +
j * (padding + cellHeight)})`
);

// create rectangles showing the plot area of each cell
cell
.append("rect")
.attr("fill", "#ECEFF1")
.attr("stroke", "none")
.attr("width", cellWidth)
.attr("height", cellHeight);

// the following function is called for each individual cell
// i, j are the column- and row-index of this cell
cell.each(function([i, j]) {
// TODO: draw the scatterplot for this cell here
d3.select(this)
.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", d => x[i](d[attributes[i]]))
.attr("cy", d => y[j](d[attributes[j]]))
// create an x-axis below each cell
d3.select(this)
.append("g")
.attr("transform", `translate(0, ${cellHeight})`)
.call(d3.axisBottom(x[i]).ticks(cellWidth / 50));

// create a y-axis to the left of each cell
d3.select(this)
.append("g")
.call(d3.axisLeft(y[j]).ticks(cellHeight / 30));
});

// set attributes of the circles
const circle = cell
.selectAll("circle")
.attr("r", 4.5)
.attr("fill-opacity", 0.5)
.attr("fill", d => color(d.species));

// setup a brush for each cell
cell.call(brush, circle);

// create a label for each attribute on the diagonal
svg
.append("g")
.style("font", "18px Source Serif Pro")
.style("pointer-events", "none")
.selectAll("text")
.data(attributes)
.join("text")
.attr(
"transform",
(d, i) =>
`translate(${padding + i * (padding + cellWidth)},${padding +
i * (padding + cellHeight)})`
)
.attr("x", 10)
.attr("y", 10)
.attr("dy", ".71em")
.text(d => d);

return svg.node();
}
Insert cell
Insert cell
function brush(cell, circle) {
const brush = d3
.brush()
.extent([[0, 0], [cellWidth, cellHeight]])
.on("start", startBrushing)
.on("brush", updateBrush)
.on("end", stopBrushing);

cell.call(brush);

let brushCell;

// clear any previous brush
function startBrushing() {
if (brushCell !== this) {
d3.select(brushCell).call(brush.move, null);
brushCell = this;
}
}

// highlight circles in the brush selection
function updateBrush([i, j]) {
if (d3.event.selection === null) return;
const [[x0, y0], [x1, y1]] = d3.event.selection;

// TODO: implement the linking by de-emphasizing data items outside the brush
circle.classed("hidden", d => {
/* Result is just a check if the circle is outside of the brush. I find out that (x0, y0)
is under left and (x1, y1) is upper right after trying out several
methods for different positions of (x0, y0) and (x1, y1). */
var result = x0 > x[i](d[attributes[i]]) ||
x1 < x[i](d[attributes[i]]) ||
y0 > y[j](d[attributes[j]]) ||
y1 < y[j](d[attributes[j]]);
return result;
});
}

// lift the selection if the brush is empty
function stopBrushing() {
if (d3.event.selection !== null) return;
circle.classed("hidden", false);
}
}
Insert cell
md`## Appendix`
Insert cell
x = attributes.map(attribute =>
d3
.scaleLinear()
.domain(d3.extent(data, item => item[attribute]))
.range([0, cellWidth])
)
Insert cell
y = x.map(x => x.copy().range([cellHeight, 0]))
Insert cell
padding = 32
Insert cell
import { swatches } from "@d3/color-legend"
Insert cell
d3 = require("d3@5")
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