D3 layout directivity - priority of the distance between channels

Using gain-centric layout in d3, how do I prioritize link distance while maintaining a good graph?

If I specify dynamic distances on the line, but keep the default charge, my distances on the diagram are slightly changed by the charge function and are no longer exact distances:

enter image description here

However, if I remove the charge, the graph will look like this:

enter image description here

Any advice appreciated!

+6
source share
1 answer

If I understand correctly, I believe that there is a potential solution.

To get the exact communication distance, you need to set the charge and collision forces to zero, but, as your image shows, the nodes are not arranged so that other nodes are taken into account, but only the nodes with which they exchange links with. Since d3.force initializes nodes that do not have x, y values ​​in the phyllotaxis layout, links and nodes will be unintentionally grouped. But, using the repulsive force during the simulation, the distance increases, but the distances are distorted.

A possible solution is to use the repulsive force initially, because you need to split the nodes into recognizable link-based clusters. Then, after they are separated, reduce the repulsive force to zero, so that the only force applied is related to the desired bond distance.

This requires that you change the forces in the tick function when the graph develops. It also requires that all distances between links be compatible with each other (the triangle of nodes cannot have two angles divided by 100 pixels, and the remaining corner is connected with two others by 10 pixels).

Something like this might work inside the tick function in simple situations:

var alpha = this.alpha(); // starts at 1 by default, simulation ends at zero var chargeStrength; // a multiplier for charge strength if ( alpha > 0.2 ) { chargeStrength = (alpha - 0.2 / 0.8); // decrease for the first portion of the simulation } else { chargeStrength = 0; // leave at zero and give the link distance force time to work without competing forces } 

For more complex visualizations, you can increase the cooling time by decreasing the alphaDecay value or increasing it for simpler ones.

I made a simple example here, at the end of the visualization, the distances are covered (I increased alphaDecay in the fragment below to speed it up due to accuracy, but this is still pretty good) and refers to the desired distances.

 var graph = { nodes: d3.range(15).map(Object), links: [ {source: 0, target: 1, distance: 20 }, {source: 0, target: 2, distance: 40}, {source: 0, target: 3, distance: 80}, {source: 1, target: 4, distance: 20}, {source: 1, target: 5, distance: 40}, {source: 1, target: 6, distance: 80}, {source: 2, target: 7, distance: 12}, {source: 2, target: 8, distance: 8}, {source: 2, target: 9, distance: 6}, {source: 3, target: 10, distance: 10}, {source: 3, target: 11, distance: 10}, {source: 3, target: 12, distance: 2}, {source: 3, target: 13, distance: 2}, {source: 3, target: 14, distance: 2} ] }; var svg = d3.select("svg"), width = +svg.attr("width"), height = +svg.attr("height"); var color = d3.scaleOrdinal(d3.schemeCategory20); var simulation = d3.forceSimulation() .force("charge", d3.forceManyBody().strength(-30 )) .force("link", d3.forceLink().distance(function(d) { return d.distance } ).strength(2) ) .force("center", d3.forceCenter(width / 2, height / 2)) .force("collide",d3.forceCollide().strength(0).radius(0)) .alphaDecay(0.03) .velocityDecay(0.4); var link = svg.append("g") .attr("class", "links") .selectAll("line") .data(graph.links) .enter().append("line") .attr("stroke-width", 1); var node = svg.append("g") .attr("class", "nodes") .selectAll("circle") .data(graph.nodes) .enter().append("circle") .attr("r", 3) simulation .nodes(graph.nodes) .on("tick", ticked); simulation.force("link") .links(graph.links); function ticked() { var alpha = this.alpha(); var chargeStrength; if ( alpha > 0.2 ) { chargeStrength = (alpha - 0.2 / 0.8); } else { chargeStrength = 0; } this.force("charge", d3.forceManyBody().strength( -30 * chargeStrength )) link .attr("x1", function(d) { return d.source.x; }) .attr("y1", function(d) { return d.source.y; }) .attr("x2", function(d) { return d.target.x; }) .attr("y2", function(d) { return d.target.y; }); node .attr("cx", function(d) { return dx; }) .attr("cy", function(d) { return dy; }); // validate: if (alpha < 0.001) { link.each(function(d,i) { var a = d.source.x - d.target.x; var b = d.source.y - d.target.y; var c = Math.pow(a*a + b*b, 0.5); console.log("specified length: " + graph.links[i].distance + ", realized distance: " + c ); }) } } 
 .links line { stroke: #999; stroke-opacity: 0.6; } .nodes circle { stroke: #fff; stroke-width: 1.5px; } 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script> <svg width="500" height="300"></svg> 

Depending on the complexity of the graph, you may need to adapt the cooling time, the strength of the repulsive force, and how you change it as alpha cooling, speedDecay (potentially changing it in the checkbox function) and / or the distance force itself.

+3
source

All Articles