Skip to content

Gosper

Gosper uses the Gosper curve, so the cells are hexagons rather than rectangles. A level-LL curve has 7L7^L cells, arranged in the fractal “Gosper island” that the curve fills.

Unlike the lattice methods, Gosper returns cell centres in the input’s own coordinate range.

Data

Technique

Vis

Colour
Show

namedefaultmeaning
whitespace1spare cells beyond the point count
pluslevel0extra curve levels — each one costs seven times the cells
orientation"flat"which way up the hexagons sit
const { positions } = hagrid.gridify(data, "gosper", { whitespace: 1.5 });
const positions = hagrid.gridify_gosper(data, { level: 4, orientation: "pointy" });

Because a level costs 7×7\times the cells, ask for slack with whitespace rather than pluslevel.

The hexagonal lattice is not only a different look. If what you are placing in each cell is round — a circle, a radial glyph, anything bounded by its own circumcircle — a hexagonal grid fits a larger one than a square grid of the same cell area.

Each Gosper cell has six equidistant neighbours where a square cell has four, and the distance between centres is larger for the same area:

latticeequidistant neighbourslargest round glyph
square (Hilbert, Gilbert, DGrid)41.000
hexagonal (Gosper)61.075

That figure is exact rather than empirical. A regular hexagon of area AA has its centres 2A/3\sqrt{2A / \sqrt{3}} apart, against A\sqrt{A} for a square of the same area, so the largest circle that fits in each grows by

2A/3A=231.0746\frac{\sqrt{2A/\sqrt{3}}}{\sqrt{A}} = \sqrt{\frac{2}{\sqrt{3}}} \approx 1.0746
Measured against the library's own cells it comes back as 1.0746.

7.5% more diameter is 15.5% more glyph area, free, for the same number of cells over the same region. It is the same fact as the classic circle-packing densities:

ηhex=π230.9069againstηsquare=π40.7854\eta_{\text{hex}} = \frac{\pi}{2\sqrt{3}} \approx 0.9069 \qquad \text{against} \qquad \eta_{\text{square}} = \frac{\pi}{4} \approx 0.7854
The share of the plane covered when equal circles are packed on each lattice.

The advice inverts for glyphs that are not round. A square glyph fills a square cell exactly, and in a hexagon the corners go to waste — so a bar chart, a spectrogram or a stacked column is better served by Hilbert or Gilbert, and a flower, a star or a pie by Gosper. Match the lattice to the shape of what sits in it.

"pointy" puts neighbours at multiples of 60°, "flat" at 30° off those. They are the same lattice turned by 30° — the basis sends axial (1, 0) to 0° and 30° respectively, at equal length. The default is "flat", the paper’s own lattice.

hagrid.gosper_curve(level, "pointy");
hagrid.gridify_gosper(data, { level, orientation: "pointy" });
hagrid.gosper_encode(p, level, size, "pointy");

Because the rotation is rigid, cell ordering and step length are unchanged and indexing round-trips exactly in either orientation. Note that switching turns the lattice underneath the data rather than carrying each point to a matching cell, so points are reassigned — that is usually the point of the option. Toggle it in the demo and watch the assignment change.

const index = hagrid.gosper_encode([x, y], level, size); // 6.6 M/s
const [cx, cy] = hagrid.gosper_decode(index, level, size); // 8.8 M/s
const cells = hagrid.gosper_curve(level, "flat", size);
const size = hagrid.gosper_size(data, level);

The implementation is the direct indexing of Uher et al.: it computes a curve index arithmetically in O(level) and never materializes anything. gosper_curve exists only for drawing the grid; indexing does not go through it.

gosper_encode returns -1 for a point outside the island. A contained point always finishes its climb on the root hexagon (0, 0), so anything that does not is out of bounds — without that check an outside point folds onto an arbitrary interior cell, very often one at the opposite end of the curve.

gosper_size scales the island so the data’s bounding circle fits inside its inscribed circle, and that only holds about a shared centre. gridify_gosper therefore centres the data on the origin and shifts the result back. If you call gosper_encode yourself, you must do the same — for ordinary data like [0,100]², centred at (50,50), most points otherwise sit off the tile entirely.

Elongated data. Gosper fits its island isotropically, so it does not preserve the data’s aspect ratio: a 1000×10 dataset comes back at aspect 2.8 rather than 100. Use Gilbert for those.

Too small a level. A point that falls outside the island throws rather than being silently folded inward. Raise whitespace.

Rene Cutura, Cristina Morariu, Zhanglin Cheng, Yunhai Wang, Daniel Weiskopf and Michael Sedlmair Hagrid: using Hilbert and Gosper curves to gridify scatterplots Journal of Visualization 25(6), 1291–1307, 2022. doi:10.1007/s12650-022-00854-7

The method this page documents.

Vojtěch Uher, Petr Gajdoš, Václav Snášel, Yu-Chi Lai and Michal Radecký Hierarchical Hexagonal Clustering and Indexing Symmetry 11(6), 731, 2019. doi:10.3390/sym11060731

The arithmetic indexing used here, which computes a curve index in O(level) without materializing the curve.

Martin Gardner Mathematical Games: In which “monster” curves force redefinition of the word “curve” Scientific American 235(6), 124–133, 1976. doi:10.1038/scientificamerican1276-124

Where the curve was first described in print, as Bill Gosper's flowsnake.