Public
Edited
Aug 1, 2023
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
sampleTileGpuOutput = {
const options = {
colorTable: sampleColorTable,
wrapX: sampleWrapX,
maxTileSize: sampleTileSize
}
const tsOptions = _.pick(options, ["wrapX", "maxTileSize"]);
const tileSlicer = new TileSlicerPadded(sampleImage, tsOptions);
const mapRect = {
x: TILE_PADDING,
y: TILE_PADDING,
width: tileSlicer.imageWidth,
height: tileSlicer.imageHeight
};
const reglProgram = getReglProgram(tileSlicer.baseTileSize, options.colorTable);
const bitmap = await tileSlicer.getTileBitmap(sampleTileRect);
const flattenedIO = await gpuProcessTile(bitmap, sampleTileRect, mapRect, reglProgram);
bitmap.close();
reglProgram.destroy();
return flattenedIO;
}
Insert cell
Insert cell
Insert cell
Insert cell
sampleTraceResult = {
const options = {
colorTable: sampleColorTable,
wrapX: sampleWrapX,
maxTileSize: sampleTileSize
}
const ts = performance.now();
const result = await gpuTraceImage(sampleImage, options);
result.runtime = performance.now() - ts;
return result;
}
Insert cell
Insert cell
proj = getMapGeoProjection(d3.geoMiller, [sampleImage.naturalWidth, sampleImage.naturalHeight], mapGeoExtent)
Insert cell
Insert cell
ColorMask.toHex(4278190335)
Insert cell
sampleTopology = {
const traceResult = sampleTraceResult;
return await buildTopology(traceResult, { projection: proj })
}
Insert cell
Insert cell
{
const result = [];
for (let tileCount of d3.range(1, 33)) {
const batchSizeRaw = tileCount / Math.ceil(tileCount / 6);
const batchSize = Math.ceil(tileCount / Math.ceil(tileCount / 6));
const batchCount = Math.ceil(tileCount / batchSize);
result.push({tileCount, batchSizeRaw, batchSize, batchCount});
}
return result;
}
Insert cell
{
const ts = performance.now();
return getReglProgram(4096);
return performance.now() - ts;
}
Insert cell
chunk([1, 2, 3, 4, 5, 6, 7], 8)
Insert cell
function chunk(array, chunkSize) {
array = [...array];
var chunks = [];
for (var i = 0; i < array.length; i += chunkSize) {
chunks.push(array.slice(i, i + chunkSize));
}
return chunks;
}
Insert cell
async function gpuTraceImage(image, options) {
let allCoordTypes = new Map(Object.keys(coordTypeEncoding.values).map(k => [k, []]));
const allPixelVectors = new Map();
allPixelVectors.getOrCreate = (key) => {
let vectors = allPixelVectors.get(key);
if (!vectors) allPixelVectors.set(key, vectors = []);
return vectors;
}

function ingestParsedResult(parsedTileResult) {
const { coordTypes, pixelVectors } = parsedTileResult;
for (let [coordType, coordNums] of parsedTileResult.coordTypes) {
allCoordTypes.get(coordType).push(...coordNums);
}
for (let [colorNum, tilePixelVectors] of parsedTileResult.pixelVectors) {
allPixelVectors.getOrCreate(colorNum).push(...tilePixelVectors);
}
}

const { colorTable, fnOnTileProcessed } = (options ?? {});
const tsOptions = _.pick(options, ["wrapX", "maxTileSize"]);
const tileSlicer = new TileSlicerPadded(image, tsOptions);
const mapRect = {
x: TILE_PADDING,
y: TILE_PADDING,
width: tileSlicer.imageWidth,
height: tileSlicer.imageHeight
};
const reglProgram = getReglProgram(tileSlicer.baseTileSize, colorTable);
for (let tileRect of tileSlicer.getTileRects()) {
// commence to tracing
const bitmap = await tileSlicer.getTileBitmap(tileRect);
const pixelIO = await gpuProcessTile(bitmap, tileRect, mapRect, reglProgram);
const parsed = await parseGpuTileResult(tileRect, pixelIO);
ingestParsedResult(parsed);
await pauseIfNeeded();
}
reglProgram.destroy();

const coordTypesReversed = new Map();
for (let [coordType, coordNums] of [...allCoordTypes]) {
coordNums.forEach(coordNum => coordTypesReversed.set(coordNum, coordType));
}
allCoordTypes = MaskMap.around(coordTypesReversed, CoordMask);
for (let [colorNum, xydNums] of [...allPixelVectors]) {
/*
const xydSet = new Set();
for (let xydNum of xydNums) {
const reverseXydNum = XydMask.getReverseNum(xydNum);
if (xydSet.has(reverseXydNum)) {
debugger;
xydSet.delete(reverseXydNum);
}
else {
xydSet.add(xydNum);
}
}
allPixelVectors.set(colorNum, MaskSet.around(xydSet, XydMask));
*/
allPixelVectors.set(colorNum, MaskSet.around(new Set(xydNums), XydMask));
}
const polygonRings = await pixelVectorsToRings(allPixelVectors, allCoordTypes, [tileSlicer.imageWidth, tileSlicer.imageHeight]);
const bbox = [0, 0, image.naturalWidth ?? image.width, image.naturalHeight ?? image.height];
return { bbox, polygonRings, coordTypes: allCoordTypes, pixelVectors: allPixelVectors };
}
Insert cell
async function gpuProcessTile(bitmap, tileRect, mapRect, reglProgram) {
const { regl, commands } = reglProgram;
const tileTexture = regl.texture({ data: bitmap });
const tileDimensions = [ bitmap.width, bitmap.height ];

const mapEdgesAbs = {
x0: mapRect.x,
y0: mapRect.y,
x1: mapRect.x + mapRect.width,
y1: mapRect.y + mapRect.height
};
const mapBboxVec = [
mapEdgesAbs.x0 - tileRect.x,
mapEdgesAbs.y0 - tileRect.y,
mapEdgesAbs.x1 - tileRect.x - 1,
mapEdgesAbs.y1 - tileRect.y - 1
]
const tileBboxVec = [
TILE_PADDING,
TILE_PADDING,
TILE_PADDING + tileRect.width - 1,
TILE_PADDING + tileRect.height - 1
]
const buffers = {};
for (let name of ["color", "neighbors", "coordType", "flattened"]) {
buffers[name] = regl.framebuffer({ depthStencil: false, shape: tileDimensions });
}


commands.replaceColors({ uSampler: tileTexture, framebuffer: buffers.color });
await glClientWaitAsync(regl, 'replaceColors');
tileTexture.destroy();
commands.findNeighbors({ uSampler: buffers.color, mapBboxVec, tileBboxVec, framebuffer: buffers.neighbors });
await glClientWaitAsync(regl, 'findNeighbors');
[mapBboxVec, tileBboxVec].forEach(v => { v[2] += 1; v[3] += 1 });
commands.classifyCoords({ uSampler: buffers.color, mapBboxVec, tileBboxVec, framebuffer: buffers.coordType });
await glClientWaitAsync(regl, 'classifyCoords');
commands.flattenLayers({
color: buffers.color,
neighbors: buffers.neighbors,
coordType: buffers.coordType,
framebuffer: buffers.flattened });
await glClientWaitAsync(regl, 'flattenLayers');
const results = [];
const rect = Bbox.dims(tileBboxVec);
const data = await readFramebufferAsync(regl, buffers.flattened, rect);
const pixelIO = new PixelIO(rect.width, rect.height, data);
Object.values(buffers).forEach(r => r.destroy());
return pixelIO;
/*
const regions = []
Bbox.split(Bbox.dims(tileBboxVec), 1, 1).forEach((rect,i) => {
debugger;
regions.push({ rect, array: dataArrays[i] });
});

// const data = regl.read({ framebuffer: buffers.flattened, ...readRect });
debugger;
const results = [];
for (let { rect, array } of regions) {
const data = readFast(regl, buffers.flattened, rect, array);
const pixelIO = new PixelIO(rect.width, rect.height, data);
results.push({ rect, pixelIO });
}
Object.values(buffers).forEach(r => r.destroy());
return results;
*/
}
Insert cell
Bbox.dims([0,0,100,100]);
Insert cell
async function parseGpuTileResult(rect, flattenedIO) {

// Initialize result objects
const coordTypes = new Map(Object.keys(coordTypeEncoding.values).map(k => [k, []]));
const pixelVectors = new Map();
pixelVectors.getOrCreate = (key) => {
let xydVectors = pixelVectors.get(key);
if (!xydVectors) pixelVectors.set(key, xydVectors = []);
return xydVectors;
}

// Read flattenedIO
const { width, height } = rect;
let loopCount = 0;
for (let { colorNum: flattenedColorNum, x: tileX, y: tileY } of flattenedIO.getNonZeroPixels()) {
const x = tileX + rect.x;
const y = tileY + rect.y;
const pixelData = GpuColorMask.toObject(flattenedColorNum);

// coordType
if (pixelData.coordType) {
coordTypes.get(pixelData.coordType).push(CoordMask.fromArray([x,y]));
}

// pixelVectors
if (pixelData.colorOpacity) {
// Fork of http://defghi1977.html.xdomain.jp/tech/dotrace/dotrace.htm
const { n, e, s, w } = pixelData.neighbors;
if (!n && !e && !s && !w) continue;

const vectors = pixelVectors.getOrCreate(pixelData.colorNum);
// pixelData.n means it has a neighbor to the north, so we want to draw the eastward-pointing vector, etc.
if (n) vectors.push(XydMask.fromArray([x, y, XydMask.direcs.e]));
if (e) vectors.push(XydMask.fromArray([x+1, y, XydMask.direcs.s]));
if (s) vectors.push(XydMask.fromArray([x+1, y+1, XydMask.direcs.w]));
if (w) vectors.push(XydMask.fromArray([x, y+1, XydMask.direcs.n]));
}

// if (loopCount++ % 10000) await pauseIfNeeded();
}

return { pixelVectors, coordTypes };
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function getPassThruCommand(regl) {
const command = {
frag: passThruFragShader,
uniforms: { uSampler: regl.prop("uSampler") },
framebuffer: regl.prop("framebuffer")
};
return regl(Object.assign({}, reglCommandDefaults, command));
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function getDrawEdgesCommand(regl) {
let command = {
frag: drawEdgesFragShader,
uniforms: {
uSampler: regl.prop("uSampler"),
mapBboxVec: regl.prop("mapBboxVec"),
tileBboxVec: regl.prop("tileBboxVec")
},
framebuffer: regl.prop("framebuffer")
};
return regl(Object.assign({}, reglCommandDefaults, command));
}
Insert cell
async function glClientWaitAsync(glOrRegl, label) {
label ??= "glClientWaitAsync";
const gl = glOrRegl._gl ?? glOrRegl;
const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
const ts = performance.now();
gl.flush();

await new Promise((resolve, reject) => {
function test() {
const res = gl.clientWaitSync(sync, gl.SYNC_FLUSH_COMMANDS_BIT, 0);
if (res === gl.WAIT_FAILED) {
reject();
return;
}
if (res === gl.TIMEOUT_EXPIRED) {
setTimeout(test, 10);
return;
}
resolve();
}
test();
});

gl.deleteSync(sync);
console.log(label, performance.now() - ts);
}
Insert cell
async function readFramebufferAsync(regl, framebuffer, rect) {
const gl = regl._gl;
const x = rect.x ?? 0;
const y = rect.y ?? 0;
const width = rect.width ?? framebuffer.width;
const height = rect.height ?? framebuffer.height;
const data = new Uint8Array(width * height * 4);

async function readAsync() {
// Define and start filling PBO
const pbo = gl.createBuffer();
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pbo);
gl.bufferData(gl.PIXEL_PACK_BUFFER, data.byteLength, gl.STREAM_READ);
gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, 0);
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
// Wait for PBO ready
await glClientWaitAsync(gl, 'readAsync');
// read PBO
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pbo);
gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, data);
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);

// clean up and return
gl.deleteBuffer(pbo);
return data;
}

let promise;
{
let fnResolve, fnReject;
promise = new Promise((resolve, reject) => { fnResolve = resolve; fnReject = reject });
promise.resolve = fnResolve;
promise.reject = fnReject;
}

framebuffer.use(function() {
readAsync().then(r => {
regl._refresh();
promise.resolve(data);
}).catch(e => {
try {
regl._refresh();
}
catch (re) {
console.log(re);
}
promise.reject(e);
});
})

await promise;
return data;
}
Insert cell
async function _readAsync(regl, framebuffer, rect, data) {
const gl = regl._gl;
const format = gl.RGBA;
const type = gl.UNSIGNED_BYTE;
rect ??= {};
const x = rect.x ?? 0;
const y = rect.y ?? 0;
const width = rect.width ?? framebuffer.width;
const height = rect.height ?? framebuffer.height;
data ??= new Uint8Array(width * height * 4);

// gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, data);
// Modified from https://github.com/KhronosGroup/WebGL/blob/main/sdk/tests/conformance2/reading/read-pixels-from-rgb8-into-pbo-bug.html
framebuffer.use(async function() {
var pbo = gl.createBuffer();
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pbo);
gl.bufferData(gl.PIXEL_PACK_BUFFER, width * height * 4, gl.STREAM_READ);
gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, 0);

const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
gl.flush();
await clientWaitAsync(gl, sync, 0, 10);
gl.deleteSync(sync);

gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, data);
gl.deleteBuffer(pbo);
});
regl._refresh();
return data;





function clientWaitAsync(sync) {
return new Promise((resolve, reject) => {
function test() {
const res = gl.clientWaitSync(sync, gl.SYNC_FLUSH_COMMANDS_BIT, 0);
if (res === gl.WAIT_FAILED) {
reject();
return;
}
if (res === gl.TIMEOUT_EXPIRED) {
setTimeout(test, 10);
return;
}
resolve();
}
test();
});
}
async function getBufferSubDataAsync(target, buffer) {
const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
gl.flush();
await clientWaitAsync(gl, sync, 0, 10);
gl.deleteSync(sync);
gl.bindBuffer(target, buffer);
gl.getBufferSubData(target, srcByteOffset, dstBuffer, dstOffset, length);
gl.bindBuffer(target, null);
return dest;
}
}
/*
function clientWaitAsync(gl, sync, flags, interval_ms) {
return new Promise((resolve, reject) => {
function test() {
const res = gl.clientWaitSync(sync, flags, 0);
if (res === gl.WAIT_FAILED) {
reject();
return;
}
if (res === gl.TIMEOUT_EXPIRED) {
setTimeout(test, interval_ms);
return;
}
resolve();
}
test();
});
}

async function getBufferSubDataAsync(
gl,
target,
buffer,
srcByteOffset,
dstBuffer
) {
const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
gl.flush();

await clientWaitAsync(gl, sync, 0, 10);
gl.deleteSync(sync);

gl.bindBuffer(target, buffer);
gl.getBufferSubData(target, srcByteOffset, dstBuffer, dstOffset, length);
gl.bindBuffer(target, null);

return dest;
}

async function readPixelsAsync(gl, x, y, w, h, format, type, dest) {
const buf = gl.createBuffer();
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, buf);
gl.bufferData(gl.PIXEL_PACK_BUFFER, dest.byteLength, gl.STREAM_READ);
gl.readPixels(x, y, w, h, format, type, 0);
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);

await getBufferSubDataAsync(gl, gl.PIXEL_PACK_BUFFER, buf, 0, dest);

gl.deleteBuffer(buf);
return dest;
}
*/
Insert cell
function readFast(regl, framebuffer, rect, data) {
const gl = regl._gl;
const format = gl.RGBA;
const type = gl.UNSIGNED_BYTE;
rect ??= {};
const x = rect.x ?? 0;
const y = rect.y ?? 0;
const width = rect.width ?? framebuffer.width;
const height = rect.height ?? framebuffer.height;
data ??= new Uint8Array(width * height * 4);

// gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, data);
// Modified from https://github.com/KhronosGroup/WebGL/blob/main/sdk/tests/conformance2/reading/read-pixels-from-rgb8-into-pbo-bug.html
framebuffer.use(function() {
var pbo = gl.createBuffer();
gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pbo);
gl.bufferData(gl.PIXEL_PACK_BUFFER, width * height * 4, gl.STREAM_READ);
gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, 0);
gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, data);
});
regl._refresh();
return data;
}
Insert cell
Insert cell
Insert cell
Insert cell
parseGpuTileResult(sampleTileGpuResult)
Insert cell
async function _gpuProcessImage(image, options) {
const allCoordTypes = new Map();
const allPixelVectors = new Map();
allPixelVectors.getOrCreate = (key) => {
let maskSet = allPixelVectors.get(key);
if (!maskSet) allPixelVectors.set(key, maskSet = new Set());
return maskSet;
}

function ingestParsedResult(parsedTileResult) {
const { coordTypes, pixelVectors } = parsedTileResult;
for (let [coordNum, coordType] of parsedTileResult.coordTypes) {
allCoordTypes.set(coordNum, coordType)
}
for (let [colorNum, tilePixelVectors] of parsedTileResult.pixelVectors) {
const allVectors = allPixelVectors.getOrCreate(colorNum);
tilePixelVectors.forEach(v => allVectors.add(v));
}
}

const { colorTable, wrapX, maxTileSize, fnOnTileProcessed } = (options ?? {});

const tileSlicer = new TileSlicerPadded(image, { wrapX, maxTileSize });
const reglProgram = getReglProgram(tileSlicer.baseTileSize, colorTable);
for (let rect of tileSlicer.getTileRects()) {
const bitmap = await tileSlicer.getTileBitmap(rect);
const gpuTileResult = await gpuProcessTile(bitmap, rect, reglProgram, { fnOnTileProcessed, wrapX });
bitmap.close();
const parsedTileResult = parseGpuTileResult(gpuTileResult);
ingestParsedResult(parsedTileResult);
}

return { allCoordTypes, allPixelVectors };
/*

reglProgram.destroy();
const polygonRings = await pixelVectorsToRings(pixelVectors, coordClasses, [tileSlicer.imageWidth, tileSlicer.imageHeight]);
const bbox = [0, 0, image.naturalWidth ?? image.width, image.naturalHeight ?? image.height];
return { bbox, polygonRings, coordClasses, wrapX: !!wrapX };
*/
}
Insert cell
Insert cell
getMapEdgeCoords({n: true, e: true, s: true, w: true}, { x: 5, y: 5, width: 10, height: 10 })
Insert cell
function getMapEdgeCoords(mapEdges, rect) {
const mapEdgePixels = {};
const x0 = rect.x;
const x1 = rect.x + rect.width;
const y0 = rect.y;
const y1 = rect.y + rect.height;

const xValues = d3.range(x0, x1);
const yValues = d3.range(y0, y1);
if (mapEdges.n) mapEdgePixels.n = d3.cross(xValues, [x0]);
if (mapEdges.e) mapEdgePixels.e = d3.cross([x1-1], yValues);
if (mapEdges.s) mapEdgePixels.s = d3.cross(xValues, [y1-1]);
if (mapEdges.w) mapEdgePixels.w = d3.cross([y0], yValues);

return mapEdgePixels;
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{

const coordTypes = new MaskMap(CoordMask);
coordTypes.set([1,1], "junction");
function testCollinear([ax, ay], [bx, by], [cx, cy]) {
return (bx-ax) * (cy-ay) == (cx-ax) * (by-ay);
}
function simplifyArc(arc, coordTypes) {
const simplifiedArc = [arc[0]];
for (let i = 1; i < arc.length - 2; i++) {
const coord = arc[i];
const prevCoord = arc[i-1];
const nextCoord = arc[i+1];
if (coordTypes.get(coord) == "junction" || !testCollinear(coord, prevCoord, nextCoord)) {
simplifiedArc.push(coord);
}
}
simplifiedArc.push(arc.at(-1));
return simplifiedArc;
}

const coords = [[0,0], [1,1], [2,2], [3,3], [2,3], [2,4], [3,4]];
return simplifyArc(coords, coordTypes);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
li = new LoadIndicator(sampleImage);
Insert cell
li.canvas
Insert cell
Insert cell
Insert cell
Insert cell
{
const sourceColorNum = ColorMask.fromArray([10, 20, 30, 83]);
const obj = GpuColorMask.toObject(sourceColorNum);
const obj2 = _.cloneDeep(obj);

obj2.colorNum = ColorMask.fromArray([...ColorMask.toArray(obj2.colorNum).slice(0,3), 6]);
obj2.neighbors.e = true;
obj2.coordType = "junction";
const reverse = GpuColorMask.fromObject(obj2);
const rereverse = GpuColorMask.toObject(reverse);
return { sourceColorNum, obj, reverse, rereverse, finalColor: ColorMask.toObject(rereverse.colorNum) }
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Bbox.split({x: 5, y: 5, width: 105, height: 110}, 2, 30)
Insert cell
Bbox.fromDims({x: 5, y: 0, width: 105, height: 110})
Insert cell
class Bbox {
static isEqual(bbox1, bbox2) {
if (bbox1 == bbox2) return true;
return (bbox1?.length == 4 &&
bbox1?.length == 4 &&
bbox1[0] == bbox2[0] &&
bbox1[1] == bbox2[1] &&
bbox1[2] == bbox2[2] &&
bbox1[3] == bbox2[3]);
}
static contains(bbox1, bbox2) {
if (bbox1 == bbox2) return false;
return (bbox1?.length === bbox2?.length &&
bbox1[0] < bbox2[0] &&
bbox1[1] < bbox2[1] &&
bbox1[2] > bbox2[2] &&
bbox1[3] > bbox2[3]);
}
static covers(bbox1, bbox2) {
if (bbox1 == bbox2) return true;
return (bbox1?.length === bbox2?.length &&
bbox1[0] <= bbox2[0] &&
bbox1[1] <= bbox2[1] &&
bbox1[2] >= bbox2[2] &&
bbox1[3] >= bbox2[3]);
}
static containsPoint(bbox, point) {
return Bbox.contains(bbox, [...point, ...point]);
}
static coversPoint(bbox, point) {
return Bbox.covers(bbox, [...point, ...point]);
}
static isPointOnEdge(bbox, point) {
return bbox[0] == point[0] ||
bbox[1] == point[1] ||
bbox[2] == point[0] ||
bbox[3] == point[1];
}
static width(bbox) {
return bbox[2] - bbox[0];
}
static height(bbox) {
return bbox[3] - bbox[1];
}
static dims(bbox) {
return { x: bbox[0], y: bbox[1], width: Bbox.width(bbox), height: Bbox.height(bbox) };
}
static fromDims(dims) {
return [
dims.x,
dims.y,
dims.width + dims.x - 1,
dims.height + dims.y - 1
];
}
static fromPoints(points) {
let x0 = Infinity, y0 = Infinity, x1 = -Infinity, y1 = -Infinity;
for (let [x, y] of points) {
if (x < x0) x0 = x;
if (x > x1) x1 = x;
if (y < y0) y0 = y;
if (y > y1) y1 = y;
}
return [x0, y0, x1, y1];
}
static area(bbox) {
return Bbox.width(bbox) * Bbox.height(bbox);
}
static buffer(bbox, amount) {
return [bbox[0] - amount, bbox[1] - amount, bbox[2] + amount, bbox[3] + amount];
}
static split(bbox, stripCount, minStripSize) {
stripCount ??= Math.max(1, navigator.hardwareConcurrency - 1);
minStripSize ??= 512;
debugger;
const splitBboxes = []
if (bbox.width > bbox.height) {
while (stripCount > 1 && bbox.width / stripCount < minStripSize) stripCount--;
const [bboxX0, y0, bboxX1, y1] = Bbox.fromDims(bbox);
const stripSize = Math.max(minStripSize, Math.ceil(bbox.width / stripCount));
for (let x0 of d3.range(bboxX0, bboxX1, stripSize)) {
const split = {...bbox};
const x1 = Math.min(x0 + stripSize - 1, bboxX1);
Object.assign(split, Bbox.dims([x0, y0, x1, y1]));
splitBboxes.push(split);
}
}
else {
while (stripCount > 1 && bbox.height / stripCount < minStripSize) stripCount--;
const [x0, bboxY0, x1, bboxY1] = Bbox.fromDims(bbox);
const stripSize = Math.max(minStripSize, Math.ceil(bbox.height / stripCount));
for (let y0 of d3.range(bboxY0, bboxY1, stripSize)) {
const split = {...bbox};
const y1 = Math.min(y0 + stripSize, bboxY1);
Object.assign(split, Bbox.dims([x0, y0, x1, y1]));
splitBboxes.push(split);
}
}
return splitBboxes;
}
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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