Published
Edited
Aug 13, 2020
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof factor = slider({ min: 0, max: 100 })
Insert cell
function makeSearchFn(costFn) {
return function(element, elements, direction) {
return _(elements)
.filter(compare => isInDirection(element, compare, direction))
.minBy(compare => costFn(element, compare, direction));
};
}
Insert cell
function biasedDistance(start, end, direction) {
if (direction === Direction.UP || direction === Direction.DOWN) {
return Math.sqrt(
Math.pow(start.x - end.x, 4) + Math.pow(start.y - end.y, 2)
);
} else {
return Math.sqrt(
Math.pow(start.x - end.x, 2) + Math.pow(start.y - end.y, 4)
);
}
}
Insert cell
Insert cell
function distance(A, B) {
return Math.sqrt(Math.pow(A.x - B.x, 2) + Math.pow(A.y - B.y, 2));
}
Insert cell
draw(elements, makeSearchFn((start, end) => distance(start.center, end.center)))
Insert cell
function angularCost(A, B, direction) {
const theta = directionToAngle(direction);
const idealx = Math.cos(theta);
const idealy = Math.sin(theta);
const dx = B.x - A.x;
const dy = B.y - A.y;
return Math.abs(
normalizeAngle(Math.atan2(-dy, dx) - Math.atan2(idealy, idealx))
);
}
Insert cell
function normalizeAngle(theta) {
if (theta > Math.PI) {
return theta - 2 * Math.PI;
} else if (theta <= -Math.PI) {
return theta + 2 * Math.PI;
} else {
return theta;
}
}
Insert cell
draw(
elements,
makeSearchFn((start, end, direction) =>
angularCost(start.center, end.center, direction)
)
)
Insert cell
function compositeCost(A, B, direction) {
const angleCost = angularCost(A, B, direction);
const distanceCost = distance(A, B);
return angleCost * factor + distanceCost;
}
Insert cell
Insert cell
Insert cell
Type JavaScript, then Shift-Enter. Ctrl-space for more options. Arrow ↑/↓ to switch modes.

Insert cell
Insert cell
Insert cell
function makeElements(wiggle, width, height) {
let i = 0;
const random = d3.randomNormal.source(seedrandom("hey what's up"))(0, 1);
return d3
.range(40, width - 40, 60)
.map(x => {
return d3.range(40, height - 40, 60).map(y => {
return {
...rect(x + random() * wiggle, y + random() * wiggle, 30, 30),
id: i++
};
});
})
.flat();
}
Insert cell
elements = makeElements(wiggle, width, height)
Insert cell
function findBestAnchors(start, end, direction) {
const startAnchor = anchorInDirection(start, direction);
const endAnchors = anchorPoints(end);
const pairings = endAnchors.map(end => ({ start: startAnchor, end }));
return _.minBy(pairings, ({ start, end }) => distance(start, end));
}
Insert cell
function anchorInDirection(rect, direction) {
if (direction === Direction.UP) {
return { x: rect.center.x, y: rect.top };
} else if (direction === Direction.DOWN) {
return { x: rect.center.x, y: rect.bottom };
} else if (direction === Direction.LEFT) {
return { x: rect.left, y: rect.center.y };
} else if (direction === Direction.RIGHT) {
return { x: rect.right, y: rect.center.y };
} else {
throw new Error('Not a valid direction');
}
}
Insert cell
function anchorPoints(rect) {
return [
{ x: rect.center.x, y: rect.top },
{ x: rect.center.x, y: rect.bottom },
{ x: rect.left, y: rect.center.y },
{ x: rect.right, y: rect.center.y }
];
}
Insert cell
function isInDirection(start, end, direction) {
if (direction === Direction.UP) {
return end.bottom <= start.top;
} else if (direction === Direction.DOWN) {
return end.top >= start.bottom;
} else if (direction === Direction.LEFT) {
return end.right <= start.left;
} else if (direction === Direction.RIGHT) {
return end.left >= start.right;
} else {
return false;
}
}
Insert cell
height = 300
Insert cell
function rect(x, y, width, height) {
const shape = {
x,
y,
width,
height,
top: y,
left: x,
bottom: y + height,
right: x + width
};
return { ...shape, center: rectCenter(shape) };
}
Insert cell
function rectCenter(rect) {
return {
x: rect.x + rect.width / 2,
y: rect.y + rect.height / 2
};
}
Insert cell
// Returns angle in radians in range [0,2π)
function directionToAngle(direction) {
switch (direction) {
case Direction.RIGHT:
return 0;
case Direction.UP:
return Math.PI / 2;
case Direction.LEFT:
return Math.PI;
case Direction.DOWN:
return (3 * Math.PI) / 2;
default:
throw new Error('Not a valid direction');
}
}
Insert cell
function keyToDirection(key) {
switch (key) {
case 'ArrowUp':
return Direction.UP;
case 'ArrowDown':
return Direction.DOWN;
case 'ArrowLeft':
return Direction.LEFT;
case 'ArrowRight':
return Direction.RIGHT;
default:
return null;
}
}
Insert cell
Insert cell
Insert cell
Insert cell
d3 = require('d3@5')
Insert cell
seedrandom = require("seedrandom")
Insert cell
_ = require('lodash@4')
Insert cell
import { slider } from '@jashkenas/inputs'
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