Adding label to D3 path - javascript

I know how to add text element to simple node (append text). The problem is when I would like to add text to path surrounding several nodes. I have created example on http://jsfiddle.net/FEM3e/5/ Please ignore nodes in upper left corner. So I have two groups of nodes. And I would like to add text for each group. Printscreen of desired output http://dopisna.bencin.si/screenshot.png.
I set path in
force.on("tick", function () {
node.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
vis.selectAll("path")
.data(groups)
.attr("d", singlePath)
.enter().insert("path", "g")
.style("fill", groupFill)
.style("stroke", groupFill)
.style("stroke-width", 57)
.style("stroke-linejoin", "round")
.style("opacity", .7);
});
I have tried appending text with no success. I am asking for some hints.

OK then. The problem is that you're using text instead of textPath. I've modified your fiddle and now there's some text, albeit some rather ugly text, appended to your path.
The only real change I've made is the addition of this snippet:
vis.selectAll("text")
.data(groups)
.enter()
.append("text")
.attr("x", 8)
.attr("dy", 28)
.append("textPath")
.attr("xlink:href", function (d,i) { return "#path_" + i; })
.text(function (d,i) { return "path_" + i; });
You can see that you go through the usual selection and data binding. You then append your text with the attributes you want (definitely change the ones I borrowed from Mikes Bl.ock) and then you append the text path linking it to a path element in the xlink:href attribute. Obviously you then create some text. One of the cool things about textPath is that it allows you append curved paths.
I think that there's a bit of overkill using the groups as data for the textPath, so you might want to select a more appropriate data selection to bind to this.

Related

How to update node data in a D3.js force graph?

I'm looking for a way to update multiple node attributes and appended elements on data change.
Here is how I'm trying to do it right now.
This function gets called every-time node or link data changes.
function restart() {
link = link.data(links);
link
.enter().insert("line", ".node")
.attr("class", "link")
.on('click' , function(d, i){
console.log(d);
links.splice(i,1);
restart();
})
.on("mouseover", function() {
d3.select(this).style("stroke","red");
d3.select(this).style("stroke-width","5px");
})
.on("mouseout", function() {
d3.select(this).style("stroke","#999");
d3.select(this).style("stroke-width","initial");
});
link
.exit().remove();
node = node.data(nodes);
node
.enter()
.append("g")
.attr("class", "node")
.call(node_drag);
node
.insert("circle", ".cursor")
.attr("r", function(d) { return calcSize(d.links+1); })
node
.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.links });
node
.exit().remove();
force.start();
}
The full code at the current state can be seen here: http://jsbin.com/takatugazo/edit?html,js,output
The expected behaviour is that when two nodes get dragged into each other they create a link and the more links they have the bigger they get.
Your appended text IS updating. The only issue seems to be that the instead of replacing the old text, it's adding a <text> element on top. If you just inspect a node, you'll find multiple <text> elements based on how many links it has. For some reason your code doesn't update the text the way it should.
Try removing the text element before you append one:
node.select("text").remove()
node
.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text(function(d) { return d.links });
node
.exit().remove();
I made those changes in JS Bin and it seems to work for me (with a bit of a delay): http://jsbin.com/goqumutelu/edit?html,js,output
There is a delay in the dragend() function whenever is a link is pushed (not sure if intentional?) So if you don't want the delay: removing the timeout, or simply replacing the delay value (1500) to 0 should solve that: http://jsbin.com/hazecozumu/1/edit?html,js,output
About the size of the nodes, what's wrong it? How else did you want it to update?

How to change d3.js text entry from within that text's onclick method?

My AngularJS app uses d3.js to draw a nice chart.
While drawing this chart, it uses paints some text on the screen.
I want to change that text when someone clicks on it based on the boolean value of myCondition. This is how I do it:
var nodeEnter = node.enter()
var myLabel = nodeEnter.append("text")
.attr("x", 50)
.attr("dy", "3")
.text("Hello World")
.style("fill-opacity", 0)
.style("cursor", "pointer")
.on("click", function(d) {
if (myCondition)
myLabel.text("Mars");
else
myLabel.text("Venus");
}
);
It sorta works. The value of the text does indeed change from Hello World to Mars or Venus. But there is a problem. This code is called within a recursive function and within a loop. That recursion + loop use the same code to draw numerous such texts on the SVG Container. So when I click this label, not only does it change the text that I want. It also changes the text in other places too! I don't want that. How can I prevent it?
I really just need a way I can address this or myself from within the click function so it knows I'm talking about the object. How?
Without knowing your recursive function and the loop, I'll try two different approaches, I hope that one of them works.
The first one is using this for the click event:
var myLabel = nodeEnter.append("text")
.attr("x", 50)
.attr("dy", "3")
.text("Hello World")
.style("fill-opacity", 0)
.style("cursor", "pointer")
.on("click", function(d) {
if (myCondition)
d3.select(this).text("Mars");
else
d3.select(this).text("Venus");
}
);
If this doesn't work, you can try to set a specific class to your different myLabel texts. Doing this, even if you have several myLabel in your SVG, each one has a unique class. Suppose that index is a specific value for the loop (like i). So, you can try:
var myLabel = nodeEnter.append("text")
.attr("x", 50)
.attr("dy", "3")
.attr("class", "myLabel" + index)//index is any value unique for the loop
.text("Hello World")
.style("fill-opacity", 0)
.style("cursor", "pointer")
.on("click", function(d) {
if (myCondition)
d3.selectAll(".myLabel" + index).text("Mars");
else
d3.selectAll(".myLabel" + index).text("Venus");
}
);

Loop through SVG circles in directed graph

I have been going through some code I found online for creating and playing with directed graphs in D3 (http://bl.ocks.org/cjrd/6863459). I asked a question about this yesterday - Directed graph - node level CSS styles and that gave me a general idea of how to add CSS styles to SVG objects. However, I am still unable to do what I want. This is because, in the JS file, they seem to use the "nodes" to create "circles" and then render them all in one go instead of looping through them. In the updateGraph function, we have the lines -
// add new nodes
var newGs= thisGraph.circles.enter()
.append("g");
newGs.classed(consts.circleGClass, true)
.attr("transform", function(d){return "translate(" + d.x + "," + d.y + ")";})
.on("mouseover", function(d){
if (state.shiftNodeDrag){
d3.select(this).classed(consts.connectClass, true);
}
})
.on("mouseout", function(d){
d3.select(this).classed(consts.connectClass, false);
})
.on("mousedown", function(d){
thisGraph.circleMouseDown.call(thisGraph, d3.select(this), d);
})
.on("mouseup", function(d){
thisGraph.circleMouseUp.call(thisGraph, d3.select(this), d);
})
.call(thisGraph.drag);
First of all, I am not sure what the .append("g") means here. But more importantly, the line where the CSS class is applied,
newGs.classed(consts.circleGClass, true)
seems to apply the class to all "circles" in one line. Instead, I want to loop through each node and for the circle of that node, apply a CSS style based on attributes of the node (to keep things simple, lets say that it the "title" starts with a certain text, I want to make it a blue circle). I still have no idea how to do this. Can someone help here? Again, the answers to my previous question helped a lot in understanding CSS but this other issue is still blocking me from doing what I want.
Adding comments for more clarity.
// here thisGraph.circles is data selection
//so if the data array has 10 elements in array it will generate 10 g or groups.
var newGs= thisGraph.circles.enter()
.append("g");
//here we are adding classes to the g
newGs.classed(consts.circleGClass, true)
.attr("transform", function(d){return "translate(" + d.x + "," + d.y + ")";})
//attaching mouse event to the group
.on("mouseover", function(d){
if (state.shiftNodeDrag){
d3.select(this).classed(consts.connectClass, true);
}
})
.on("mouseout", function(d){
d3.select(this).classed(consts.connectClass, false);
})
.on("mousedown", function(d){
thisGraph.circleMouseDown.call(thisGraph, d3.select(this), d);
})
.on("mouseup", function(d){
thisGraph.circleMouseUp.call(thisGraph, d3.select(this), d);
})
.call(thisGraph.drag);//attaching drag behavior to the group
What does this line mean?
newGs.classed(consts.circleGClass, true)
This line means to add class to all the created g DOM element or group.
In the code you referring it means circleGClass: "conceptG"
Read this on how to add CSS to DOM in D3
In the code you are appending circle to the group like this
newGs.append("circle")
.attr("r", String(consts.nodeRadius));
So now each group will have a circle.
Next Question
I want to loop through each node and for the circle of that node, apply a CSS style based on attributes of the node
You can iterate through all the circles and add style depending on the data associated with the node like this.
newGs.append("circle")
.attr("r", String(consts.nodeRadius))
.style("fill", function(d){
if(d)//some condition on data
{
return "red";
}
else
return "blue";
});
Question:
if you could tell me how to add CSS classes instead of "red", "blue" it would be every thing I need.
To add class you can do like this.
newGs.append("circle")
.attr("r", String(consts.nodeRadius))
.attr("class", function(d){
function(d){
if(d)//some condition on data
{
return "red";//this will put class red in the node.
}
else
return "blue";//this will put class blue in the node.
});
Another way of doing the same:
newGs.append("circle")
.attr("r", String(consts.nodeRadius))
.classed({
'red': function(d) { return d.condition1 == "something"; },
'blue': function(d) { return d.condition1 != "something"; }
});
Hope this helps!

How to add label to edges in d3 graph

Please see
http://bl.ocks.org/rkirsling/5001347
It shows some nodes and the edges between them. Can you tell what code to add in that and where so that the edges have labels. You can assume any suitable location for the labels and you can also assume any label text.
Thank you.
You can add labels just as you add the paths for the links themselves. All you need to do is calculate the position according to the positions of the two nodes the link connects. The code would look something like this.
svg.selectAll("text").data(links).enter()
.append("text")
.attr("x", function(d) { return d.source.x + (d.target.x - d.source.x)/2; })
.attr("y", function(d) { return d.source.y + (d.target.y - d.source.y)/2; })
.text(function(d) { return d.something; });
Note that in your tick function, you would also need to update the position of the labels.

Creating a dynamic list of DIVs with D3

I have been using D3 to create fancy animated charts, and the examples are great. However, I'm trying to do something seemingly a lot more basic, and having trouble - binding data to a simple list of DIVs.
I set up enter() to initialize elements at opacity 0, transition() to fade them in, and exit() to fade them out and remove them. enter() and exit() seem to be working fine - however, when an update contains an existing element already in the list, it seems to get partially removed - the containing DIV remains, but the contents disappear. I can't understand why the contents of the element would get changed in this way.
My code is as follows:
var data = [...];
sorted = data.sort(function(a, b) { return d3.descending(a.id, b.id); });
var tweet = tweetsBox
.selectAll('div')
.data(sorted, function(d) { return d.id; });
var enterDiv = tweet.enter()
.append("div")
.attr("class", "tweetdiv")
.style("opacity", 0);
enterDiv.append("div")
.attr("class", "username")
.text(function(d) { return "#" + d.username });
enterDiv.append("div")
.attr("class", "displayname")
.text(function(d) { return d.displayname });
enterDiv.append("div")
.attr("class", "date")
.text(function(d) { return d.date });
enterDiv.append("div")
.attr("class", "text")
.text(function(d) { return d.text });
tweet.transition()
.delay(200)
.style("opacity", 1);
tweet.exit()
.transition()
.duration(200)
.style("opacity", 0)
.remove();
I also set up a jsFiddle here demonstrating the issue.
The problem is that you're selecting the divs you created, but create more than one div per data element. When updating, d3 tries to match the data to the nested divs. As you're already assigning a special class to the top-level divs, the fix is very simple. Replace
.selectAll('div')
with
.selectAll('.tweetdiv')

Categories

Resources