Published
Edited
Feb 8, 2022
1 fork
8 stars
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
ballApproximationStroke = {
const [w, h] = [piece.width * zoom, piece.height * zoom];
const ctx = DOM.context2d(w, h, 1);
ctx.canvas.style.cssText = `
image-rendering: crisp-edges;
image-rendering: pixelated;
-webkit-image-rendering: pixelated;
-webkit-image-rendering: crisp-edges;
`;
let balls = [...pieceTree.approximation(20)].map((t) => {
let { x, y, r } = t.entry;
return new Ball(x * zoom, y * zoom, r * zoom);
});
ctx.fillStyle = `#00000000`;
drawBalls(ctx, balls);
return ctx.canvas;
}
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
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
margin = 20
Insert cell
piece = letterImage
Insert cell
pieceImageData = binarizeImageData(getImageData(piece), 128)
Insert cell
pieceBalls = {
if (ballSet == "SDT+filter") return ballCoverNew(pieceImageData, epsilon);
if (ballSet == "DMA") return ballCover(pieceImageData);
const pixelRadius = Math.SQRT2 / 2;
return piecePoints.map(({ x, y }) => new Ball(x, y, pixelRadius));
}
Insert cell
function drawBalls(ctx, balls) {
for (let { x, y, r } of balls) {
ctx.beginPath();
ctx.arc(x, y, Math.max(0.5, r), 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
}
}
Insert cell
function drawPoints(ctx, points) {
ctx.strokeStyle = "black";
for (let { x, y } of points) {
ctx.strokeRect(x, y, 0.001, 0.001);
}
}
Insert cell
function drawBoxes(ctx, boxes) {
ctx.strokeStyle = "black";
for (let b of boxes) {
let size = b.size();
ctx.fillRect(b.min.x, b.min.y, size.x, size.y);
ctx.strokeRect(b.min.x, b.min.y, size.x + 0.01, size.y + 0.01);
}
}
Insert cell
pieceTree = {
let tree = bottomUp(pieceBalls);
return tree;
}
Insert cell
pieceTreeHeight = pieceTree.height()
Insert cell
sampleDistances = btSamples.concat(raSamples)
Insert cell
function sample(tree, w, h, nx = 30, ny = 30) {
const dx = (w * 3) / (nx - 1),
dy = (h * 3) / (ny - 1);
const x0 = -w,
y0 = -h;
let [treeType, dist, stepCount] =
tree instanceof RA
? ["Rect", (p) => tree.distance(p), () => tree.distanceSteps]
: ["Ball", (p) => tree.closest(p).dist(p), () => tree.closestSteps];
let samples = [];
for (let ix = 0; ix < nx; ix++) {
let x = x0 + ix * dx;
for (let iy = 0; iy < ny; iy++) {
let y = y0 + iy * dy;
let p = Vec(x, y);
samples.push({ p, dist: dist(p), steps: stepCount(), tree: treeType });
}
}
return samples;
}
Insert cell
raSamples = sample(pieceRaCutTree, piece.width, piece.height)
Insert cell
btSamples = sample(pieceTree, piece.width, piece.height)
Insert cell
pieceRaTree = new RA(piecePoints)
Insert cell
pieceRaTreeHeight = pieceRaTree.height()
Insert cell
pieceRaCutTree = pieceRaTree.cutTree(raTreeCut).approxTree(nodeFillRate)
Insert cell
piecePoints = imageDataPoints(pieceImageData)
Insert cell
groundPoints = imageDataPoints(getImageData(zoomedShape))
Insert cell
Insert cell
Insert cell
{
textSettings, zoom, ballSet, epsilon, raTreeCut;
mutable chartsData = null;
return md`*This cell resets the \`chartsData\` object whenever parameters change *`;
}
Insert cell
//
// This function encapsulates all the computation in a single function that depends only
// on the input parameters. To speed up computation, an object with some of the values of
// previous run of the pipeline can be provided
//
function pipeline(inputs, prevOutputs = {}) {
let {
textSettings,
zoom,
ballSet,
epsilon,
raTreeCut,
nodeFillRate
} = inputs;
let {
piece,
pieceImageData,
piecePoints,
zoomedShape,
pieceBalls,
pieceTree,
pieceRaTree,
pieceRaCutTree,
ballApproximation,
ballApproximationErrorImage,
ballApproximationErrorImagePoints,
pieceRects,
rectApproximation,
rectApproximationErrorImage,
rectApproximationErrorImagePoints,
groundPoints
} = prevOutputs;
piece =
piece ||
textImage(
textSettings.text,
textSettings.fontSize,
textSettings.fontFamily,
textSettings.fontVariant
);
pieceImageData =
pieceImageData || binarizeImageData(getImageData(piece), 128);
zoomedShape =
zoomedShape ||
textImage(
textSettings.text,
textSettings.fontSize,
textSettings.fontFamily,
textSettings.fontVariant,
zoom
);
groundPoints = groundPoints || imageDataPoints(getImageData(zoomedShape));
piecePoints = piecePoints || imageDataPoints(pieceImageData);
pieceBalls =
pieceBalls ||
(function () {
if (ballSet == "SDT+filter") return ballCoverNew(pieceImageData, epsilon);
if (ballSet == "DMA") return ballCover(pieceImageData);
const pixelRadius = Math.SQRT2 / 2;
return piecePoints.map(({ x, y }) => new Ball(x, y, pixelRadius));
})();
// pieceTree =
// pieceTree ||
// (function () {
// let pieceTree = bottomUp(pieceBalls);
// pieceTree.computeEnclosingCircles();
// return pieceTree;
// })();
pieceRaTree = pieceRaTree || new RA(piecePoints);
pieceRaCutTree =
pieceRaCutTree || pieceRaTree.cutTree(raTreeCut).approxTree(nodeFillRate);
ballApproximation =
ballApproximation ||
(function () {
const [w, h] = [piece.width * zoom, piece.height * zoom];
const ctx = DOM.context2d(w, h, 1);
ctx.canvas.style.cssText = `
image-rendering: crisp-edges;
image-rendering: pixelated;
-webkit-image-rendering: pixelated;
-webkit-image-rendering: crisp-edges;`;
let balls = pieceBalls.map(
({ x, y, r }) => new Ball(x * zoom, y * zoom, r * zoom)
);
drawBalls(ctx, balls);
return ctx.canvas;
})();
ballApproximationErrorImage =
ballApproximationErrorImage || errorImage(zoomedShape, ballApproximation);
ballApproximationErrorImagePoints =
ballApproximationErrorImagePoints ||
imageDataPoints(getImageData(ballApproximationErrorImage));

pieceRects =
pieceRects || [...pieceRaCutTree.approximation(20)].map((t) => t.box);
rectApproximation =
rectApproximation ||
(function () {
const [w, h] = [piece.width * zoom, piece.height * zoom];
const ctx = DOM.context2d(w, h, 1);
ctx.canvas.style.cssText = `
image-rendering: crisp-edges;
image-rendering: pixelated;
-webkit-image-rendering: pixelated;
-webkit-image-rendering: crisp-edges;`;
let boxes = pieceRects;
let raster = rasterizeBoxes(boxes, w, h, zoom);
ctx.putImageData(raster, 0, 0);
return ctx.canvas;
})();
rectApproximationErrorImage =
rectApproximationErrorImage || errorImage(zoomedShape, rectApproximation);
rectApproximationErrorImagePoints =
rectApproximationErrorImagePoints ||
imageDataPoints(getImageData(rectApproximationErrorImage));
return {
piece,
pieceImageData,
piecePoints,
zoomedShape,
pieceBalls,
pieceTree,
pieceRaTree,
pieceRaCutTree,
ballApproximation,
ballApproximationErrorImage,
ballApproximationErrorImagePoints,
pieceRects,
rectApproximation,
rectApproximationErrorImage,
rectApproximationErrorImagePoints,
groundPoints
};
}
Insert cell
Insert cell
import { Vec, Curve, MBR } from "@esperanc/2d-geometry-utils"
Insert cell
import {
RA,
textImage,
errorImage,
rasterizeBoxes,
imageDataPoints
} from "a8b35859f33908c1"
Insert cell
import {
ballCover,
ballCoverNew,
binarizeImageData,
getImageData
} from "9761ebfa33a5220f"
Insert cell
import {
Ball,
BT,
kdconstruct,
bottomUp
} from "@esperanc/omohundro-balltree-construction-algorithms"
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