Published
Edited
Jun 2, 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
//function math round to avoid non integer values
//2* padding for the left and right side of matrix, 3* for the columns needed
cellWidth = Math.round ((width-5*padding) / 4)
Insert cell
cellHeight = Math.round ((height-5*padding) / 4)
Insert cell
Insert cell
//Reference: https://github.com/d3/d3-scale-chromatic
color = d3.scaleOrdinal(d3.schemeYlGnBu[3])
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]) {
//select all circles from the Dataset data
d3.select(this)
.selectAll("circle")
.data(data)
.enter()
//draw the circles
.append("circle")
//go through every object and look at the x value
.attr ("cx", function (object)
{
var value_x = object [attributes[i]];
//scale the x value with appropiate function
value_x = x[i](value_x);
return value_x;
})
//go through every object and look at the y value
.attr ("cy", function (object)
{
var value_y = object [attributes[j]];
//scale the y value with appropiate function
value_y = y[j](value_y);
return value_y;
});
// 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 => {
//get the x value for point d
var value_x = d [attributes[i]];
value_x = x[i](value_x);
//get the y value for point d
var value_y = d [attributes[j]];
value_y = y[j](value_y);
//if the x value is between the brush selection make it colored
if (value_x <= x1 && value_x >= x0 && value_y <= y1 && value_y >= y0) {
return false;
// true hidden points, false colored points
}
else {return true;}
});
}

// 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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more