Thanks to this earlier question, I produced a static fixed-layout graph as below using force layout in d3.js:
and I have two specific questions to further customize the layout:
First, I notice that initializing nodes positions deterministically (e.g. done diagonally here, see the script for details) fixes the positions of the nodes, and the orientation of the nodes seem to depend on this initialization as well as the dimensions of the force graph*. I wonder how can I make the nodes A, D, E, F, I in the graph above aligned horizontally? In other words, I want to turn the orientation of the graph anti-clockwise for roughly 45 degrees. I have tried to initialize the nodes horizontally in the middle:
nodes.forEach(function(d, i) { d.x = w / size * i; d.y = h / 2; });
but the produced output has all the nodes and edges horizontally at where they were initialized.
Second, is it true that the force graph is automatically centered within the svg element? If no, how can we make it so? If yes, how can we specify a center for the force graph within the svg element?
(* Note: strangely, when setting .size([w, h]) where w = h for the force graph, and deterministically initializing the nodes along a diagonal, all the nodes and edges are positioned along that diagonal in the output, why?)
Try adding a custom gravity force applied on each tick which pulls the nodes towards the horizontal line. Something like this:
force.on('tick', function(e) {
nodes.forEach(function(d) {
d.y += (height/2 - d.y) * e.alpha;
});
})
To your second question, I believe, it is not centered, but there is a small gravity force by default which pulls the nodes towards the center.
Related
I'm making a diagramming library in Blazor which uses HTML nodes and SVG links. I am wondering how can I draw links between two nodes when they aren't always rectangular.
All the solutions I find are based on nodes that are rectangles/squares, where it's easy to draw a link on the borders (or even the center, but only works for direct links).
But what about nodes that have custom stuff in them that makes them non rectangular, for example a div with border-radius: 50%?
One possible solution is to draw the lines from/to the center of the elements, but that would only work with simple lines, curved lines would look weird.
In this example:
How does arrow position get calculated?
You need to have an container, width and height of the container, then inside the container find the x / y point of the element that you want to connect and draw a line to the next elements x / y point, the x/y points can be calculated using x,y,w,h of the element, for an example x:100 y:100 w:100 h:100 the center point sits at x:150, y:150 x = x + ( w / 2 ), y = y + ( h / 2 ).. using math just calculate the point of connection of the elements, the complexity of math for calculating the connection point is in the shape of the element, for each different shape you need a different calculation metod if not in center
I have a swarm plot that shows ranges from 0 to 1 on a linear x axis and size via the SVG circle's radius attribute. Snippet below:
<script src="https://d3js.org/d3.v5.min.js"></script>
I have had modest success by setting the strength to a low value and forceCollide to a high value:
var simulation = d3.forceSimulation(data)
.force("x", d3.forceX(function(d) {
return xScale(d.pequity);
}).strength(0.01))
.force("y", d3.forceY(function(d) {
return 100;
}).strength(0.01))
.force("collide", d3.forceCollide(10).iterations(1))
.stop();
However there are many instances where the circles are all lumped together.
My intended goal is to have each circle stand apart (not overlapping), but at the same time I want the circles to be placed closed together. I imagine this would entail variable force settings, not sure how small circles next to big circles should be handled (and vice versa).
Question
How can I adjust my force logic to account for variable radius sizes in my swarm circles such that each circle is close but not engulfed by another circle?
I am trying to implement a radial force layout in D3.js , I saw a similar example but i am stuck on how to initiate the node positions in the layout.
http://bl.ocks.org/vlandham/5087480
Thanks in Advance
Initialising a position is just done by setting the cx and cy positions. The most logical place is where the radius is currently being set i.e.
.attr("r", 10)
.attr("cx", 5) //added
.attr("cy", 5) //added
Of course, you can do something more exotic if you are using the bound data to initialise position.
This will only set the starting point though - the force layout will then take over and position elements. The advantage is that you can potentially reduce some of the initial node movement if you get it right.
I have a bit of an annoying problem.
I'm trying to position a bunch of SVG circle elements according to an existing bunch of SVG text elements that share similar properties.
The circle elements are created in a very separate process than the text elements, so positioning the new elements just using the same transforms etc. as the old one isn't a viable option.
I'm trying to use .getBoundingClientRect() to get the positions since the text elements are transformed into position (so .getBBox() isn't an option) rather than positioned by x and y attributes.
With .getBoundingClientRect(), I can get the correct size/arrangement of the new elements, but since the width of the svg-containing div is variable, there's always a bit of a weird offset that I can't quite account for.
I created a simplified example of my issue here. Resize and refresh the page to see the issue in action.
The code I use to position the circle elements is replicated below.
var circs = theSvg.selectAll("circle")
.data(theCircles)
.enter()
.append("circle")
.attr("r", 15)
.attr("fill", "#f00")
.style("opacity", 0.3)
.attr("transform", function(d){
var sizeDif = 800/(d3.select(".svgTestHolder")[0][0].getBoundingClientRect()["width"]);
var theNum = parseInt(d.split("&")[1]);
var thePosition = theSvg.selectAll("text").filter(function(e){
return e == theNum;})[0];
var theCoords = thePosition[0].getBoundingClientRect();
var leftOffset = d3.select(".svgTestHolder")[0][0].getBoundingClientRect()["left"];
var leftOffset2 = d3.select(".svgTest")[0][0].getBoundingClientRect()["left"];
var bottomOffset = d3.select(".svgTestHolder")[0][0].getBoundingClientRect()["top"];
var bottomOffset2 = d3.select(".svgTest")[0][0].getBoundingClientRect()["top"];
return
"translate(" + ((theCoords["left"] - leftOffset - leftOffset2)
* sizeDif) + "," + ((theCoords["top"] - bottomOffset - bottomOffset2)
* sizeDif) + ")";
})
EDIT:
This is a very delayed update just to note that while I was unable to answer my question as stated, I was able to make a workable solution based on Paul LeBeau's suggestion to extract the transforms from the target element.
In my case, I had to use a series of consecutive transforms rather than a combination of transforming and changing the x/y position (due to certain realities of the project not represented in the linked example). But I'm happy to have found an answer!
Your example works fine for me on Chrome. But really that's only because the SVG is the only thing on the page. If I add some text above the SVG everything goes wrong.
https://jsfiddle.net/rrpfmm6d/1/
Is this the problem you are talking about?
If so, the reason is because you are making the wrong choice in using getBoundingClientRect(). It provides coordinates in screen space. It's origin is the top left of the window (or iframe in the case of jsfiddle).
You should be using getBBox(). The values it returns are in the same coordinate space as the SVG elements. It's origin is (normally) at the top left of the SVG.
In summary, use the coordinates returned by calling getBBox() on your <text> element to calculate the position for your circle. If the circles are inserted into the same SVG as the text, there will be no need to do any adjusting with the div or svg offsets.
I have the same problem as this topic : D3 Tree Layout Separation Between Nodes using NodeSize
I have tried the solution, but there is another problem, the root is always on the left and when I collapse nodes, they are out of the screen.
the solution is based ond d3.js and on http://bl.ocks.org/mbostock/4339083 but when I set a nodeSize, the root doesn't dynamically move to optimized position.
So How can I have space between my "rect" nodes and a dynamically replace of root node to optimize display ?
Sorry for my bad english !
Thanks for your help
You can try something like this :
// Normalize for fixed-depth.
nodes.forEach(function(d) { d.y = d.depth * 450; });
Modify the value 450 to increase/decrease the x axis distance between the nodes