Public
Edited
Sep 14, 2022
1 star
Insert cell
Insert cell
{
const size = 300;
const n = 8;
const cellSize = size / n;
const svg = d3.create("svg")
.attr("width", width)
.attr("height", size)
.attr("viewbox", [0,0,width, size]);

// Offset out grid from the origin a bit.
const offsetG = svg.append("g").attr("id", "iso").attr("transform", `translate(${size*1.8}, 0)`);
// First we just want a grid.
makeGrid(offsetG, n, cellSize, "flatGrid", "#146152")
// We can apply a transform to the grid to make it isometric.
const isoG = svg.append("g").attr("id", "iso").attr("transform", `matrix(0.866, 0.5, -0.866, 0.5, ${size*0.9}, 0)`);
makeGrid(isoG, n, cellSize, "flatGrid", "#FF5A33")

const point = [80,80];
// Add point in ortho space
offsetG.append("circle")
.attr("r", 15)
.attr("stroke","#146152")
.attr("fill","white")
.attr('stroke-width', 1)
.attr("cx", point[0])
.attr("cy", point[1]);
// Add point in iso space
const isoPoint = toIso(point);
isoG.append("circle")
.attr("r", 15)
.attr("stroke","#FF5A33")
.attr("fill","white")
.attr('stroke-width', 1)
.attr("cx", point[0])
.attr("cy", point[1]);
// Draw a line between the two.
// offsetG.append('path')
// .attr('d', d3.line()([[point[0],point[1]], [0,0]]))
// .attr('stroke', 'black')
// .attr('stroke-width', 1)
// Write coords
svg.append('text').attr('id','offset-coords').attr('x', 5).attr('y', '20')
svg.append('text').attr('id','iso-coords').attr('x', 5).attr('y', size - 10)

svg.on("pointermove", (event) => {
const pointer = d3.pointer(event);
// if our pointer is right of the iso grid, convert cartesian -> iso
var [x0, y0] = [pointer[0]-(size*1.8), pointer[1]];
// if our pointer is left of the iso grid, convert iso -> cartesian
if (x0 < 0) [x0, y0] = fromIso([pointer[0] - (size*0.9), pointer[1]])
offsetG.selectAll('circle')
.attr('cx', x0)
.attr('cy', y0)
// We can use the same coords for circle since the entire group is transformed.
isoG.selectAll('circle')
.attr('cx', x0)
.attr('cy', y0)
// For our path we need the end point of the iso circle in cartesian space.
const [x1, y1] = toIso([x0, y0]);
offsetG.selectAll('path').attr('d', d3.line()([[x0 ,y0], [x1 - size * 0.9, y1]]))
// color rects based on distance from the point
const gs = [isoG, offsetG];
gs.forEach((selection, k) => {
const cm = d3.scaleLinear().range([k ==0 ? '#FF5A33' : '#146152', 'white']).domain([0, size]);
const rows = selection.selectAll('.gridrow');
rows.each((d, i) => {
const rects = d3.select(rows._groups[0][i]).selectAll('rect');
rects.attr('fill', (r, j) => {
const dist = distance(
[
cellSize * j,
cellSize * i,
],
[x0, y0]
)
return cm(dist)
});
});
})
const fmt = d3.format('.2d')
svg.select('#offset-coords').text(`Coords: ${fmt(x0)}, ${fmt(y0)}`)
svg.select('#iso-coords').text(`Coords (Projected): ${fmt(x1)}, ${fmt(y1)}`)
})
// We can make another grid
yield svg.node();
}

Insert cell
function makeGrid(selection, n, cellSize, id="grid", stroke="black", fill="None") {
const numbers = d3.range(n);
const gridrow = selection.append("g")
.attr("id","grid")
.selectAll("g")
.data(numbers)
.join("g")
.attr("id", (d) => `gridrow-${d}`)
.attr("class","gridrow")
.attr("transform", d => `translate(0, ${d*cellSize})`);

const gridCell = gridrow.selectAll("g")
.data(numbers)
.join("g")
.attr("id", d => `gridcell-${d}`)
.attr("transform", d => `translate(${d*cellSize}, 0)`);;
gridCell.append("rect")
.attr("rx", Math.round(cellSize / 10))
.attr("x", Math.round(cellSize / 10))
.attr("y", Math.round(cellSize / 10))
.attr("stroke", stroke)
.attr("fill", fill)
.attr("width", cellSize - Math.round(cellSize / 5))
.attr("height", cellSize - Math.round(cellSize / 5));
return selection
}
Insert cell
function toIso(coords) {
// Do a linear matrix transform
const [a,b,c,d] = [
Math.cos(Math.PI/6), // a
Math.sin(Math.PI/6), // b
-Math.cos(Math.PI/6), // c
Math.sin(Math.PI/6), // d
];
const [x, y] = coords;
const _x = a*x + c*y;
const _y = b*x + d*y;
return [_x, _y];
}
Insert cell
function fromIso(coords) {
// Do the inverse transform
const [a,b,c,d] = [
Math.sin(Math.PI/6), // d
-Math.sin(Math.PI/6), // -b
Math.cos(Math.PI/6), // - c
Math.cos(Math.PI/6), // a
];
const [x, y] = coords;
const adminusbc = a*d - b*c;
const _x = (a*x + c*y) / adminusbc;
const _y = (b*x + d*y) / adminusbc;
return [_x, _y];
}
Insert cell
function distance(a,b) {
const [ax,ay] = a;
const [bx,by] = b;
return Math.sqrt(Math.pow(bx - ax, 2) + Math.pow(by - ay, 2))
}
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