D3 force layout by node size - d3.js

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.

Related

d3.js Forcing Nodes away from bounding box

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

get point coordination using path.getPointAtLength after rotation in d3 and svg

i want get some points from path after rotating it
i use below code to rotate path
let path= svg.append("path")
.attr("class","offset control")
.attr("d", lineFunction(offsetLineData))
.style("stroke-width", 0.5)
.style("stroke", "red")
.style("fill", "none")
.attr("transform","rotate(90,"+p.x+","+p.y+")")
.attr('transform-origin',"center")
then i want get end point and start point of path
path.node().getPointAtLength(0)
but it return coordination where it don't rotated
how can i get x and y of point after rotation
Think of a SVG as a tree of nested coordinate systems. Every element has its own coordinates, and is fitted into its parent with a rule how to re-calculate them. These rules can be explicit transform attributes, but also implicit combinations of width, height and viewBox ("fit box A in size B").
A transform attribute is considered to be the link to the parent element. That means, if you ask for a geometric property of an element, most of the times you get the value before the transform is applied. After it is applied would be asking for geometric values in the coordinate system of the parent.
Because of this complexity there are several SVG API functions to find out how these coordinate systems fit together. getPointAtLength() gets you coordinates before applying the transform attribute.
var localPoint = path.node().getPointAtLength(0)
First, you have to find out what the transform attribute does. This looks a bit complicated, partly because the attribute can be animated and can contain a list of functions, partly because the API is...well...:
// construct a neutral matrix
var localMatrix = path.node().viewportElement.createSVGMatrix()
var localTransformList = path.node().transform.baseVal
// is there at least one entry?
if (localTransformList.length) {
// consolidate multiple entries into one
localMatrix = localTransformList.consolidate().matrix
}
You can then apply the found transformation to the point with
var transformedPoint = localPoint.matrixTransform(localMatrix)
There are several functions hat will return a SVGMatrix to transform data from the coordinate system after application of the transform attribute (i. e. after the step above):
to ask for the transformation to the nearest viewport (in most cases the nearest parent <svg>) element: element.getCTM()
to ask for the transformation to screen pixels: element.getScreenCTM()
to ask for the transformation to an arbitrary element: element.getTransformToElement(...)

D3 toggle between straight edges and multiple curved edges

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();
}

generating vertices for voronoi diagram in d3js

In the two examples in d3js website:
Easy Version
More complicated
I find some of their code hard to understand. The D3 reference API did not offer in depth explanation. In the easy version, the vertices were generated using Math.random() like so:
var vertices = d3.range(100).map(function(d) {
return [Math.random() * width, Math.random() * height];
});
Width and height are the document size. This ensure all vertices are within the scope of the SVG element ( svg tag has width and height attribute set to these values too).
However in the more complicated version, it uses an algorithm to generate the vertices:
var vertices = d3.range(numVertices).map(function(d) { return {x: d.x, y: d.y}; })
voronoiVertices = vertices.map(function(o){return [o.x, o.y, o]})
path = path.data(d3.geom.voronoi(voronoiVertices))
When examining the variable vertices in console. I found out that it is a two dimensional array. Each sub-array has a length of 3, first and second element are numbers, and the last element is an object which contains a lot more properties like x, y and index and so on.
The resulting Voronoi diagram are very different. Why is that? They are both generated using d3.geom.voronoi() and (from what I can hopefully understand) the only differences lies in the object that is passed into it. In the easy example it was simply a two dimensional array with 2 numbers in each sub-array. I cannot seem to see how the two lines in the complicated example works to create such a complex object and append it to the end of each sub-array.
How is it ensured that the veritices are within the bound of the parent SVG element too?
I am really stuck, thank you so much for any help.

Log the x,y coordinates of nodes in a converged D3 force layout

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.

Resources