Published
Edited
Feb 7, 2022
6 stars
Insert cell
Insert cell
Insert cell
{
// USER DEFINED VARS
// set outer width and height for svg
const width = window.innerWidth * .9
const height = width * 2/3
// user defined: size of grid and # cells
const nrows = 10;
const ncols = 20;
// CHART STUFF
// chart constants
const margin = {
top: 70,
bottom: 50,
left: 60,
right: 50
}
// params for chart itself.
const gridWidth = width - margin.left - margin.right;
const gridHeight = height - margin.bottom - margin.top;
// make svg (use margin convention for spacing)
const svg = DOM.svg(
gridWidth + margin.left + margin.right,
gridHeight + margin.bottom + margin.top
)
const svgSel = d3.select(svg)
const sel = svgSel.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`)
// calculate cell size dynamically based on user input
const cellWidth = gridWidth / ncols;
const cellHeight = gridHeight / nrows;
// define scales to be dynamic based on grid size
const gridXScale = d3.scaleLinear().domain([0, ncols]).range([0, gridWidth]);
const gridYScale = d3.scaleLinear().domain([0, nrows]).range([0, gridHeight]);
// init grid data
let data = []
// loop thru and generate desired grid data
// let cell = 0;
for (let rowNumber = 0; rowNumber < nrows; rowNumber++) {
for (let colNumber = 0; colNumber < ncols; colNumber++) {
let obs = {
x: colNumber,
y: rowNumber,
height: cellHeight,
width: cellWidth
};
// add data to data
data.push(obs)
}
}
// DELAUNAY STUFF
const delan = d3.Delaunay.from(
data,
(d) => gridXScale(d.x),
(d) => gridYScale(d.y)
);

// create g tags to append to (in grid shape)
const join = sel.selectAll('g.grid')
.data(data)
.enter().append('g')
.attr('class', 'grid')
.attr('transform', (d, i) => {
return `translate(${gridXScale(d.x)}, ${gridYScale(d.y)})`
})
// add the overlay on top of everything to take the mouse events
svgSel
.append("rect")
.attr("class", "overlay")
.attr("width", gridWidth + margin.left + margin.right)
.attr("height", gridHeight + margin.bottom + margin.top)
.style("opacity", 0)
// .style('pointer-events', 'none')
.on("mousemove", hover)
.on("mouseleave", unhover);

const circlePadding = 2
// append whatever in grid; circles here
sel.selectAll('g.grid').each(function(d,i) {
const cell = d3.select(this).append('g')
// append circles
cell.append('circle')
.attr('class', 'grid-circle')
.attr('r', cellWidth / 2 - circlePadding - 6)
.attr('fill', 'coral')
.attr('ii', i)
})
// .on('mouseover', function() {
// d3.select(this).attr("stroke", "purple").attr("stroke-width", 7)
// })
// .on('mouseout', function() {
// d3.select(this).attr("stroke", "purple").attr("stroke-width", 0)
// })
function hover(event) {
// get the current mouse position
const [mx, my] = d3.pointer(event);
// use the new diagram.find() function to find the Voronoi mouse position
// make sure to account for margin differences since delan on g
const closestPoint = delan.find(mx - margin.left, my - margin.top);
// highlight the point if we found one
const closestDataPoint = data[closestPoint];
// highlight current circle
d3.selectAll("circle.grid-circle").filter(
(d) => d.x === closestDataPoint.x && d.y === closestDataPoint.y
).attr("stroke", "purple").attr("stroke-width", 7).raise();
// unhover all other circles
d3.selectAll("circle.grid-circle").filter(
(d) => d.x !== closestDataPoint.x || d.y !== closestDataPoint.y
).attr('stroke-width', 0).attr('stroke', 'coral')
}
// unhover all cells
function unhover() {
d3.selectAll('circle.grid-circle').attr('stroke-width', 0).attr('stroke', 'coral')
}

return svg
}
Insert cell
Type JavaScript, then Shift-Enter. Ctrl-space for more options. Arrow ↑/↓ to switch modes.

Insert cell
Type JavaScript, then Shift-Enter. Ctrl-space for more options. Arrow ↑/↓ to switch modes.

Insert cell
d3 = require("d3@6", "d3-delaunay@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