class ForceRect {
constructor(params) {
this.params = params;
}
ctx = memo(this, 'ctx', () => {
return DOM.context2d(this.params.width, this.params.height);
});
canvas = memo(this, 'canvas', () => {
return this.ctx().canvas;
})
nodesCount = 1500;
nodesAndEdges = memo(this, 'nodesAndEdges', () => {
const res = {
nodes: [],
edges: [],
};
for (let i=0; i<this.nodesCount; i++) {
const h = 60 / (1 + Math.random() * 10) ** 0.5;
const w = h * 0.6 * Math.random() * 15;
res.nodes.push({
index: i,
width: w,
height: h,
...(i === 0 ? {
fx: Math.random() * this.params.width,
fy: Math.random() * this.params.height,
} : {}),
});
if (i > 0) {
res.edges.push({
source: i,
target: Math.floor(Math.random() * i),
});
}
}
return res;
});
simulation = memo(this, 'simulation', () => {
const sim = d3.forceSimulation(this.nodesAndEdges().nodes)
.force("link", d3.forceLink(this.nodesAndEdges().edges))
.force("charge", d3.forceManyBody())
.force("rectCollide", null)
.force("center", d3.forceCenter(this.params.width/2, this.params.height/2))
.on("tick", () => this.tick());
if (this.params.invalidation != null) this.params.invalidation.then(() => sim.stop());
return sim;
});
drawNode({x, y, width, height}) {
const ctx = this.ctx();
ctx.fillStyle = 'black';
ctx.fillRect(x - width / 2, y - height / 2, width, height);
}
drawEdge({source, target}) {
const ctx = this.ctx();
const {nodes} = this.nodesAndEdges();
ctx.strokeStyle = 'black';
ctx.beginPath();
ctx.moveTo(source.x, source.y);
ctx.lineTo(target.x, target.y);
ctx.stroke();
}
stepNum = 0;
tick() {
this.stepNum ++;
if (this.stepNum > 100) {
this.simulation().force("rectCollide", forceCollide());
}
this.draw();
}
draw() {
const ctx = this.ctx();
ctx.save();
ctx.clearRect(0, 0, this.params.width, this.params.height);
const dpi = 1 / window.devicePixelRatio;
ctx.translate(this.params.width * dpi / 2, this.params.height * dpi / 2);
const s = 0.2;
ctx.scale(s, s);
const {nodes, edges} = this.nodesAndEdges();
edges.forEach(edge => this.drawEdge(edge));
nodes.forEach(node => this.drawNode(node));
ctx.restore();
}
}