11import { blurImage , Delaunay , randomLcg , rgb } from "d3" ;
22import { valueObject } from "../channel.js" ;
33import { create } from "../context.js" ;
4- import { map , first , second , third , isTuples , isNumeric , isTemporal , take , identity } from "../options.js" ;
4+ import { map , first , second , third , isTuples , isNumeric , isTemporal , identity } from "../options.js" ;
55import { maybeColorChannel , maybeNumberChannel } from "../options.js" ;
66import { Mark } from "../mark.js" ;
77import { applyAttr , applyDirectStyles , applyIndirectStyles , applyTransform , impliedString } from "../style.js" ;
@@ -282,31 +282,16 @@ export function interpolateNone(index, width, height, X, Y, V) {
282282
283283export function interpolatorBarycentric ( { random = randomLcg ( 42 ) } = { } ) {
284284 return ( index , width , height , X , Y , V ) => {
285- // Flatten the input coordinates to prepare to insert extrapolated points
286- // along the perimeter of the grid (so there’s no blank output).
287- const n = index . length ;
288- const nw = width >> 2 ;
289- const nh = ( height >> 2 ) - 1 ;
290- const m = n + nw * 2 + nh * 2 ;
291- const XY = new Float64Array ( m * 2 ) ;
292- for ( let i = 0 ; i < n ; ++ i ) ( XY [ i * 2 ] = X [ index [ i ] ] ) , ( XY [ i * 2 + 1 ] = Y [ index [ i ] ] ) ;
293-
294- // Add points along each edge, making sure to include the four corners for
295- // complete coverage (with no chamfered edges).
296- let i = n ;
297- const addPoint = ( x , y ) => ( ( XY [ i * 2 ] = x ) , ( XY [ i * 2 + 1 ] = y ) , i ++ ) ;
298- for ( let j = 0 ; j <= nw ; ++ j ) addPoint ( ( j / nw ) * width , 0 ) , addPoint ( ( j / nw ) * width , height ) ;
299- for ( let j = 0 ; j < nh ; ++ j ) addPoint ( width , ( j / nh ) * height ) , addPoint ( 0 , ( j / nh ) * height ) ;
300-
301- // To each edge point, assign the closest (non-extrapolated) value.
302- V = take ( V , index ) ;
303- const delaunay = new Delaunay ( XY . subarray ( 0 , n * 2 ) ) ;
304- for ( let j = n , ij ; j < m ; ++ j ) V [ j ] = V [ ( ij = delaunay . find ( XY [ j * 2 ] , XY [ j * 2 + 1 ] , ij ) ) ] ;
305-
306285 // Interpolate the interior of all triangles with barycentric coordinates
307- const { points, triangles} = new Delaunay ( XY ) ;
308- const W = new V . constructor ( width * height ) ;
286+ const { points, triangles, hull} = Delaunay . from (
287+ index ,
288+ ( i ) => X [ i ] ,
289+ ( i ) => Y [ i ]
290+ ) ;
291+ const W = new V . constructor ( width * height ) . fill ( NaN ) ;
292+ const S = new Uint8Array ( width * height ) ; // 1 if pixel has been seen.
309293 const mix = mixer ( V , random ) ;
294+
310295 for ( let i = 0 ; i < triangles . length ; i += 3 ) {
311296 const ta = triangles [ i ] ;
312297 const tb = triangles [ i + 1 ] ;
@@ -323,9 +308,9 @@ export function interpolatorBarycentric({random = randomLcg(42)} = {}) {
323308 const y2 = Math . max ( Ay , By , Cy ) ;
324309 const z = ( By - Cy ) * ( Ax - Cx ) + ( Ay - Cy ) * ( Cx - Bx ) ;
325310 if ( ! z ) continue ;
326- const va = V [ ta ] ;
327- const vb = V [ tb ] ;
328- const vc = V [ tc ] ;
311+ const va = V [ index [ ta ] ] ;
312+ const vb = V [ index [ tb ] ] ;
313+ const vc = V [ index [ tc ] ] ;
329314 for ( let x = Math . floor ( x1 ) ; x < x2 ; ++ x ) {
330315 for ( let y = Math . floor ( y1 ) ; y < y2 ; ++ y ) {
331316 if ( x < 0 || x >= width || y < 0 || y >= height ) continue ;
@@ -337,14 +322,91 @@ export function interpolatorBarycentric({random = randomLcg(42)} = {}) {
337322 if ( gb < 0 ) continue ;
338323 const gc = 1 - ga - gb ;
339324 if ( gc < 0 ) continue ;
340- W [ x + width * y ] = mix ( va , ga , vb , gb , vc , gc , x , y ) ;
325+ const i = x + width * y ;
326+ W [ i ] = mix ( va , ga , vb , gb , vc , gc , x , y ) ;
327+ S [ i ] = 1 ;
341328 }
342329 }
343330 }
331+ extrapolateBarycentric ( W , S , X , Y , V , width , height , hull , index , mix ) ;
344332 return W ;
345333 } ;
346334}
347335
336+ // Extrapolate by finding the closest point on the hull.
337+ function extrapolateBarycentric ( W , S , X , Y , V , width , height , hull , index , mix ) {
338+ X = Float64Array . from ( hull , ( i ) => X [ index [ i ] ] ) ;
339+ Y = Float64Array . from ( hull , ( i ) => Y [ index [ i ] ] ) ;
340+ V = Array . from ( hull , ( i ) => V [ index [ i ] ] ) ;
341+ const n = X . length ;
342+ const rays = Array . from ( { length : n } , ( _ , j ) => ray ( j , X , Y ) ) ;
343+ let k = 0 ;
344+ for ( let y = 0 ; y < height ; ++ y ) {
345+ const yp = y + 0.5 ;
346+ for ( let x = 0 ; x < width ; ++ x ) {
347+ const i = x + width * y ;
348+ if ( ! S [ i ] ) {
349+ const xp = x + 0.5 ;
350+ for ( let l = 0 ; l < n ; ++ l ) {
351+ const j = ( n + k + ( l % 2 ? ( l + 1 ) / 2 : - l / 2 ) ) % n ;
352+ if ( rays [ j ] ( xp , yp ) ) {
353+ const t = segmentProject ( X . at ( j - 1 ) , Y . at ( j - 1 ) , X [ j ] , Y [ j ] , xp , yp ) ;
354+ W [ i ] = mix ( V . at ( j - 1 ) , t , V [ j ] , 1 - t , V [ j ] , 0 , x , y ) ;
355+ k = j ;
356+ break ;
357+ }
358+ }
359+ }
360+ }
361+ }
362+ }
363+
364+ // Projects a point p = [x, y] onto the line segment [p1, p2], returning the
365+ // projected coordinates p’ as t in [0, 1] with p’ = t p1 + (1 - t) p2.
366+ function segmentProject ( x1 , y1 , x2 , y2 , x , y ) {
367+ const dx = x2 - x1 ;
368+ const dy = y2 - y1 ;
369+ const a = dx * ( x2 - x ) + dy * ( y2 - y ) ;
370+ const b = dx * ( x - x1 ) + dy * ( y - y1 ) ;
371+ return a > 0 && b > 0 ? a / ( a + b ) : + ( a > b ) ;
372+ }
373+
374+ function cross ( xa , ya , xb , yb ) {
375+ return xa * yb - xb * ya ;
376+ }
377+
378+ function ray ( j , X , Y ) {
379+ const n = X . length ;
380+ const xc = X . at ( j - 2 ) ;
381+ const yc = Y . at ( j - 2 ) ;
382+ const xa = X . at ( j - 1 ) ;
383+ const ya = Y . at ( j - 1 ) ;
384+ const xb = X [ j ] ;
385+ const yb = Y [ j ] ;
386+ const xd = X . at ( j + 1 - n ) ;
387+ const yd = Y . at ( j + 1 - n ) ;
388+ const dxab = xa - xb ;
389+ const dyab = ya - yb ;
390+ const dxca = xc - xa ;
391+ const dyca = yc - ya ;
392+ const dxbd = xb - xd ;
393+ const dybd = yb - yd ;
394+ const hab = Math . hypot ( dxab , dyab ) ;
395+ const hca = Math . hypot ( dxca , dyca ) ;
396+ const hbd = Math . hypot ( dxbd , dybd ) ;
397+ return ( x , y ) => {
398+ const dxa = x - xa ;
399+ const dya = y - ya ;
400+ const dxb = x - xb ;
401+ const dyb = y - yb ;
402+ return (
403+ cross ( dxa , dya , dxb , dyb ) > - 1e-6 &&
404+ cross ( dxa , dya , dxab , dyab ) * hca - cross ( dxa , dya , dxca , dyca ) * hab > - 1e-6 &&
405+ cross ( dxb , dyb , dxbd , dybd ) * hab - cross ( dxb , dyb , dxab , dyab ) * hbd <= 0
406+ ) ;
407+ } ;
408+ }
409+
348410export function interpolateNearest ( index , width , height , X , Y , V ) {
349411 const W = new V . constructor ( width * height ) ;
350412 const delaunay = Delaunay . from (
0 commit comments