vasturiano/force-graph

Anti nodes collisions (overlay)

Blabla5943 opened this issue · 1 comments

EDIT : The idea is to use is example but WITH anti collision for the text: https://vasturiano.github.io/force-graph/example/text-nodes/

Hello,

I use force-graph to generate the relations between the tables of my SQL database.

https://zupimages.net/up/22/15/xn1f.png

However, when I have a lot of relations between tables, I get a graph with collisions between the nodes (overlay between two tables). This is not convenient for the global reading and forces us to use the zoom to navigate in the graph.

I would like to set up an anti-collision system (anti overlapping of the nodes), but I don't know how to proceed.
It seems that force-graph does not offer this option in the configuration. Do you have a solution for me?

My currently code:

                graph.nodeCanvasObject((node, ctx, globalScale) => nodePaint(node, ctx, globalScale))
                .linkWidth(2)
                .linkColor(() => 'rgba(255,255,255,0.2)')
                .nodeRelSize(1)
                .d3Force("link", d3.forceLink().distance(d => d.distance))
                .linkDirectionalArrowLength(3)
                .d3Force('collision', d3.forceCollide(node => Math.sqrt(100 / (node.level + 1)) * 1))
                .d3VelocityDecay(0.3)
                .onNodeClick(node => {
                    // Center/zoom on node
                    graph.centerAt(node.x -100, node.y-100);
                    graph.zoom(8, 200);
                })
                .onEngineStop(() => graph.zoomToFit(0,10,node =>true))
                .graphData(gData);

function nodePaint(node,  ctx, globalScale) {
    const label = node.tableName;
    const fontSize = 12/globalScale;
    if(node.level === 0){
        ctx.fillStyle = "#d23537";
    }
    else {
        ctx.fillStyle = "rgba(208,195,49,0.87)";
    }
    ctx.font = `${fontSize}px Sans-Serif`;
    const textWidth = ctx.measureText(label).width;
    const bckgDimensions = [textWidth, fontSize].map(n => n + fontSize * 0.2); // some padding
    ctx.fillRect(node.x - bckgDimensions[0] / 2, node.y - bckgDimensions[1] / 2, ...bckgDimensions);
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillStyle = '#000';
    ctx.fillText(label, node.x, node.y);
}

Thanks in advance for your help.


PS: I did some research before posting this message, and I found this:
https://stackoverflow.com/questions/23251590/prevent-node-overlap-in-force-directed-graph
https://stackoverflow.com/questions/58137867/prevent-node-overlap-in-d3js-network-map-v4
Since force-graph allows to use code directly for the D3 engine (which integrates an anti-colision system), I think it would be possible to apply such a behavior on force-graph. But after many attempts, I didn't manage to get anything.

@Blabla5943 it seems you are already including a collision force in your example:

   .d3Force('collision', d3.forceCollide(node => Math.sqrt(100 / (node.level + 1)) * 1))

If you are not getting the intended outcome, perhaps it's a matter of calibrating that collision force to suit your specific case. You can find the collision force docs here: https://github.com/d3/d3-force#forceCollide