Dynamically add rectangles on click event - javascript

I'm trying to add rectangles dynamically on-click event. The goal is once i click on a rectangle, additional rows with text will be added.
I have tried to use the script below:
svg.append("rect")
.attr("x", 0)
.attr("y", 31)
.attr("width", 250)
.attr("height", 30)
.attr("fill", "rgb(82,82,82)")
.attr("stroke-width", 0)
.on("click",function ()
{
MyDataSource= dataset.filter(function(d) {
return d.Topic== "MyTopic";
});
console.log("Button clicked");
var topics = svg.selectAll("g")
.data(MyDataSource)
.enter()
.append("g")
.attr("class","bar");
var rects = topics.append("rect")
.attr("y", function(d, i) {
return (i+2) * 31;
})
.attr("x", 0)
.attr("width", 250)
.attr("height",30)
.attr("fill", "rgb(8,81,156)")
.attr("stroke-width",0)
.append("title")
.text(function(d) {
return d.Topics;
});
var recttext = topics.append("text")
.attr("y", function(d,i) {
return (i+2)*31+20;
})
.attr("x", 150)
.text(function(d) {
return d.Topics;
}) ;
});
Once i click on that rectangle, the console would have "Button clicked" but nothing will happen on the page.
If i take away the script in the Onclick function and add it to the page default code, the rows would be added successfully when the page first loads.
How can I set this code to work correctly on the On-click event?
Thanks in advance

If you want to add new rectangles on every click, make sure you also add new data to MyDataSource. With the selection.data().enter().append() pattern, d3 compares the argument passed to data against whatever data is exists on selection. New elements are appended for all new data. Right now, the data is the same on every click (except the first), so d3 decides there is nothing to update. Thus, topics refers to an empty selection and your subsequent calls to append to it will not render anything.
However, if MyDataSource is not changing at all, you'd just end up with a long array that contains many copies of your data set. A better choice in this case is to use .datum() instead of .data(), which allows you to bind many elements to the same single datum. If that datum is an array, you can then iterate through it to create new elements and achieve the same end as .data() but a cleaner underlying data structure.

Related

D3:reflect data changes on visualization keeping the same array size. Trying to represent a sort algorithm visually

I'm trying to represent a selection sort visually with d3 to show some students and I'm having problems updating the data once the positions swap(I will add the transitions and delays once it's working). The positional attributes don't seem to be working as well, I don't know why, any ideas?. The codepen is here:
HTML:
<div id="canvas">
</div>
CSS:
rect{
border:1px solid black;
}
JS:
function selectionSort(array,svg){
//where the index will position
var positioningIndex=0;
var aux;
var minIndex;
var minVal=Number.MAX_VALUE;
while(positioningIndex<array.length){
//get the index of the mínimum
for(var i=positioningIndex;i<array.length;i++){
if(array[i]<minVal){
minIndex=i;
minVal=array[i];
}
}
//swap the mínimum to the positioningIndex
aux=array[minIndex];
array[minIndex]=array[positioningIndex];
array[positioningIndex]=aux;
//update visualization
svg.selectAll("rect").data(array);
minVal=Number.MAX_VALUE;
++positioningIndex;
}
return array;
}
var dataSet=[10,7,8,44];
var svg=d3.select("#canvas").selectAll("rect")
.append("svg")
.attr("width", 960)
.attr("height", 500);
var rect=svg.data(dataSet)
.enter()
.append("rect");
rect.text(function(el){
return el;
})
.attr("width", 30)
.attr("height", 30)
.attr("x", function(d, i) {
return i*5;
})
.attr("y", 30);
.style("color","green");
array=selectionSort(dataSet,svg);
You've got a lot of mixing up of html and svg elements going on there
First off, your svg element isn't getting appended:
var svg=d3.select("#canvas").selectAll("rect") // <- selectAll("rect") is causing problems
.append("svg")
No existing rect elements at the start means no svg's getting appended (do you mean to add one for each rect?) Edit: and in fact that one error is the cause of everything that happens afterwards - the selectAll("rect") needs moved to the line where elements are added to the svg - not on the line where the svg itself is added -->
var rect=svg.selectAll("rect").data(dataSet) // rect should really be g
.enter()
.append("rect");
Secondly, and because of the above error, the elements called 'rect' that are added (and added directly to the #canvas id div) aren't svg:rect objects - they're just html elements with the name 'rect' - see Is there a way to create your own html tag in HTML5?. The browser just treats them as inline elements, so none of your x's or y's make a difference they just line up one after the other
Finally, if this was svg you wouldn't be able to add text directly to a rect, you'd need to use a group (g) element and add both rect and text elements to that to keep them associated, and style("transform", translate(x,y)) the group element to move them around.
var g=svg.selectAll("g").data(dataSet) // <-- now changed from rect
.enter()
.append("g") // <-- and here
.attr ("transform", function(d,i) {
return "translate("+(i*35)+" 30)";
})
;
// texts n rects added here
g.append("text").text(function(el){
return el;
})
.attr("dy", "1em")
g.append("rect")
.attr("width", 30)
.attr("height", 30)
;
See http://codepen.io/anon/pen/bwJWEa?editors=1111

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!

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