Calling D3.JS function on 'load' rather than on mouse event - javascript

I am trying to make modifications to the D3 sankey example here:
http://bl.ocks.org/d3noob/c2637e28b79fb3bfea13
I want to shift the y-position of each node to a specified location (300px in the example below).
The most straight forward way I can to see to achieve this is to simply repurpose dragmove() to be called after the the SVG elements have been added. I've made changes to d.y in this function to shift to 300px:
function dragmove(d) {
d3.select(this).attr("transform",
"translate(" + d.x + "," + (
d.y = 300
) + ")");
sankey.relayout();
link.attr("d", path);
}
This function is called when adding the nodes:
var node = svg.append("g").selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; })
.call(d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", function() {
this.parentNode.appendChild(this); })
.on("drag", dragmove));
At present, it shifts to 300px as expected on the specified mouse event, but I want it to shift on its own after all the SVG elements have been added instead.
Simply using .call() without the mouse event doesn't work.
I've also tried to incorporate the shift in var node instead:
return "translate(" + d.x + "," + 300 + ")"; })
However this leads to a mismatch between the leads and the nodes, and calling sankey.relayout() doesn't seem to make a difference.

Found a solution.
First I removed .call() at the end of var node, as I don't need the drag event:
var node = svg.append("g").selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"; });
Then I set d.y to the arbitrary location (300):
return "translate(" + d.x + "," + (d.y = 300) + ")"; });
Finally I forced it to re-draw the connecting links immediately after var node.
sankey.relayout();
link.attr("d", path);

Related

How to get innerHTML attribute value "r" in this code?

I'm using D3.js for my personal project.
But, I faced with some troubles.
bubble.nodes(root)
svg = d3.select("svg").attr("class", "bubble")
var node = svg.selectAll(".node")
.data(bubble.nodes(root)
.filter(function(d) {
return !d.children;
}))
.enter()
.append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
In the picture, I want to get "r" value in the circle.
How can I get this value?
Also, this circle is one part of bubble chart node.
Using vanilla JS... getElementsByTagName('circle'), then getAttribute('r') to find the value of r
let circle = document.getElementsByTagName('circle');
let target = circle[0].getAttribute('r');
console.log(target);
<circle r="0.75847397430597"></circle>

trouble understanding d3 tree node transitions

I am modifying a open source angular library, angular-d3-tree. I am modifying because I want to make my nodes render as rectangles. I got the rectangle part worked out but I cannot figure out how to keep the rectangles from over lapping:
I have noticed these transform attributes getting add to nodes like this:
var nodeEnter = node.enter().append('g')
.attr('class', 'node')
.attr("transform", function(d) {
return " translate(" + source.y0 + "," + source.x0 + ")";
//return 'translate(' + source.y + ',' + (source.x + 150) + ')';
// return 'translate(' + source.y + ',' + (source.x + 150) + ')';
// return "translate(" + source.y0 + "," + (source.x0 + 150) + ")";
});
.
.
.
var nodeUpdate = nodeEnter.merge(node);
nodeUpdate.transition()
.duration(this.duration)
.attr("transform", function(d) {
console.log('RBC trans d is:');
console.log(d);
return "translate(" + d.y + "," + d.x + ")";
// return "translate(" + d.y + "," + d.x + ")";
// return "translate(" + d.y + "," + (d.x + 150) + ")";
});
Anytime I make a change to the transform attributes either nothing changes or I just make things worse. Could some please explain to me how I can keep my rectangles from overlapping? I am just grasping at straws here.
Thanks!
Using a static modification of the transform will only shift every node in one direction or the other. However, we can use d3.tree to avoid overlap out of the box, assuming we know the size of the rectangles (or max size).
D3.tree offers two methods for sizing:
tree.size([width,height]), this will take your tree and place its nodes within a box of the provided dimensions. It makes no assumptions about the size of each node: the more nodes and/or the larger the nodes are, the more likely there is overlap. With this method the tree size is specified and nodes are positioned accordingly.
tree.nodeSize([width,height]), this takes a tree and places nodes so that they are spaced in a manner that gives each node the specified size. The overall size of the tree is dictated by the structure of the tree and the spacing of the nodes. With this method the node spacing is specified and the tree is sized accordingly.
*With the second approach the root node is placed at [0,0], this requires a transform to be applied on all nodes to place them properly. The first approach does not require this since the dimension of the plot area is known and the root is placed properly.
width and height are in this order for a vertical tree, horizontal trees will be reversed.
If we know the size of the rectangles, we can set nodeSize to those dimensions, plus some additional margin to allow the connecting paths to be seen. For example, adopting this block, we can convert it to use rectangular nodes by:
specifying a node size
appending rectangles rather than circles
reposition the text slightly
and change the translate on the parent g so [0,0] is half way down the page on the left (where our root node will appear):
var data = { "name": "Parent", "children": [
{ "name": "Child A", "children": [ { "name": "Grandchild" } ] },
{ "name": "Child B", }
] };
var width = 800;
var height = 200;
margin = {left: 100, top: 0, right: 50, bottom: 50}
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var g = svg.append("g").attr('transform','translate('+ margin.left +','+ (height/2) +')');
var root = d3.hierarchy(data);
var tree = d3.tree()
.nodeSize([50,100]); // because we are using a horizontal tree this is [height,width] of each node
var link = g.selectAll(".link")
.data(tree(root).links())
.enter().append("path")
.attr("class", "link")
.attr("d", d3.linkHorizontal()
.x(function(d) { return d.y; })
.y(function(d) { return d.x; }));
var node = g.selectAll(".node")
.data(root.descendants())
.enter().append("g")
.attr("class", function(d) { return "node" + (d.children ? " node--internal" : " node--leaf"); })
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
node.append("rect")
.attr("width", 80)
.attr("height", 40)
.attr("x", -40) // half of width
.attr("y", -20) // half of height
.attr("rx",5)
.attr("ry",5)
.attr("fill","steelblue")
node.append("text")
.text(function(d) { return d.data.name; })
.attr('y',5)
.attr('text-anchor','middle');
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
Here's a full size version.

d3 scaling basic shapes

I'm trying to dynamically scale some shapes based on the data. Here's what I have so far:
svg.selectAll(".point")
.data(data)
.enter()
.append("path")
.attr("class", "point")
.attr("d", d3.svg.symbol().type("triangle-up"))
.attr("transform", function(d) { return "translate(" + x(d.x) + "," +
y(d.y) + ")"; });
Is there another attr that I can add? I tried to use the following but not working:
.attr("transform", "scale(xMap)");
xMap is a value in data scaled to a range.
You can do the translate and the scale in the same anonymous function. Besides that, you have to concatenate the string with the number. The way you're doing...
"scale(xMap)"
... you're passing literally "xMap" to the scale().
Thus, it should be:
.attr("transform", function(d) {
return "translate(" + x(d.x) + "," + y(d.y) + ") scale(" + xMap + ")";
});

D3 Dragging on SVG "Path" Element Breaks when d.x is defined, hidden datum?

I'm a beginner to D3/Javascript.
I have a working D3 script where a bunch of path elements are drawn and can be dragged around.
However, when I add seemingly unrelated code elsewhere, that sets the d.x and d.y of the data (to its proper values BTW) the dragging breaks. The element jumps, so that it starts off some distance away and needs to be dragged back to its original place.
(The undesirable "jumping" is orderly way, consistent with a linear transformation of the mouse coordinates)
The "offending" code that seems to cause this behavior is:
hexdata.forEach(function(d) {
d["x"] = grid_x(d);
d["y"] = grid_y(d.grid_y);
});
The code that constructs the nodes and path that works without the code above is:
var node = svg.selectAll('g')
.data(hexdata)
.enter()
.append("g")
.call(d3.drag()
.on("drag", dragged))
.attr("class", "node");
node.append('path')
.attr("d", function (d) {
hex_alignment = (d.grid_y%2==1) ? hexRadius : 0
return "M" + (d.x *hexRadius*2 + 100) + "," + ((d.y*1.75 +100)) + hexPath;
})
function dragged(d) {
d3.select(this).attr("transform", "translate(" + d3.event.x + "," + d3.event.y + ")");
}
Does anyone know what is going on?
As mentioned in the comments, setting the origin(v3)/subject(v4) will fix this.
However, if you don't want (for any reason) to set the origin(v3)/subject(v4) in the drag function, simply change the property names for something else, like a and b. We know that x and y is the most common choice for naming the coordinates, but it will cause a conflict in the drag function (whose explanation is beyond the scope here, since you said you are a beginner).
This is easy to show. Here is a simple code using x and y, drag the circle around: it will jump.
var svg = d3.select("svg");
var circle = svg.append("circle")
.datum({
x: 150,
y: 75
})
.attr("transform", d => "translate(" + d.x + "," + d.y + ")")
.attr("r", 10)
.call(d3.drag()
.on("drag", dragged))
function dragged(d) {
d3.select(this).attr("transform", "translate(" + d3.event.x + "," + d3.event.y + ")");
}
svg{
border: 1px solid gray;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
Now the same code, using a and b. The circle will not jump:
var svg = d3.select("svg");
var circle = svg.append("circle")
.datum({
a: 150,
b: 75
})
.attr("transform", d => "translate(" + d.a + "," + d.b + ")")
.attr("r", 10)
.call(d3.drag()
.on("drag", dragged))
function dragged(d) {
d3.select(this).attr("transform", "translate(" + d3.event.x + "," + d3.event.y + ")");
}
svg{
border: 1px solid gray;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
Another alternative is reassigning d.x and d.y in the drag function:
d3.select(this).attr("transform", "translate(" + (d.x = d3.event.x) + ","
+ (d.y = d3.event.y) + ")");
Here is the code:
var svg = d3.select("svg");
var circle = svg.append("circle")
.datum({
x: 150,
y: 75
})
.attr("transform", d => "translate(" + d.x + "," + d.y + ")")
.attr("r", 10)
.call(d3.drag()
.on("drag", dragged))
function dragged(d) {
d3.select(this).attr("transform", "translate(" + (d.x = d3.event.x) + "," + (d.y = d3.event.y) + ")");
}
svg{
border: 1px solid gray;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>

Updating the coordinates in d3js for a tree layout

I am currently using the d3.layout.tree() to compute the positions of my data.
var tree = d3.layout.tree()
.sort(null)
.size([size.height, size.width - maxLabelLength * options.fontSize])
.children(function(d)
{
return (!d.contents || d.contents.length === 0) ? null : d.contents;
});
Initially I compute and add my nodes like this:
var nodes = tree.nodes(treeData);
var nodeGroup = layoutRoot.selectAll("g.node")
.data(nodes, function (d) { return d.name })
.enter()
.append("svg:g")
.attr("class", "node")
.attr("transform", function(d)
{
return "translate(" + d.y + "," + d.x + ")";
});
nodeGroup.append("svg:circle")
.attr("class", "node-dot")
.attr("r", options.nodeRadius);
Now I add a new node to the treeData and also to the layoutRoot:
var grp = layoutRoot.selectAll("g.node")
.data(nodes, function (d) { return d.name })
.enter()
.append('svg:g')
.attr("transform", function (d)
{
return "translate(" + d.y + "," + d.x + ")";
})
grp.append("svg:circle")
.attr("class", "node-dot")
.attr("r", options.nodeRadius)
The problem is now, that the newly computed nodes that are already present in the rootLayout have different x,y coordinates after having added a new node. But they are not within the enter() or exit() selection and are thus not redrawn at their correct position. How is this supposed to be handled, ie. how should the position of the nodes that have not changed anything but their coordinates be updated/refreshed?
I a noob to d3js. So don't be too harsh :D
I would separate the enter() selection from the update of nodes like this :
var nodeGroup = layoutRoot.selectAll("g.node")
.data(nodes, function (d) { return d.name });
// Enter selection
nodeGroup.enter()
.append("svg:g")
.attr("class", "node")
// Update
nodeGroup.attr("transform", function(d) {
return "translate(" + d.y + "," + d.x + ")";
});
var nodeDots = layoutRoot.selectAll("g.node-dot")
.data(nodes, function (d) { return d.name });
// Enter
nodeDots.enter()
.append("circle")
.attr("class", "node-dot")
// Update
nodeDots.attr("r", options.nodeRadius);
Hope this helps, but in a general way of speaking, it is perhaps easier to code this way, with separation of enter and updates (see here for more info)

Categories