function genPolygonCP(options = {}) {
const {
randomSeed = 1,
n = 20,
rigidity = 2,
minRigidity = 0,
rigidityPeriods = 0,
variability = 100,
iterations = 100,
tieDensity = 0.5,
tieStrength = 0.5
} = options;
const rand = d3.randomLcg(randomSeed);
const factor = variability / 100;
const lengths = d3.range(n).map(() => 1 + rand() * factor);
const totalLength = lengths.reduce((a, b) => a + b, 0);
const avgLength = totalLength / n;
const R = totalLength / (Math.PI * 2);
let angle = 0;
const nodes = [];
const links = [];
let prevLen = lengths[n - 1];
for (let i = 0; i < n; i++) {
const len = lengths[i];
const x = R + R * Math.cos(angle);
const y = R + R * Math.sin(angle);
const r = len / 2;
angle += ((len + prevLen) / 2 / totalLength) * (2 * Math.PI);
nodes.push({ x, y, r });
prevLen = len;
}
const rigidityVariation = cyclicVariation(
n,
minRigidity,
rigidity,
rigidityPeriods,
rand()
);
for (let i = 0; i < n; i++) {
const r = rigidityVariation[i];
for (let k = 1; k <= 1 + r; k++) {
let prev = (i + n - k) % n;
let distance = Math.hypot(
nodes[i].x - nodes[prev].x,
nodes[i].y - nodes[prev].y
);
links.push({ source: prev, target: i, distance });
}
}
const m = Math.trunc(n * tieDensity);
let pools = [d3.range(n)];
for (let i = 0; i < m; i++) {
pools.sort((a, b) => a.length - b.length);
let pool = pools.pop();
let a = Math.trunc(rand() * (pool.length - 1));
let b = Math.trunc(rand() * (pool.length - 1));
if (a > b) [a, b] = [b, a];
const source = pool[a];
const target = pool[b];
if (Math.abs(source - target) <= 2) {
pools.push(pool);
continue;
}
pools.push(pool.slice(a, b));
pools.push(pool.slice(b).concat(pool.slice(0, a)));
let d = Math.hypot(
nodes[source].x - nodes[target].x,
nodes[source].y - nodes[target].y
);
links.push({
source,
target,
distance: d * (1 - tieStrength) + tieStrength * avgLength
});
}
const sim = d3
.forceSimulation(nodes)
.alphaMin(0.1)
.force(
"link",
d3.forceLink(links).distance((link) => link.distance)
)
.force("center", d3.forceCenter().x(R).y(R) )
.force("many", d3.forceManyBody().strength(0.2 / R))
.force(
"collide",
d3.forceCollide().radius((node) => node.r)
)
.stop();
if (!iterations) {
return { sim, nodes, links, R };
} else {
sim.tick(iterations);
return nodes.map(({ x, y }) => [x / R / 2, y / R / 2]);
}
}