I have a D3 force graph similar to the one in this example, except there can be a lot more than 2 edges between a pair of nodes nodes. I would like the ability to switch between visualizing all of the edges, and just a single, straight edge. I have gotten this to work by simply changing edges' visibility (hidden or visible), however there are so many edges between nodes that there is visible lag.
My idea to fix this is to only run the force simulation on the straight edges, instead of also including all of the curved edges. This would limit the edges to one edge per pair of nodes, thus making the force simulation algorithm less intensive.
Is it possible to do this while still being able to render the curved edges that aren't being used in the force algorithm?
It looks like the biggest computing boggleneck actually came in the .on("tick", tickActions) step when the code is computing how to draw all the paths, even the ones that aren't visible.
For anyone that's interested, I changed the tick actions function to first check a type variable before drawing the edge or not:
function tickActions() {
// plot the curved links
link.attr("d", function(d) {
if (d.type != draw_type) return null;
// code to draw paths
});
}
By changing the draw_type variable, you can decide which edges actually get computed and drawn.
In addition to this, you will also need to ignore the strength of certain edges. I knew that I wanted my graph to be spaced based only on the straight edges, so I did the following for the simulation.force('link', link_force) attribute:
var link_force = d3.forceLink()
// code for .id and .distance attributes
// return 0 for all non
.strength(function(d) {
if (d.type != 'straight') return 0;
return 0.3;
});
By setting the strength of all non-straight edges to 0, the force algorithm basically ignores them when spacing nodes.
Finally, I added a restart_network() function which updates the graph. It's possible to use this function to change the actual link data seen by the graph, but I decided to include the other changes as well.
function restart_network() {
simulation.force("link", link_force);
simulation.alpha(1).restart();
}
Related
I am currently working on a project where I need to keep the nodes of a force simulation away from the svg edges.
My attempt was using a exponential curve to increase the centering force for nodes reaching a certain x coordinate.
I am using this function
function getBorderForce(x, width, middle, steepness = 10) {
return Math.pow(((x-middle)/(width/2)), steepness)
}
If the given x value reaches middle + width / 2 it returns a value of 1. The steepness of the increase can modified by steepness parameter. A higher value means the nodes would get repealed later. I hope it is understandable.
I would then implement this function as follows into a d3.forceSimulation():
const sim = d3.forceSimulation().
.force("forceX", d3.forceX().x(d => d.forceX).strength(0.02))
.force("forceY", d3.forceY().y(d => d.forceY).strength(0.1))
.force("edgeRepeal", d3.forceX().x(_ => center).strength(d =>
getBorderForce(d.x, width, center)
))
.nodes(nodes)
When I use this approach my browser crashes and I get an out of memory error.
It works is if I use the d.forceX instead of the d.x value, but that means that nodes closer to the edges would be pushed in and would not be placed on their desired position d.forceX.
Maybe someone has a better idea or can show the flaw in my code – I am relatively new to d3, so it would be no surprise.
All the Best
Jason Davies graph coloring didn't avoided me to get neighbors polygons with the same color.
.style("fill", function(d, i) { return color(d.color = d3.max(neighbors[i], function(n) { return countries[n].color; }) + 1 | 0); });
Four color theorem:
We know :
the four color map theorem states that, given any separation of a plane into contiguous regions, producing a figure called a map, no more than four colors are required to color the regions of the map so that no two adjacent regions have the same color. (wikipedia)
and:
Second, for the purpose of the theorem every "country" has to be a simply connected region, or contiguous. [...] Because the territory of [non-contiguous countries] must be the same color, four colors may not be sufficient. (wikipedia)
Still, Is there any four color theorem-like implementation/function available for #D3js so we get neighbors polygons with different coloring ? (so we don't go to use 20 colors since 4-6 are generally enough)
See also:
Four color theorem
Graph theorem
Jason Davies Graph theorem coloring example
D3 coloring
Note: tag #four-color-theorem may be welcome.
Do you mind 8 colors? Reducing the number of possible colors is pretty simple:
color.range(color.range().slice(0, 8));
http://bl.ocks.org/1wheel/5899035
7 colors results in some adjacent countries sharing a border.
I also tried sorting the countries by number of neighbors before coloring; 7 colors still don't work:
var permutation = d3.range(neighbors.length).sort(function(i, j){
return neighbors[j].length - neighbors[i].length; });
countries = d3.permute(countries, permutation);
neighbors = d3.permute(neighbors, permutation)
.map(function(array){
return array.map(function(d){
return permutation.indexOf(d); }); });
Are you trying to color a specific map? It might be easier to set up a color scheme ahead of time instead of trying to code an algorithm that each client runs.
No, there is no such implementation. D3 wouldn't be the best thing to implement it in either in my opinion, as its design largely assumes that the data are independent.
I have a graph with about 600 nodes. After a while, d3.layout.force() converges. How can I display the (x,y) coordinates of the nodes such that I can store them as data and use them as a cached layout as described in this answer? The answer may be quite simple, as I'm a novice with javascript.
After the force layout converged, you can iterate over the list of nodes to get the current coordinates:
var force = d3.layout.force().nodes(nodes);
// after convergence
var positions = nodes.map(function(d) { return [d.x, d.y]; });
This assumes that your browser supports map, if not see here.
You can of course run this at every tick and save the result each time; this saves you figuring out when the layout has converged.
I'm trying to create a visualization with D3 such that nodes are differently sized by a particular attribute and bigger nodes go to the center and smaller nodes go to the outside. I have sizing and clustering and collision detection working, but I can't figure out how to tell the bigger nodes to go to the center.
I've tried messing with the charge, but couldn't convince that to work. I got linkDistance to move the bigger ones to the center, but (a) getting there was VERY jittery and (b) the smaller ones are way outside rather than tightly packed. The linkDistance is still in the code, just commented out.
It's up at http://pokedex.mrh.is/stats/index.html:
The relevant code (I assume) is also below. The nodes are sized per their attr attribute. Oh, and the nodes are Pokémon.
force = d3.layout.force()
// .gravity(0.05)
// .charge(function(d, i) { return d.attr; })
// .linkDistance(function(d) {
// return 50000/Math.pow(d.source.attr+d.target.attr,1);
// })
.nodes(pokemon)
// .links(links)
.size([$(window).width(), $(window).height()]);
The following gave me a less jittery version of what you have now.
force = d3.layout.force()
.gravity(0.1)
.charge(function(d, i) { return -d[selectedAttr]})
.friction(0.9)
.nodes(pokemon)
.size([$(window).width(), $(window).height()]);
To answer your actual question, each node's coordinates are currently being placed in your graph at random. I quote from the D3 documentation:
When nodes are added to the force layout, if they do not have x and y attributes already set, then these attributes are initialized using a uniform random distribution in the range [0, x] and [0, y], respectively.
From my experience, there's no magic force method that gets the nodes you want to the center of the map. The way that I've accomplished your desired result in the past has been by replacing the randomized coordinates of each node with coordinates that place the nodes in a the desired order, expanding from the center of the map.
Is there any way to find adjacent cells in a quadtree subdivision? I mean all the cell adjacent to the selected one at any level?
A space filling curves fills a space completley and reduces the 2 dimension to 1 dimension. I've written a free php class at phpclasses.org (hilbert curve). It includes a z curve, 4 hilbert curves and the moore curve and a quadkey function. Here is a blog about collision detection and quadtrees: lab.polygonal.de/?p=202?
A morton a.k.a. z-curve is easy to construct. Translate the x-and y-value to binary and concatenate the values. You can find some code here:http://msdn.microsoft.com/en-us/library/bb259689.aspx. You can verify the upper boundaries by using the most significant bits.
You need to keep track of which child the node is. If the adjacent node is in the same parent, just return it. If not, you need to walk upward in the tree until you can find a common ancestor. Then follow a similar path downwards until you come back to the correct level (or reach the bottom).
Node WalkLeft(Node node)
{
if (node == null) return null;
Node leftParent;
switch (node.ChildDirection)
{
case ChildDirection.Root:
return null;
case ChildDirection.TopRight:
return node.Parent.TopLeft;
case ChildDirection.BottomRight:
return node.Parent.BottomLeft;
case ChildDirection.TopLeft:
leftParent = WalkLeft(node.Parent);
return leftParent.TopRight ?? leftParent;
case ChildDirection.BottomLeft:
leftParent = WalkLeft(node.Parent);
return leftParent.BottomLeft ?? leftParent;
}
}
Similarly for the other directions.
x ?? y picks the first non-null value.