Parenting D3.js-Nodes for dragging - d3.js

I have a network of nodes and links. One of them has a fixed position in the center but is draggable, the others are in a force field around the centered one. If the user drags any node, the others will be draged behind him, because the are linked. Is there a possibility to drag the others with the centered node, but keeping the drag-event of the other nodes single?
thanks for thinking about it,
David
edit: if someone knew a possibility to set a dragg-listener for all the other nodes to the centered one, the problem would be solved. I'd be grateful if you had an idea!
Please leave me a comment which parts of th ecode could help you solve this issue, and I'll post it asap!
edit: with the help of nrabinowitz I can now move the nodes just as I wanted! But the new code-parts somehow crashed my coordinate-restrictions. For the nodes not to drop out of the svg, I put a cx/cy-attr to all nodes, preventing them from crossing the border of svg. This still works in the beginning, but after the first drag of the center-node (and therefore the 'g'-element) the restrictions seem to shift. Is there anything dragged except the svg?
The part of the script providing the restriction is
force.on("tick", function() {
node.attr("cx", function(d) { return d.x = Math.max(15, Math.min(width - 15, d.x)); })
.attr("cy", function(d) { return d.y = Math.max(15, Math.min(height - 15, d.y)); });
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
});

See working fiddle: http://jsfiddle.net/nrabinowitz/4Rj4z/
This encloses the nodes in a g element, and uses the transform attribute to move them around.
In order to get this to work, you can't use force.drag for the node you want to have pull the group - you need a custom d3.behavior.drag. Unfortunately, per this answer, you can't put the drag handler on the node itself - it needs to be on the group. This works, but it means that other elements without a separate drag handler - e.g. links - will also drag the group. You might be able to fix this with pointer-events on the group.

Related

How do I properly add transitions to D3 Polygons?

Original Code can be found at: http://bl.ocks.org/Guerino1/451f4c47842967dd813c8a64b24f7686
Problem: Applying .transition() code to different polygon sets appears to yield different results.
The following portion of the code seems to work as expected. Applying a transition causes the chevrons to transition onto the svg canvas, from left to right...
svgChevronsCanvas.selectAll("a")
.data(dataSet)
.enter().append("a")
.attr("xlink:href", function(d) { return d.link; })
.append("svg:polygon")
.attr("id", function(d,i){ return ("chevron_" + selectString.replace(/ /g,'_').replace(/#/g,'') + "_index_" + i); })
.attr("originalcolor", polygonPrimaryColor)
//.style("stroke","blue")
//.style("stroke-width",1)
.style("fill", polygonPrimaryColor)
.attr("points", chevronOrigin)
.on('mouseover', chevronMouseOver)
.on("mouseout", chevronMouseOut)
.on("click", chevronMouseOut)
.transition() // <------------------- TRANSITION HERE
.duration(3000)
.attr("points", calculateChevron);
The following code, which attempts to follow the same pattern as above does not seem to work as expected. Given the transition, I would expect the textboxes (in light blue below the chevrons), which are also drawn using D3 polygons, to transition onto their svg canvas from left to right, just like the chevron polygons in the above code...
svgTextboxesCanvas.selectAll("a")
.data(dataSet)
.enter().append("a")
.attr("xlink:href", function(d) { return d.link; })
.append("svg:polygon")
.attr("id", function(d,i){ return ("textbox_" + selectString.replace(/ /g,'_').replace(/#/g,'') + "_index_" + i); })
.attr("originalcolor", textboxColor)
.style("stroke", textboxColor)
.style("stroke-width",1)
.style("fill", textboxColor)
.attr("points", textboxesOrigin)
.on('mouseover', textboxMouseOver)
.on("mouseout", textboxMouseOut)
.on("click", textboxMouseOut)
.transition()
.duration(3000)
.attr("points", calculateTextbox);
Question: How do I properly add transitions to the D3 polygons that are built to look like rectangles (below the chevrons), in the latter set of code, and make them transition into the page just like the dark blue chevrons above them?
In the original code:
Make
var chevronGapSpace = 5;//this is the distance between each rectangle.
var slantDepth = 0;//to make the polygon rectangle.
Next, to make rectangle transition inside function calculateChevron change the calculations accordingly:
function calculateChevron(d, i){
return [
[(svgMargin) + i*(chevronDistance),chevronTopOffset],
[(svgMargin + chevronWidth - slantDepth) + i*(chevronDistance),chevronTop],
[(svgMargin + chevronWidth - slantDepth) + i*(chevronDistance),chevronPointY],
[(svgMargin + chevronWidth - slantDepth) + i*(chevronDistance),chevronBottom],
[(svgMargin) + i*(chevronDistance),chevronBottom],
[(svgMargin + slantDepth) + i*(chevronDistance),chevronPointY]
];
};
working code here

D3 add zoom to circle pack

Is it possible to add zooming to a circle pack? Seems like it should be but mine is jumping all around the place when zoom is clicked. I've been attempting to solve this for a few days but with little success.
I've been referencing Mike's Zoomable Circle Packing block (#7607535) and nilanjenator's block (#4950148). Other examples seem to be based on these two. Here's a fiddle of my work in progress: http://jsfiddle.net/cajmcmahon/9weovdm2/5/.
From what I can make out, my layout problems lie in these two functions:
t.selectAll("circle")
.attr("cx", function(d) {
return x(d.x);
})
.attr("cy", function(d) {
return y(d.y);
})
.attr("r", function(d) {
return k * d.r;
});
t.selectAll("text")
.attr("x", function(d) {
return x(d.x);
})
.attr("y", function(d) {
return y(d.y);
})
.style("opacity", function(d) { return k * d.r > 20 ? 1 : 0; });
Also, I can't get the viz to reset (zoom out?) when I click on the background. I believe it's not getting values for 'data'...
//Reset when click on background
d3.select(window).on("click", function(d, i) {
zoom(data)
});
Thanks for any help.
Ok both examples:
Have same output but they are implemented differently.
http://bl.ocks.org/nilanjenator/4950148: This one relies on changing the cx and cy of the circle for moving and updating the radius for zoom effect.
http://bl.ocks.org/mbostock/7607535: This one relies on translate to move the circles.
So in your example: you mixed both of it and thus you got a different flavor of circle packing.
You created circles and moved it into their position using translate but in the zoom section you made use of changing the cx and cy, as a result your circles flew out of the pack on zooming.
I have removed the translate and gave the cx and cy so the zoom function remain the same as what you have written.
node.append("circle")
.attr("r", function(d) {
return d.r;
})
.attr("class", function(d) { return d.children ? "parent" : "child"; })
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.on("click", function(d) {
zoom(node == d ? root : d);
});
I have changed the fiddle you gave here is a working example:
http://jsfiddle.net/cyril123/khq21pgb/2/
Hope this is helpful!

Animation with Data Joins d3

I'm trying to animate a data join. I can get the state to change and update but there isn't any seamless animation between states.
I tried to refer to this to answer my question:
d3.js trying to animate data, but changed data always treated as new
This data is created upon json load...and then, on a button click, i'm just simply adding 20 ( for testing purposes ) to my y values...it updates but there is no transition. What am i selecting incorrectly?
The variable bar is already defined globally.
function btnClick(){
//bar.exit().remove();
bar = chart.selectAll("g")
.data(mydata.players)
console.log(bar.data(mydata.players))
bar.exit().remove()
bar.enter().append("g")
.transition()
.duration(1650)
.attr("transform", function (d) {
return "translate(" + x(d.name) + ",0)";
});
bar.append("rect")
.attr('width', x.rangeBand())
.attr("y", function (d) {
return y(d.money+20)
})
.attr("height", function (d) {
return height - y(d.money+20);
})
Trying to move the transition before the height change:
chart.selectAll("g")
.data(mydata.players);
bar.exit().remove();
bar.attr("transform",
function(d) {
return "translate(" + x(d.name) + ",0)";
});
bar.append("rect")
.attr('width', x.rangeBand())
.transition()
.attr("y", function(d) {
return y(d.money + 20)
})
.attr("height", function(d) {
return height - y(d.money + 20);
})
I think i'm just not grabbing the right selection for bar in btnClick(). Maybe I'm not supposed to grab all the 'g' elements? And the bar is defined on json load earlier so I'm not certain I should even have to define it again by selecting all the g elements....if it's already been built. I have stored it and should be able to manipulate it: but if I don't define it, then for some reason the exit.remove() doesn't work.
Ok, I made the small necessary changes to your code to have this working. So, two things to note:
you want to select your rectangles for update:
bar.select("rect")
.attr('width', x.rangeBand())
.transition().duration(750).ease("linear")
.attr("y", function (d) {
return y(d.money + 20)
})
.attr("height", function (d) {
return height - y(d.money + 20);
})
you want to re-set your y domain to accommodate for the new data values (you really don't have a new set of data, you are just adding 20...so, do the same here):
y.domain([0, d3.max(mydata.players, function (d) {
return d.money + 20;
})])
NOTE: your tallest bar will not move since its value puts it at the top of the old and new scales (the domain is based on the data values). To see all bars rise, you can inflate the domain of your original data (say, + 20). I placed a comment where you can do this.
Here is the complete FIDDLE.

D3 Data updates not working correctly

I thought I understood the D3 enter/update/exit, but I'm having issues implementing an example. The code in http://jsfiddle.net/eamonnmag/47TtN/ illustrates what I'm doing. As you'll see, at each 5 second interval, I increase the rating of an item, and update the display again. This works, in some way. The issue is in only updating what has changed - D3 is updating everything in this case. The enter and exit methods, displayed in the console output that nothing has changed, which makes my think that it's treating each array as a completely new instance.
My understanding of the selectAll() and data() calls was that it would 'bind' all data to a map called 'chocolates' somewhere behind the scenes, then do some logic to detect what was different.
var chocolate = svg.selectAll("chocolates").data(data);
In this case, that is not what's happening. This is the update code. Any pointers to what I've missed are most appreciated!
function update(data){
var chocolate = svg.selectAll("chocolates").data(data);
var chocolateEnter = chocolate.enter().append("g").attr("class", "node");
chocolateEnter.append("circle")
.attr("r", 5)
.attr("class","dot")
.attr("cx", function(d) {return x(d.price)})
.attr("cy", function(d) {
//put the item off screen, to the bottom. The data item will slide up.
return height+100;})
.style("fill", function(d){ return colors(d.manufacturer); });
chocolateEnter
.append("text")
.text(function(d) {
return d.name;})
.attr("x", function(d) {return x(d.price) -10})
.attr("y", function(d){return y(d.rating+step)-10});
chocolateEnter.on("mouseover", function(d) {
d3.select(this).style("opacity", 1);
}).on("mouseout", function(d) {
d3.select(this).style("opacity", .7);
})
chocolate.selectAll('circle')
.transition().duration(500)
.attr('cy', function(d) {return y(d.rating+step)});
var chocolateExit = chocolate.exit().remove();
chocolateExit.selectAll('circle')
.attr('r', 0);
}
setInterval(function() {
chocolates[3].rating = Math.min(chocolates[3].rating+1, 5);
update(chocolates);
}, 5000);
Easy as apple pie!
Why are you doing svg.selectAll("chocolates")? There is no HTML element in your DOM called chocolates.
You need to change that to svg.selectAll(".node"). That will fix the problem.
There are a couple of issues in your code. First, the logic to detect what's different is, by default, to use the index of the item. That is, the first data item is matched to the first DOM element, and so on. This works in your case, but will break if you ever pass in partial data. I would suggest using the second argument to .data() to tell it how to match:
var chocolate = svg.selectAll("g.node").data(data, function(d) { return d.name; });
Second, as the other poster has pointed out, selecting "chocolate" will select nothing, as there are no such DOM elements. Just select the actual elements instead.
Finally, since you're adding g elements for the data items, you might as well use them. What I mean is that currently, you're treating the circles and text separately and have to update both of them. You can however just put everything underneath g elements. Then you have to update only those, which simplifies your code.
I've made all the above changes in your modified fiddle here.

Show social graph with D3 force layout

I want to show a social graph with D3(Date-Driven-Documents) with three hundred nodes
The Json data looks like this.
{
"nodes":[
{"name":"node1","group":1},
{"name":"node2","group":1},
{"name":"node3","group":1},
{"name":"node4","group":2},
{"name":"node5","group":2},
{"name":"node6","group":2}
],
"links":[
{"source":1,"target":0,"value":1},
{"source":2,"target":0,"value":1},
{"source":1,"target":2,"value":1},
{"source":3,"target":4,"value":1},
{"source":3,"target":5,"value":1},
{"source":4,"target":5,"value":1},
{"source":1,"target":3,"value":1}
]
}
Now I meet several problems
When I use node.append("image") for every node ,the fps is so low (just like pause and skip)!
The graph always goes far away from center , after I use the d3.layout.force().charge(whatever number).distance(10).charge(-100).size([width,height])
Now I want to show a social graph with groups , nodes have profile_image and name ! how to make my graph clear ? Need a lot of advise and examples ,Thanks!
For question 2, use a Bounding Box. A good example can be found here in the tick function.
node.attr("cx", function(d) { return d.x = Math.max(r, Math.min(w - r, d.x)); })
.attr("cy", function(d) { return d.y = Math.max(r, Math.min(h - r, d.y)); });
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
This prevents the nodes from moving beyond the bounding box.
Also, you can set the gravity to keep the nodes centralized. As Lars mentioned in his comment, there is no code listed to help you further.

Resources