score:5
This is an interesting question, which every so often is brought up in various flavors. The last time I remember having answered one of these was Gerardo Furtado's "Conflict between d3.forceCollide() and d3.forceX/Y() with high strength() value". It may be hard to grasp at first, but once it has fully sunken in, it is almost obvious. So bear with me as I will try to put this down in plain words first.
When using d3.forceSimulation()
it is important to always keep in mind, that it is just that: a simulation, no more, no less. And its inner workings are far from being a realisitic replica of nature's forces. One major drawback is the fact, that forces are applied sequentially instead of simultaneously. And even the calculations for a single force will be applied to one node after another instead of all nodes at once. This is by no means a problem special to D3, but an issue any computer-driven simulation faces and has to find a solution for. Even worse, there will never be a real solution to the problem, but rather a trade-off between the performance of the calculations and the degree it lives up to the viewers expectations.
Contrary to nature, in our simulation a constraint can easily violate another constraint or the same constraint for another node without causing the annihilation of the essence of being. Often, these results might come unexpected when compared to forces you are accustomed to and are thus expecting.
How is this related to your special problem?
When doing collision detection you are pushing around some nodes to avoid violating the constraint of mutual exclusion. Mainly depending on how clever your algorithm is, there is a risk of pushing one node into the realm of another while trying to avoid a third node. Again, depending on the way you do the calculations, this induced violation might not be resolved until the next tick of the simulation. The same may happen if a force, which is calculated after the collision detection, pushes a node to a position where it violates the collision avoidance (or any other constraint). This may even give rise to a bunch of problems further down the road dealing with the other forces or the same force on other nodes.
The most common way around this is to apply the collision avoidance algorithm multiple times within one tick, whereby iteratively getting closer to a real solution, hopefully. When I first came across this approach, it seemed so helpless and pathetic, that I was astounded, that it should be our best shot… But it works not that bad.
Since you provided your own implementation for a collision detection, namely the function collide()
, let us first try to improve that. By wrapping your entire calculations into a loop repeating it, say, ten times, the output will significantly improve (JSFiddle):
for (let i=10; i>0; i--) { // approximation by iteration
quadtree.visit(function(quad, x1, y1, x2, y2) {
// heavy lifting omitted for brevity
});
}
Although this works out pretty well, one notices nodes still overlapping, if not that many as before. To further improve on this, I suggest you ditch your own implementation in favor of D3's own collision detection algorithm which is available as d3.forceCollide()
. This force's implementation has the above mentioned iterations already built in. You can control the number of iterations by setting it via collide.iterations()
. Apart from that, the implementation is equipped with some more clever optimizations:
Overlapping nodes are resolved through iterative relaxation. For each node, the other nodes that are anticipated to overlap at the next tick (using the anticipated positions ⟨x + vx,y + vy⟩) are determined; the node’s velocity is then modified to push the node out of each overlapping node. The change in velocity is dampened by the force’s strength such that the resolution of simultaneous overlaps can be blended together to find a stable solution.
Tweaking your simulation
could look like this (JSFiddle):
var simulation = d3.forceSimulation()
.force("link",
d3.forceLink()
.id(function(d) { return d.id; })
.distance(50)
.strength(.5) // weaken link strength
)
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2))
.force("gravity", gravity(0.25))
.force("collide",
d3.forceCollide()
.strength(.9) // strong collision avoidance
.radius(radius) // set your radius function
.iterations(10) // number of iterations per tick
);
As you can see, there are still overlapping nodes, and it might be worth playing around with the parameters of the forces some more to yield an outcome which is pleasing to the eye. Given the large number of nodes, their rather large circles and the number of forces you apply, however, I would not expect it to be free of any collisions.
Source: stackoverflow.com
Related Query
- Collision detection for nodes of varying sizes not working as expected
- D3 inside for loop not working as expected
- Title Attribute not working for an SVG Rect on Safari
- d3 transition for transform translate not working for DIV
- d3.js update pattern not working as expected
- Rickshaw : data for multiple series not working
- CSS transform-origin not working for svg in safari
- d3.js date time format specifier not working as expected
- D3.js Pie Chart Dynamic Update Transitions not Working as Expected
- General update pattern not working as expected with d3.selection
- Looking for a way to display labels on sunburst chart (could not find a working example)
- Drag rect not working as expected
- brush extents not working as expected
- Exit() is not working for d3 group bar chart
- C3 Extent not working as expected
- Ajax not working when get data based on input for D3
- hovering on one class not working for classes defined before it
- d3.js v5.9.1 - some methods in this version are not working as expected
- d3.js ticks values not working as expected
- D3.js v4 circle radius transition not working as expected
- tree.nodeSize() not working in D3 tree graph to inc. the space between nodes
- D3 Zoom's translateExtent not working as expected
- Animation is not working for multiline chart D3 v4
- d3.js Stacked Bar Chart working for one dataset but not the other
- d3 clip-path not working for filled line graph
- JavaScript dictionary value incrementing not working in nested for loop
- append not working when called for the second time
- C3 xFormat is not working for timestamp in seconds
- D3 force collision detection for ellipses
- d3.js drag node boundaries not working for linked path
More Query from same tag
- d3.js svg stroke applying to child element
- Text along circles in a D3 circle pack layout
- d3.js - Force-Layout boundary issue V4
- Capture XAxis value NVD3 Stacked Area Chart
- Fit svg inside of div and automatically scale svg if size of div changes
- Resetting D3s transform attributes
- D3js Multi line graph convert from using CSV file
- Add event listener of a method present in another js file
- d3 geomap drawing on top of map z-order
- How to translate and wrap d3.js world map 2D projection
- how to set d3.brush extent right?
- Pie Chart Labels with D3.js and AngularJS
- Adding text to the center of a D3 Donut Graph
- angularjs-nvd3-directives updating data
- d3js removing a transition of an element
- Convert a d3 chart to load data from json inside a variable
- How to prevent d3 brush events from firing after calling brush.move
- How to label one specific scatter point (not all points) using d3 or regular javascript?
- How to Grow the Arc On Mouseover in D3.js and HTML
- D3 with AngularJs, Multiple charts on same page with different data based on common key
- Cannot hide cities behind spinning globe
- Donut chart in D3 JS Sankey diagram
- Hide Tick Labels in D3 on Line Graph
- SVG animation inside svg:use element. Works in Firefox, not in Chrome
- Angular D3 bar chart animation
- How to programmatically add properties to topojson file at runtime?
- d3 Bubble Chart positioning and text in side bubbles
- Show only the relationships of selected node in network visualization graph
- D3 Bar Chart with Tooltip on X-Axis
- How to create axisLeft dynamic in D3.js