Public
Edited
Sep 22, 2023
6 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
dataPoints = 5000;
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
columns = 20;
Insert cell
rows = 10;
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
K = dataPoints * 0.1;
Insert cell
powerParameter = 16;
Insert cell
function getWi_(Di, P) {
const dist = distance(Di, P);
return 1 / (dist ** powerParameter);
}
Insert cell
function getShepardsInterpolatedValue(P) {
const distancesToP = getDistancesToP(P);
if (distancesToP[0].distanceP === 0) {
return distancesToP[0].point.z;
}
const cPrime = getCPrime(P);
const numerator = cPrime
.map(Di => {
const wi = getWi(Di, P);
const deltaZi = getDeltaZi(Di, P);
return wi * (Di.z + deltaZi);
})
.reduce(sum);
const denominator = cPrime
.map(Di => {
return getWi(Di, P);
})
.reduce(sum);
const shepardsInterpolatedValue = denominator === 0 ? 0 : numerator / denominator;
return shepardsInterpolatedValue;
}
Insert cell
Insert cell
Insert cell
function distance(P, Q) {
return Math.sqrt((P.x - Q.x) ** 2 + (P.y - Q.y) ** 2);
}
Insert cell
function getDistancesToP(P) {
// distance of all data points to center point of cell
// TODO: this is horribly inefficient
const distancesToP = randomData
.map(Di => {
return {
point: Di,
distanceP: distance(P, Di)
};
})
.sort((a, b) => a.distanceP - b.distanceP);
return distancesToP;
}
Insert cell
C_PRIME = new Map();
Insert cell
function getCPrime(P) {
if (C_PRIME.has(P)) {
return C_PRIME.get(P);
}

const distancesToP = getDistancesToP(P);
// all points within radius distance of the center point of a cell
const cP = distancesToP
.filter(Di => Di.distanceP <= radius);
const cPP = [];
if (cP.length <= 4) {
// return the four nearest neighbors
cPP.push(...distancesToP.slice(0, 4).map(d => d.point));
} else if (cP.length <= 10) {
// return all points within the radius
cPP.push(...cP.map(d => d.point));
} else {
// return the ten nearest neighbors
cPP.push(...distancesToP.slice(0, 10).map(d => d.point));
}
C_PRIME.set(P, cPP);
return cPP;
}
Insert cell
R_PRIME = new Map();
Insert cell
function getRPrime(P) {
if (R_PRIME.has(P)) {
return R_PRIME.get(P);
}
const distancesToP = getDistancesToP(P);
// all points within radius distance of the center point of a cell
const cP = distancesToP
.filter(Di => Di.distanceP <= radius);
let rPrime = -1;
if (cP.length <= 4) {
rPrime = distancesToP[4].distanceP;
} else if (cP.length <= 10) {
rPrime = radius;
} else {
rPrime = distancesToP[10].distanceP;
}
R_PRIME.set(P, rPrime);
return rPrime;
}
Insert cell
SI_P = new Map();
Insert cell
function getSi(Di, P) {
let s = -1;
const d = distance(Di, P);
const rPrime = getRPrime(P);
if (d <= rPrime / 3) {
s = 1/d;
} else if (d <= rPrime) {
s = 27 / (4 * rPrime)
} else {
s = 0;
}
return s;
}
Insert cell
function getTi(Di, P) {
const cPPrime = getCPrime(P);
const numerator = cPPrime
.map(Dj => {
const sj = getSi(Dj, P);
const di = distance(P, Di);
const dj = distance(P, Dj);
const cos = ((P.x - Di.x)*(P.x - Dj.x) + (P.y - Di.y)*(P.y - Dj.y)) / di*dj;

return sj*(1 - cos);
})
.reduce(sum);
const denominator = cPPrime
.map(Dj => getSi(Dj, P))
.reduce(sum);
const ti = denominator === 0 ? 0 : numerator / denominator;
return ti;
}
Insert cell
W = new Map();
Insert cell
function getWi(Di, P) {
if (W.has(P)) {
const map = W.get(P);
if (map.has(Di)) {
return map.get(Di);
}
} else {
W.set(P, new Map());
}
const si = getSi(Di, P);
const ti = getTi(Di, P);
const wi = ti ** 2 * (1 + ti);
if (!W.has(P)) {
W.set(P, new Map());
}
const map = W.get(P);
map.set(Di, wi);
return wi;
}
Insert cell
C_DOUBLE_PRIME = new Map();
Insert cell
function getCDoublePrime(Di, P) {
if (C_DOUBLE_PRIME.has(P)) {
const map = C_DOUBLE_PRIME.get(P);
if (map.has(Di)) {
return map.get(Di);
}
} else {
C_DOUBLE_PRIME.set(P, new Map());
}
// use copy of cPrime (which is cached in C_PRIME), because splice is used later that would destroy cPrime
const cPrime = getCPrime(P).map(d => d);
const indexInCPrime = cPrime.indexOf(Di);
if (indexInCPrime > -1) {
cPrime.splice(indexInCPrime, 1);
}
return cPrime;
}
Insert cell
sum = (a, b) => a + b;
Insert cell
function getAi(Di, P) {
const cDoublePrime = getCDoublePrime(Di, P);
const numerator = cDoublePrime
.map(Dj => {
const wj = getWi(Dj, P);

return (Dj.z - Di.z)*(Dj.x - Di.x) / (distance(Dj, Di)**2);
})
.reduce((newValue, oldValue) => newValue + oldValue);
const denominator = cDoublePrime
.map(Dj => {
return getWi(Dj, P);
})
.reduce(sum);
const Ai = denominator === 0 ? 0 : numerator / denominator;
return Ai;
}
Insert cell
function getBi(Di, P) {
const cDoublePrime = getCDoublePrime(Di, P);
const numerator = cDoublePrime
.map(Dj => {
const wj = getWi(Dj, P);

return (Dj.z - Di.z)*(Dj.y - Di.y) / (distance(Dj, Di)**2);
})
.reduce(sum);
const denominator = cDoublePrime
.map(Dj => {
return getWi(Dj, P);
})
.reduce(sum);
const Ai = denominator === 0 ? 0 : numerator / denominator;
return Ai;
}
Insert cell
V = new Map();
Insert cell
function getV(P) {
if (V.has(P)) {
return V.get(P);
}
const cPrime = getCPrime(P);
const z = cPrime.map(d => d.z);
const maxZ = Math.max(...z);
const minZ = Math.min(...z);
const ASquaredPlusBSquared = cPrime.map(Di => {
const ASquared = getAi(Di, P) ** 2;
const BSquared = getBi(Di, P) ** 2;

return ASquared + BSquared;
});
const maxASquaredPlusBSquared = Math.max(...ASquaredPlusBSquared);
const v = maxASquaredPlusBSquared === 0
? 0
: 0.1*(maxZ - minZ) / Math.sqrt(maxASquaredPlusBSquared);
V.set(P, v);
return v;
}
Insert cell
function getDeltaZi(Di, P) {
const Ai = getAi(Di, P);
const Bi = getBi(Di, P);
const v = getV(P);
const di = distance(P, Di);
const a = (Ai*(P.x - Di.x) + Bi*(P.y - Di.y));
const b = v + di === 0 ? 0 : v / (v + di);
return a * b;
}
Insert cell
show()
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
height = 500;
Insert cell
margin = 10;
Insert cell
dotRadius = 3;
Insert cell
resolution = 4;
Insert cell
perlinOctaves = 8;
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function renderGrid(canvas) {
const grid = canvas.select("g.grid")
.attr("transform", `translate(${margin}, ${margin})`);
grid.selectAll("*").remove();
// generates a rows x columns matrix, using values from a Perlin noise field.
const linearizedMatrix = linearizeMatrix(matrix);
const cells = grid.selectAll("g.cell").data(linearizedMatrix).enter()
.append("g")
.attr("class", "cell")
.attr("transform", d => `translate(${d.col * spacing}, ${d.row * spacing})`);
const boundary = cells.append("rect")
.attr("class", "boundary")
.attr("width", spacing)
.attr("height", spacing)
.attr("stroke", "transparent")
.attr("fill", d => color(getShepardsInterpolatedValue(d.value)))
.on("click", (event, d) => {
});
return canvas;
}
Insert cell
/**
* Draws the dots that represent the Perlin noise from the matrix.
*/
function renderDots(canvas) {
const points = canvas.select("g.points");
points.selectAll("*").remove();
const point = points.selectAll("circle.point").data(randomData).enter()
.append("circle")
.attr("class", "point")
.attr("r", dotRadius)
.attr("cx", d => scaleX(d.dx))
.attr("cy", d => scaleY(d.dy))
.attr("fill", d => color(d.z))
.attr("stroke", "#ccc");
point.append("title").text(d => d.value);
return 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

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