Skip to content

Commit eae7db6

Browse files
committed
barycentric interpolate and extrapolate
1 parent bb01d6c commit eae7db6

File tree

2 files changed

+93
-38
lines changed

2 files changed

+93
-38
lines changed

test/plots/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ export {default as sparseCell} from "./sparse-cell.js";
238238
export {
239239
default as spatialInterpolationNone,
240240
barycentric as spatialInterpolationBarycentric,
241+
barycentricExtra as spatialInterpolationBarycentricExtra,
241242
voronoi as spatialInterpolationVoronoi
242243
} from "./spatial-interpolation.js";
243244
export {default as stackedBar} from "./stacked-bar.js";

test/plots/spatial-interpolation.js

Lines changed: 92 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,11 @@ export default async function spatial(interpolate) {
4040
}
4141

4242
export async function barycentric() {
43-
return spatial(interpolateBarycentric);
43+
return spatial(interpolateBarycentric(false));
44+
}
45+
46+
export async function barycentricExtra() {
47+
return spatial(interpolateBarycentric(true));
4448
}
4549

4650
export async function voronoi() {
@@ -65,48 +69,98 @@ function interpolateVoronoi(index, canvas, {X, Y, R, G, B, FO}, {r, g, b, a}) {
6569
}
6670
}
6771

68-
function interpolateBarycentric(index, canvas, {X, Y, R, G, B, FO}, {r, g, b, a}) {
69-
const {width, height} = canvas;
70-
const context2d = canvas.getContext("2d");
71-
const image = context2d.createImageData(width, height);
72-
const imageData = image.data;
72+
function interpolateBarycentric(extrapolate = true) {
73+
return (index, canvas, {X, Y, R, G, B, FO}, {r, g, b, a}) => {
74+
const {width, height} = canvas;
75+
const context2d = canvas.getContext("2d");
76+
const image = context2d.createImageData(width, height);
77+
const imageData = image.data;
7378

74-
const delaunay = d3.Delaunay.from(
75-
index,
76-
(i) => X[i],
77-
(i) => Y[i]
78-
);
79-
const {points, triangles} = delaunay;
79+
// renumber/reindex everything, because we're going to add points if extrapolate is true
80+
X = Array.from(index, (i) => X[i]);
81+
Y = Array.from(index, (i) => Y[i]);
82+
R = R && Array.from(index, (i) => R[i]);
83+
G = G && Array.from(index, (i) => G[i]);
84+
B = B && Array.from(index, (i) => B[i]);
85+
FO = FO && Array.from(index, (i) => FO[i]);
86+
index = d3.range(index.length);
8087

81-
// Interpolate the interior of all triangles with barycentric coordinates
82-
for (let i = 0; i < triangles.length; i += 3) {
83-
const T = triangles.subarray(i, i + 3);
84-
let [ia, ib, ic] = Array.from(T, (i) => index[i]);
88+
// to extrapolate, we need to fill the rectangle; pad the perimeter with vertices all around.
89+
if (extrapolate) {
90+
let i = 1 + index.length;
91+
const addPoint = (x, y) => {
92+
(X[i] = x), (Y[i] = y), (R[i] = NaN), index.push(i++);
93+
};
94+
for (let k = 0; k < 1.01; k += 0.01) {
95+
addPoint(k * width, -1);
96+
addPoint((1 - k) * width, height + 1);
97+
addPoint(-1, k * height);
98+
addPoint(width + 1, (1 - k) * height);
99+
}
100+
}
85101

86-
const [Ax, Bx, Cx] = Array.from(T, (i) => points[2 * i]);
87-
const [Ay, By, Cy] = Array.from(T, (i) => points[2 * i + 1]);
88-
const [x0, x1] = d3.extent([Ax, Bx, Cx]);
89-
const [y0, y1] = d3.extent([Ay, By, Cy]);
102+
const delaunay = d3.Delaunay.from(
103+
index,
104+
(i) => X[i],
105+
(i) => Y[i]
106+
);
107+
const {points, triangles} = delaunay;
108+
109+
// two rounds of extrapolation are necessary; the first fills the triangles
110+
// which have one unknown dot, the second fills the remaining triangles
111+
if (extrapolate) {
112+
for (let c = 0; c < 2; c++) {
113+
for (let i = 0; i < triangles.length; i += 3) {
114+
const [a, b, c] = triangles.subarray(i, i + 3);
115+
if (isNaN(R[index[a]])) {
116+
R[index[a]] = (R[index[b]] + R[index[c]]) / 2;
117+
G[index[a]] = (G[index[b]] + G[index[c]]) / 2;
118+
B[index[a]] = (B[index[b]] + B[index[c]]) / 2;
119+
}
120+
if (isNaN(R[index[b]])) {
121+
R[index[b]] = (R[index[c]] + R[index[a]]) / 2;
122+
G[index[b]] = (G[index[c]] + G[index[a]]) / 2;
123+
B[index[b]] = (B[index[c]] + B[index[a]]) / 2;
124+
}
125+
if (isNaN(R[index[c]])) {
126+
R[index[c]] = (R[index[a]] + R[index[b]]) / 2;
127+
G[index[c]] = (G[index[a]] + G[index[b]]) / 2;
128+
B[index[c]] = (B[index[a]] + B[index[b]]) / 2;
129+
}
130+
}
131+
}
132+
}
90133

91-
const z = (By - Cy) * (Ax - Cx) + (Ay - Cy) * (Cx - Bx);
92-
if (!z) continue;
134+
// Interpolate the interior of all triangles with barycentric coordinates
135+
for (let i = 0; i < triangles.length; i += 3) {
136+
const T = triangles.subarray(i, i + 3);
137+
let [ia, ib, ic] = Array.from(T, (i) => index[i]);
93138

94-
for (let x = Math.floor(x0); x < x1; x++) {
95-
for (let y = Math.floor(y0); y < y1; y++) {
96-
if (x < 0 || x >= width || y < 0 || y >= height) continue;
97-
const ga = ((By - Cy) * (x - Cx) + (y - Cy) * (Cx - Bx)) / z;
98-
if (ga < 0) continue;
99-
const gb = ((Cy - Ay) * (x - Cx) + (y - Cy) * (Ax - Cx)) / z;
100-
if (gb < 0) continue;
101-
const gc = 1 - ga - gb;
102-
if (gc < 0) continue;
103-
const k = (x + width * y) << 2;
104-
imageData[k + 0] = R ? ga * R[ia] + gb * R[ib] + gc * R[ic] : r;
105-
imageData[k + 1] = G ? ga * G[ia] + gb * G[ib] + gc * G[ic] : g;
106-
imageData[k + 2] = B ? ga * B[ia] + gb * B[ib] + gc * B[ic] : b;
107-
imageData[k + 3] = FO ? (ga * FO[ia] + gb * FO[ib] + gc * FO[ic]) * 255 : a;
139+
const [Ax, Bx, Cx] = Array.from(T, (i) => points[2 * i]);
140+
const [Ay, By, Cy] = Array.from(T, (i) => points[2 * i + 1]);
141+
const [x0, x1] = d3.extent([Ax, Bx, Cx]);
142+
const [y0, y1] = d3.extent([Ay, By, Cy]);
143+
144+
const z = (By - Cy) * (Ax - Cx) + (Ay - Cy) * (Cx - Bx);
145+
if (!z) continue;
146+
147+
for (let x = Math.floor(x0); x < x1; x++) {
148+
for (let y = Math.floor(y0); y < y1; y++) {
149+
if (x < 0 || x >= width || y < 0 || y >= height) continue;
150+
const ga = ((By - Cy) * (x - Cx) + (y - Cy) * (Cx - Bx)) / z;
151+
if (ga < 0) continue;
152+
const gb = ((Cy - Ay) * (x - Cx) + (y - Cy) * (Ax - Cx)) / z;
153+
if (gb < 0) continue;
154+
const gc = 1 - ga - gb;
155+
if (gc < 0) continue;
156+
const k = (x + width * y) << 2;
157+
imageData[k + 0] = R ? ga * R[ia] + gb * R[ib] + gc * R[ic] : r;
158+
imageData[k + 1] = G ? ga * G[ia] + gb * G[ib] + gc * G[ic] : g;
159+
imageData[k + 2] = B ? ga * B[ia] + gb * B[ib] + gc * B[ic] : b;
160+
imageData[k + 3] = FO ? (ga * FO[ia] + gb * FO[ib] + gc * FO[ic]) * 255 : a;
161+
}
108162
}
109163
}
110-
}
111-
context2d.putImageData(image, 0, 0);
164+
context2d.putImageData(image, 0, 0);
165+
};
112166
}

0 commit comments

Comments
 (0)