Public
Edited
Nov 3, 2022
Insert cell
Insert cell
Insert cell
nodes = [
[0, 5, 10],
[0, 10, 20, 30]
]
Insert cell
Insert cell
Insert cell
Insert cell
values = nodes[0].map((x) => nodes[1].map((y) => y + x))
Insert cell
checkShapes(nodes, values)
Insert cell
Insert cell
// [], [] -> Boolean
checkShapes = function (nodes, values) {
// number of elements, using cumulative product of length of each dimension
const nElemExpected = nodes
.map((x) => x.length)
.reduce((acc, val, i) => [...acc, val * acc[i]], [1])
.slice(1);

// number of elments, after flattening each dimension
const nElemActual = nodes.map((x, i) => values.flat(i).length);

// do they match at all levels?
const validDim = nElemExpected.reduce(
(acc, val, i) => acc && val === nElemActual[i],
true
);

// get total number of elements
const nElemTotal = values.flat(Infinity).length;

// should equal the result of finite-flattening
const validTotal = nElemActual[nElemActual.length - 1] === nElemTotal;

return validDim && validTotal;
}
Insert cell
// [] -> ([] -> number)
function makeInterpolator(values) {
const interpolator = function (locations) {
// for each dimension, a function to extract the slice
const slicers = locations.map(
(loc) => (x) => x.slice(loc.interval - 1, loc.interval + 1)
);

// build up an extractor function, starting with the innermost dimension
const extractor = slicers.reduceRight((acc, val) => (x) => val(x).map(acc));

// extract
const valuesInterval = extractor(values);

// for each dimension, a function to reduce value-arrays with interpolation
const reducers = locations.map(
(loc) => (x) => x[0] * loc.weight[0] + x[1] * loc.weight[1]
);

// build up a function, starting with innermost dimension
const valueGetter = reducers.reduceRight(
(acc, val) => (x) => val(x.map(acc))
);

const result = valueGetter(extractor(values));

return result;
};

return interpolator;
}
Insert cell
// [] -> ([] -> [])
function makeLocator(nodes) {
// calculate delta for each set of notes
const deltas = nodes.map(getDelta);

// verify each set of nodes is monotonic
deltas.map(checkDeltaMonotonic);

const coordinator = function (coords) {
// verify coords has the right dimension
if (coords.length !== nodes.length) {
throw "Error: coords length does not equal nodes length.";
}

// find interval for each dimension
const interval = coords.map((x, i) =>
// clamp to the maximum interval
Math.min(d3.bisect(nodes[i], x), nodes[i].length - 1)
);

// validate interval
if (!interval.every((x) => x > 0)) {
// throw "Coordinate out of limits.";
}

// find normalized distance for each dimension
const dist = coords.map(
(x, i) => (x - nodes[i][interval[i] - 1]) / deltas[i][interval[i] - 1]
);

// validate normDist
if (!dist.every((x) => x <= 1)) {
// throw "Coordinate out of limits.";
}

// combine locations
const locations = interval.map((x, i) => ({
interval: x,
weight: [1 - dist[i], dist[i]]
}));

return locations;
};

return coordinator;
}
Insert cell
locator = makeLocator(nodes)
Insert cell
viewof coordinates = Inputs.form(
nodes.map((x, i) =>
Inputs.range([x[0], x[x.length - 1]], { label: `Dimension ${i}`, step: 1 })
)
)
Insert cell
locations = locator(coordinates)
Insert cell
Inputs.table(locations)
Insert cell
interpolator = makeInterpolator(values)
Insert cell
interpolator(locations)
Insert cell
// [] -> Boolean
checkDeltaMonotonic = function (delta) {
const deltaSign = delta.map(Math.sign);

if (deltaSign.length == 0) {
throw "Error: Array needs more than one element.";
}

if (!deltaSign.every((x) => x !== 0)) {
throw "Error: Array has repeated value.";
}

if (!deltaSign.every((x, i, arr) => x === arr[0])) {
throw "Error: Array not monotonic.";
}

return true;
}
Insert cell
// [] -> []
getDelta = function (x) {
const tail = x.slice(1, x.length);
const head = x.slice(0, x.length - 1);

const delta = tail.map((x, i) => {
return x - head[i];
});

return delta;
}
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