Published
Edited
Feb 16, 2021
Importers
8 stars
Also listed in…
Force Layouts
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
forceAngle = function(angleFn) {
var nodes,
links = [],
strength = _ => {
return 0.1;
},
distance = _ => {
return 10;
};

function force(alpha) {
for (var i = 0, n = nodes.length, node; i < n; ++i) {
let node = nodes[i];
node.vx += (node.target.x - node.x) * strength(node) * alpha;
node.vy += (node.target.y - node.y) * strength(node) * alpha;
}
}

function initialize() {
if (!nodes) return;

let getId = d => {
return typeof d.id === "string"
? d.id
: typeof d.index === "number"
? d.index
: d;
};

var linkCache = [];
for (var i = 0; i < links.length; ++i) {
linkCache[`${getId(links[i].source)}|${getId(links[i].target)}`] = true;
}

nodes.forEach((d, i) => {
var p1, p2, p3;

// default target to current position
d.target = { x: d.x, y: d.y };

// position of the first node/link in chain is fixed
if (i == 0 || d.chainIndex == 0) {
return;
}

// angle of the first two nodes is relative to a point x-1 from the first point
if (i == 1 || d.chainIndex == 1) {
// copy so we don't modify existing
p1 = JSON.parse(JSON.stringify(nodes[i - 1]));
p1.target.x = -1;
} else {
p1 = nodes[i - 2];
}

p2 = nodes[i - 1];
p3 = nodes[i];

// only calculate angle for linked nodes if links are specified
if (links.length != 0) {
if (
(linkCache[`${getId(p1)}|${getId(p2)}`] != true ||
linkCache[`${getId(p2)}|${getId(p3)}`] != true) &&
i != 1 &&
d.chainIndex != 1
) {
return;
}
}

var angle = typeof angleFn === "function" ? angleFn(d, i) : angleFn;
var radAngle = (angle / 180) * Math.PI;

// reverse p2/p3 because canvas origin is at top left the unit circle origin is bottom left
// TODO remove this and override in canvas drawing?
// TODO negative angles are in the wrong direction
radAngle -= Math.atan2(
p1.target.y - p2.target.y,
p2.target.x - p1.target.x
);

// generate the exact destination position for current node
var dist = distance(d);
var x = p2.target.x + dist * Math.cos(radAngle);
var y = p2.target.y + dist * Math.sin(radAngle);

d.target = { x: x, y: y };
});
}

force.initialize = function(_) {
nodes = _;
initialize();
};

force.strength = function(_) {
arguments.length
? (strength =
typeof _ === "function"
? _
: () => {
return _;
})
: strength;
return force;
};

force.distance = function(_) {
arguments.length
? (distance =
typeof _ === "function"
? _
: () => {
return _;
})
: distance;
initialize();
return force;
};

force.links = function(_) {
return arguments.length ? ((links = _), initialize(), force) : links;
};

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