Skip to content

Instantly share code, notes, and snippets.

@soxofaan
Last active August 15, 2017 12:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save soxofaan/a2786167fc712ffad94b88976fb6f698 to your computer and use it in GitHub Desktop.
Save soxofaan/a2786167fc712ffad94b88976fb6f698 to your computer and use it in GitHub Desktop.
Four-dimensional hypersphere packing
license: mit
border: no

Visualisation of basic sphere packing problem in four dimensions, where there are 16 spheres with radius 1 at the corners of 2x2x2x2 hypercube and one sphere (also radius 1) at the origin touching all these 16 spheres.

The visualisation of the four dimensions x, y, z and w is done by two linked two-dimensional plots, each showing different two-dimensional slices of the four-dimensional setup. For example: the xy-plot on the left shows the edges of the hypersphere, sliced in the x- and y-direction for certain fixed z- and w-value. These z- and w-value are indicated by the pink crosshair symbol on the zw-plot on the right.

The crosshairs (and consequently the slicing) can be manipulated by clicking on each plot area or drag and drop.

For example: the hypersphere at the origin touches the other hyperspheres at positions [+/-0.5, +/-0.5, +/-0.5, +/-0.5]. Move the crosshair to these positions to see the hyperspheres touching

Inspired by (but different from) https://www.youtube.com/watch?v=zwAD6dRSVyI

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>4D sphere packing</title>
<link rel="stylesheet" type="text/css" href="main.css">
</head>
<body>
<div id="container"></div>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="main.js"></script>
<script>
main('#container');
</script>
</body>
</html>
text {
font-family: sans-serif;
font-size: 10px;
}
text.label {
text-anchor: middle;
}
text.title {
font-size: 12pt;
text-anchor: middle;
}
.spheres circle {
fill: none;
stroke-width: 2px;
}
rect.overlay {
fill: none;
pointer-events: all;
}
.slice {
fill: none;
stroke: #d629ca;
}
function main(domSelector) {
// Sphere data: list of [radius, cx, cy, cz, cw]
var data = [
// Origin
[1, 0, 0, 0, 0],
// Hypercube corners
[1, 1, 1, 1, 1],
[1, 1, 1, 1, -1],
[1, 1, 1, -1, 1],
[1, 1, 1, -1, -1],
[1, 1, -1, 1, 1],
[1, 1, -1, 1, -1],
[1, 1, -1, -1, 1],
[1, 1, -1, -1, -1],
[1, -1, 1, 1, 1],
[1, -1, 1, 1, -1],
[1, -1, 1, -1, 1],
[1, -1, 1, -1, -1],
[1, -1, -1, 1, 1],
[1, -1, -1, 1, -1],
[1, -1, -1, -1, 1],
[1, -1, -1, -1, -1]
];
var color = d3.scaleOrdinal(d3.schemeCategory20);
var width = 460;
var height = 460;
var scaleRight = d3.scaleLinear().domain([-2, 2]).range([-0.45 * width, 0.45 * width]);
var scaleUp = d3.scaleLinear().domain([-2, 2]).range([0.45 * height, -0.45 * height]);
var svg = d3.select(domSelector).append('svg')
.attr("width", 2 * width)
.attr("height", height);
function setup(svg, xName, yName, zName, wName) {
var plot = svg.append('g')
.attr("class", "plot");
// Draw axes
var axes = plot.append('g').attr("class", "axes");
axes.append('g').call(d3.axisBottom(scaleRight));
axes.append('text')
.attr("class", "label")
.attr("x", scaleRight(2)).attr("y", -5)
.text(xName);
axes.append('g').call(d3.axisLeft(scaleUp));
axes.append('text')
.attr("class", "label")
.attr("x", 10).attr("y", scaleUp(2)).attr("dy", "0.32em")
.text(yName);
var title = plot.append("text").attr("class", "title").attr("y", scaleUp(2) - 10);
function setTitle(z, w) {
var f = d3.format(".3f");
title.text(xName + yName + "-Slice at " + zName + "=" + f(z) + ", " + wName + "=" + f(w));
}
// Layer to draw spheres
var spheres = plot.append("g").attr("class", "spheres");
// Slice indicator
var slice = plot.append("g").attr("class", "slice");
var s = scaleRight(0.05) - 0;
slice.append("circle").attr("r", 0.8 * s);
slice.append("line").attr("x1", -s).attr("x2", s);
slice.append("line").attr("y1", -s).attr("y2", s);
function setSlice(x, y) {
slice.attr("transform", "translate(" + scaleRight(x) + "," + scaleUp(y) + ")");
}
// Overlay to handle drag events.
var overlay = plot.append("rect")
.attr("class", "overlay")
.attr("x", -0.5 * width).attr("y", -0.5 * height)
.attr("width", width).attr("height", height);
return {
"plot": plot,
"overlay": overlay,
"spheres": spheres,
"axes": axes,
"slice": slice,
"setSlice": setSlice,
"setTitle": setTitle
};
}
function drawPlot(plot, cxi, cyi, zi, wi, z, w) {
plot.setTitle(z, w);
// Update
var spheres = plot.spheres.selectAll("circle")
.data(data);
// Enter
spheres.enter()
.append('circle')
.attr('cx', function (d) {return scaleRight(d[cxi]);})
.attr('cy', function (d) {return scaleUp(d[cyi]);})
.style("stroke", function (d, i) {return color(i);})
// Update + enter
.merge(spheres)
.attr("r", function (d) {
// (x-cx)^2 + (y-cy)^2 + (z-cz)^2 + (w-cw)^2 = r^2
var r2 = d[0] * d[0] - (z - d[zi]) * (z - d[zi]) - (w - d[wi]) * (w - d[wi]);
var r = Math.sqrt(Math.max(0, r2));
return scaleRight(r) - scaleRight(0)
})
;
}
var xyPlot = setup(svg, "x", "y", "z", "w");
xyPlot.plot.attr("transform", "translate(" + 0.5 * width + "," + 0.5 * height + ")");
var zwPlot = setup(svg, "z", "w", "x", "y");
zwPlot.plot.attr("transform", "translate(" + 1.5 * width + "," + 0.5 * height + ")");
function drawAtXY(x, y) {
drawPlot(zwPlot, 3, 4, 1, 2, x, y);
xyPlot.setSlice(x, y);
}
function drawAtZW(z, w) {
drawPlot(xyPlot, 1, 2, 3, 4, z, w);
zwPlot.setSlice(z, w);
}
xyPlot.overlay.call(d3.drag()
.on("drag end", function () {
var where = d3.mouse(this);
drawAtXY(scaleRight.invert(where[0]), scaleUp.invert(where[1]))
})
);
zwPlot.overlay.call(d3.drag()
.on("drag end", function () {
var where = d3.mouse(this);
drawAtZW(scaleRight.invert(where[0]), scaleUp.invert(where[1]))
})
);
drawAtXY(0.1, 0.2);
drawAtZW(0.3, 0.4);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment