I'm in the process of visualizing a linked list. Just doing it for fun and to learn d3.js. I am able to create group elements for the circles (which represent a node). But I can't figure out why a new group element is not being added after that. I want this new group to contain the line/arrow (the .next reference representation). I debugged through CDT the code is being executed but nothing is being added to the DOM.
var g = svg.selectAll("g")
.data(dataArr)
.enter().append("g")
.attr("transform", function() {
if (addMethod === "headAdd") {
return "translate(" + 50 + " ," + y + ")";
} else {
return "translate(" + x + " ," + y + ")";
}
})
.attr("class", "nodes")
.attr("id", function(d) { return d.getData(); });
// Create a circle representing the node.
g.append("circle")
.attr("r", 40)
.style("fill", "#7DC24B")
.style("stroke", "#122A0A")
.style("stroke-width", 5)
.style("opacity", 0)
.style("stroke-opacity", 0.8)
.transition().duration(1000).style("opacity", 1);
// Add data to the node.
g.append("text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.style("fill", "#ffffff")
.text(function(d) { return d.getData(); });
// Create the "next" arrow.
// l is the line group that will enclose the line
// and the text.
// This doesn't seem to be working from here below.
var l = svg.selectAll("g")
.data(["1"])
.enter().append("g")
.attr("class", "lines")
.attr("transform", "translate(" + (x + 40) + ", " + y + ")");
l.append("line")
.attr("x1", x + 40)
.attr("y1", y)
.attr("x2", x + 100)
.attr("y2", y)
.style("stroke", "#8EBD99")
.style("stroke-width", 30);
The ...
var l = svg.selectAll("g")
.data(["1"])
... selects the existing group elements (i.e. the one you've just created) and performs a data join with those. The enter() selection will be empty since the elements you selected were already present in the DOM. Therefore the parts after .enter() will not be executed and the l selection will be empty.
You have to make the selector more specific, like:
var l = svg.selectAll(".lines")
.data(["1"])
The Thinking with Joins article does a good job in explaining the data join concepts. See also the General Update Patterns (I,II,III) for more details about the different kind of selections you'll encounter during data joins.
Related
[Sorry the title was quite badly formulated. I would change it if I could.]
I'm searching for a way to append text elements from a array or arrays in the data.
EDIT: I can already do a 1 level enter .data(mydata).enter(). What I'm trying here is a second level of enter. Like if mydata was an object which contained an array mydata.sourceLinks.
cf. the coments in this small code snippet:
var c = svg.append("g")
.selectAll(".node")
.data(d.nodes)
.enter()
.append("g")
.attr("class", "node")
.attr("transform", function(i) {
return "translate(" + i.x + "," + i.y + ")"
})
c.append("text")
.attr("x", -200)
.attr("y", 30)
.attr("text-anchor", "start")
.attr("font-size","10px")
.text(function(d){
// d.sourceLinks is an array of elements
// console.log(d.sourceLinks[0].target.name);
// Here I would like to apped('text') for each of the elements in the array
// and write d.sourceLinks[i].target.name in this <text>
})
;
I tried a lot of different things with .data(d).enter() but it never worked and I got lot's of errors.
I also tried to insert html instead of text where I could insert linebreaks (that's ultimately what I'm trying to achieve).
I also tried
c.append("foreignobject")
.filter(function(i) { // left nodes
return i.x < width / 2;
})
.attr('class','sublabel')
.attr("x", -200)
.attr("y", 30)
.attr("width", 200)
.attr("height", 200)
.append("body")
.attr("xmlns","http://www.w3.org/1999/xhtml")
.append("div");
but this never showed up anywhere in my page.
Your question was not exactly clear, until I see your comment. So, if you want to deal with data that is an array of arrays, you can have several "enter" selections in nested elements, since the child inherits the data from the parent.
Suppose that we have this array of arrays:
var data = [
["colours", "green", "blue"],
["shapes", "square", "triangle"],
["languages", "javascript", "c++"]
];
We will bind the data to groups, as you did. Then, for each group, we will bind the individual array to the text elements. That's the important thing in the data function:
.data(d => d)
That makes the child selection receiving an individual array of the parent selection.
Check the snippet:
var data = [
["colours", "green", "blue"],
["shapes", "square", "triangle"],
["languages", "javascript", "c++"]
];
var svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 200);
var groups = svg.selectAll("groups")
.data(data)
.enter()
.append("g")
.attr("transform", (d, i) => "translate(" + (50 + i * 100) + ",0)");
var texts = groups.selectAll("texts")
.data(d => d)
.enter()
.append("text")
.attr("y", (d, i) => 10 + i * 20)
.text(d => d);
<script src="https://d3js.org/d3.v4.min.js"></script>
Now, regarding your code. if d.nodes is an array of arrays, these are the changes:
var c = svg.append("g")
.selectAll(".node")
.data(d.nodes)
.enter()
.append("g")
.attr("class", "node")
.attr("transform", function(i) {
return "translate(" + i.x + "," + i.y + ")"
});//this selection remains the same
var myTexts = c.selectAll("myText")//a new selection using 'c'
.data(function(d){ return d;})//we bind each inner array
.enter()//we have a nested enter selection
.append("text")
.attr("x", -200)
.attr("y", 30)
.attr("text-anchor", "start")
.attr("font-size", "10px")
.text(function(d) {
return d;//change here according to your needs
});
You should use enter like this :
var data = ["aaa", "abc", "abd"];
var svg = d3.select("body").append("svg")
.attr("width", 200)
.attr("height", 200);
svg.selectAll("text")
.data(data)
.enter()
.append("text")
.attr("x", function(d,i) {
return 20 + 50 * i;
})
.attr("y", 100)
.text(function(d) { return d; });
See this fiddle : https://jsfiddle.net/t3eyqu7z/
I adapted Ger Hobbelt's excellent example of group/bundle nodes
https://gist.github.com/GerHobbelt/3071239
as a JSFiddle here:
https://jsfiddle.net/NovasTaylor/tco2fkad/
The display demonstrates both collapsible nodes and regions (hulls).
The one tweak that eludes me is how to add labels to expanded nodes. I have successfully added labels to nodes in my other force network diagrams using code similar to:
nodes.append("text")
.attr("class", "nodetext")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) {
// d.name is a label for the node, present in the JSON source
return d.name;
});
Is anyone familiar enough with Ger's example to guide me in the right direction?
On enter, instead of appending circle append a g with a circle and a text. Then re-factor a bit to fix the movement of the g instead of the circle. Finally, append write out the .text() only if the node has a name (meaning it's a leaf):
node = nodeg.selectAll("g.node").data(net.nodes, nodeid);
node.exit().remove();
var onEnter = node.enter();
var g = onEnter
.append("g")
.attr("class", function(d) { return "node" + (d.size?"":" leaf"); })
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
g.append("circle")
// if (d.size) -- d.size > 0 when d is a group node.
.attr("r", function(d) { return d.size ? d.size + dr : dr+1; })
.style("fill", function(d) { return fill(d.group); })
.on("click", function(d) {
expand[d.group] = !expand[d.group];
init();
});
g.append("text")
.attr("fill","black")
.text(function(d,i){
if (d['name']){
return d['name'];
}
});
And refactored tick to use g instead of circle:
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
Updated fiddle.
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; })
.on("click", click)
.on("mouseover",function (d){
if(d.name=="Purchased"){
jQuery.getJSON("RequestHandler?usercommand=jsoncompany&subcommand=propCount&useraction=d3Tree_frm&mgmtId="+d.id+"&type="+d.name, function(json){
var count=JSON.stringify(json.prop_purchased_count);
result="Purchased Property :"+count;
});
}
var g = d3.select(this);
var info = g.append("text")
.classed('info', true)
.attr('x', 30)
.attr('y', 30)
.text(result);
})
.on("mouseout", function() {
d3.select(this).select('text.info').remove();
});
i am using above code to display basic tooltip on mouse over of a node. The problem is that when i move from one node to another tooltip value is not updated quickely it show me previous value .if i move my cursor little bit and move again to that node then only it shows me correct value of that node.
How to resolve that issue?
I've used arc.Centroid to try to plot my circles on the arcs with labels. However, the labels do not stay with it?
force.on("tick", function() {
text.attr("x", function(d) { return d.x + 6; })
.attr("y", function(d) { return d.y + 4; });
node.attr("transform", function(d,i) {
return "translate(" + arc[i].centroid(d) + ")"; })
});
I have attempted to put centroid & arc[i] instead of the x & y. How can I put my circles with text? http://jsfiddle.net/xwZjN/20/
Also say if I were to have more json data, would I be able to restrict the plots only going into each section e.g. each section being a category?
Any help would be great. I think the solution may be similar to this - http://jsfiddle.net/nrabinowitz/GQDUS/
It seems that the force layout is not the right choice for your application. Try to group your symbol and text in a g element and place them at the calculated coordinates. See updated fiddle without force layout: http://jsfiddle.net/xwZjN/26/
var node = svg.selectAll("g.node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d,i) {
return "translate(" + arc[i].centroid() + ")";
});
node.append("path")
.attr("d", d3.svg.symbol().type(function(d) { return d.type; }))
// change (0,0) for exact symbol placement
.attr("transform", "translate(0,0)")
.style("fill", "blue" );
node.append("text")
.text(function(d) { return d.Name; })
// shift text in nice position
.attr("x", 10)
.attr("y", 5);
I am starting with d3.js, and am trying to create a row of nodes each of which contains a centered number label.
I am able to produce the desired result visually, but the way I did it is hardly optimal as it involves hard-coding the x-y coordinates for each text element. Below is the code:
var svg_w = 800;
var svg_h = 400;
var svg = d3.select("body")
.append("svg")
.attr("width", svg_w)
.attr("weight", svg_h);
var dataset = [];
for (var i = 0; i < 6; i++) {
var datum = 10 + Math.round(Math.random() * 20);
dataset.push(datum);
}
var nodes = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("class", "node")
.attr("cx", function(d, i) {
return (i * 70) + 50;
})
.attr("cy", svg_h / 2)
.attr("r", 20);
var labels = svg.append("g")
.attr("class", "labels")
.selectAll("text")
.data(dataset)
.enter()
.append("text")
.attr("dx", function(d, i) {
return (i * 70) + 42
})
.attr("dy", svg_h / 2 + 5)
.text(function(d) {
return d;
});
The node class is custom CSS class I've defined separately for the circle elements, whereas classes nodes and labels are not explicitly defined and they are borrowed from this answer.
As seen, the positioning of each text label is hard-coded so that it appears at the center of the each node. Obviously, this is not the right solution.
My question is that how should I correctly associate each text label with each node circle dynamically so that if the positioning of a label changes along with that of a circle automatically. Conceptual explanation is extremely welcome with code example.
The text-anchor attribute works as expected on an svg element created by D3. However, you need to append the text and the circle into a common g element to ensure that the text and the circle are centered with one another.
To do this, you can change your nodes variable to:
var nodes = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(dataset)
.enter()
// Add one g element for each data node here.
.append("g")
// Position the g element like the circle element used to be.
.attr("transform", function(d, i) {
// Set d.x and d.y here so that other elements can use it. d is
// expected to be an object here.
d.x = i * 70 + 50,
d.y = svg_h / 2;
return "translate(" + d.x + "," + d.y + ")";
});
Note that the dataset is now a list of objects so that d.y and d.x can be used instead of just a list of strings.
Then, replace your circle and text append code with the following:
// Add a circle element to the previously added g element.
nodes.append("circle")
.attr("class", "node")
.attr("r", 20);
// Add a text element to the previously added g element.
nodes.append("text")
.attr("text-anchor", "middle")
.text(function(d) {
return d.name;
});
Now, instead of changing the position of the circle you change the position of the g element which moves both the circle and the text.
Here is a JSFiddle showing centered text on circles.
If you want to have your text be in a separate g element so that it always appears on top, then use the d.x and d.y values set in the first g element's creation to transform the text.
var text = svg.append("svg:g").selectAll("g")
.data(force.nodes())
.enter().append("svg:g");
text.append("svg:text")
.attr("text-anchor", "middle")
.text(function(d) { return d.name; });
text.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
The best answer came from the asker himself:
just a further observation: with only .attr("text-anchor", "middle")
for each text element, the label is at the middle horizontally but
slightly off vertically. I fixed this by adding attr("y", ".3em")
(borrowed from examples at d3.js website), which seems to work well
even for arbitrary size of node circle. However, what exactly this
additional attribute does eludes my understanding. Sure, it does
something to the y-coordinate of each text element, but why .3em in
particular? It seems almost magical to me...
Just add .attr("text-anchor", "middle") to each text element.
Example:
node.append("text")
.attr("x", 0)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.text(function(d) { return d.name; });
This page describes what's going on under the svg hood when it comes to text elements. Understanding the underlying machinery and data structures helped me get a better handle on how I had to modify my code to get it working.