I have a force network something similar to this :
http://jsfiddle.net/Brb29/7/
On the button press i translate the nodes to the same position :
function positionnodes(){
force.stop();
nodes.each(function(d){
d.fixed = true;
d.x = 100;
d.y = 100;
}).transition().duration(1000).attr("transform", function(d)
{
//console.log(d.hasRelationship);
//console.log(d.y);
return "translate(" + 100 + "," + 100 + ")";
});
edges.transition().duration(1000).attr("x1", function (d) {
console.log(d.source.x)
return d.source.x;
})
.attr("y1", function (d) {console.log(d.source.y)
return d.source.y;
})
.attr("x2", function (d) {console.log(d.target.x)
return d.target.x;
})
.attr("y2", function (d) {console.log(d.target.y)
return d.target.y;
});
//setTimeout(function(){ force.start();},1000);
}
My problem is, after i do this and i go to drag the nodes, the node position jumps back to its previous even though i have set it. It's as if the d.x/d.y hasn't updated.
Any ideas ?
The drag event will trigger force.start and then (x,y) of all nodes will be re-computed. To avoid triggering force.start, you need overwrite the event handler for force.drag, see
here
OK. Here is a working example: Working Example. The core part is here:
var drag = force.drag()
.origin(function(d) {
var t =
d3.transform(d3.select(this).attr("transform")).translate;
return {x: t[0], y: t[1]};
})
.on("drag.force", function(d) {
var cord = [0,0];
cord = d3.mouse(this);
d.x = cord[0];
d.y = cord[1];
tick();
});
Related
I want to drag a node to re-adjust my collapsible tree, because the data after the node is big length wise.
How can I achieve this.
I have this code written in my script file. The below code is for force.layout. How can I achieve same result in tree.layout.
// drag code
var node_drag = d3.behavior.drag()
.on("dragstart", dragstart)
.on("drag", dragmove)
.on("dragend", dragend);
function dragstart(d, i) {
force.stop() // stops the force auto positioning before you start dragging }
function dragmove(d, i) {
d.px += d3.event.dx;
d.py += d3.event.dy;
d.x += d3.event.dx;
d.y += d3.event.dy;
tick(); // this is the key to make it work together with updating both px,py,x,y on d !
}
function dragend(d, i) {
d.fixed = true; // of course set the node to fixed so the force doesn't include the node in its auto positioning stuff
tick();
force.resume();
}
function dblclick(d) {
console.log('Double clicked. ');
d3.select(this).classed("fixed", d.fixed = false);
}
function tick(d) {
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; });
node.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
};
Let me preface this by saying I'm not even 100% sure that this should work. I totally apologize for ignorance in this subject; it's something I'm very new to.
I am using a Force Layout like this:
var force = d3.layout.force()
.charge(-1500)
.linkDistance(250)
.size([parseInt(svg.style('width')), parseInt(svg.style('height'))]);
Later on down the road I'm binding nodes like this:
var node = svg.selectAll(".node")
.data(scope.nodes);
var nodeg = node.enter().append("g")
.attr("class", "node")
.on('click', function (n) {
d3.select(this)
.attr('transform', 'translate(' + n.x + ', ' + n.y + ') scale(1.5)')
})
.call(force.drag);
The code I'm trying to implement is this:
.on('click', function (n) {
d3.select(this)
.attr('transform', 'translate(' + n.x + ', ' + n.y + ') scale(1.5)')
})
I thought, based on some research I've done, that this would cause the node to scale or zoom when clicked. However, even though the code executes, nothing is happening. When looking at the Elements page I don't even see the transform added.
I wonder if this is because of the constantly changing transform applied by the Force Layout? It's effectively overriden maybe?
At any rate, the goal is to get this node to scale. It has an image and a couple text objects inside of it added like this:
nodeg.append("image")
.attr("xlink:href", function (d) {
return d.avatar || 'https://github.com/favicon.ico'
})
.attr("x", -56)
.attr("y", -8)
.attr("width", 64)
.attr("height", 64);
nodeg.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.attr('class', 'name')
.text(function (d) {
var name;
if (d._id === scope.user.profile._id) {
name = 'You';
} else if (d.firstName) {
name = d.firstName;
if (d.lastName) {
name += ' ' + d.lastName;
}
} else if (d.lastName) {
name = d.lastName;
}
return name;
});
nodeg.append("text")
.attr("dx", 12)
.attr("dy", "1.35em")
.text(function (d) {
return d.relationship;
});
tick function
force.on("tick", function () {
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;
});
node.attr("transform", function (d) {
return "translate(" + d.x + "," + d.y + ")";
});
});
Thanks to Lars, once again, I've got a beautiful solution! I added a new function that builds the appropriate transform:
function getNodeTransform(n) {
var transform = 'translate(' + n.x + ', ' + n.y + ')';
if (n.fixed === true) {
console.log('Adding scale to node:', n);
transform += ' scale(1.4)';
}
return transform;
}
The next step was to use that in both the click and the tick:
.on('click', function (n) {
n.fixed = !n.fixed;
d3.select(this)
.transition('bounce')
.attr('transform', getNodeTransform(n));
})
force.on("tick", function () {
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;
});
node.attr("transform", function (d) {
return getNodeTransform(d);
});
});
This allows me to select nodes even while the layout is settling.
I'm trying to apply the fisheye effect to a force layout with collision detection, node dragging, zoom etc but am struggling to integrate a fisheye with my tick function.
The closest existing example I found was http://bl.ocks.org/bentwonk/2514276 but it doesn't really contain a custom tick function or enable the usual things like dragging a node while fisheye is applied. For the sake of clarity, here's my tick function regardless of fisheye...
function tick(e) {
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;})
node.each(gravity(.2 * e.alpha))
.each(collide(jitter))
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
}
And here's what I'm hoping to use on my svg listener to trigger fisheye...
svg.on("mousemove", function() {
fisheye.focus(d3.mouse(this));
node
.each(function(d) { d.fisheye = fisheye(d); })
.attr("cx", function(d) { return d.fisheye.x; })
.attr("cy", function(d) { return d.fisheye.y; })
.attr("r", function(d) { return d.fisheye.z * 8; });
link.attr("x1", function(d) { return d.source.fisheye.x; })
.attr("y1", function(d) { return d.source.fisheye.y; })
.attr("x2", function(d) { return d.target.fisheye.x; })
.attr("y2", function(d) { return d.target.fisheye.y; });
});
But when using both together I notice no fisheye effect - presumably tick() is overriding any changes from my svg listener (although even if I add force.stop() to my svg listener I still see no fisheye on the page or any console errors).
And of course if I replace the tick function, then the whole node layout won't compute properly to start with.
Apologies that I don't have a more specific question, but does anyone have any thoughts on the best way to approach combining these 2 behaviours so that I can use a fisheye effect without compromising on other force layout functionality?
Thanks!
EDIT:
My only thought so far is to simply use the svg listener to trigger the mouse position...
svg.on("mousemove", function() {
//force.stop();
fisheye.focus(d3.mouse(this));
});
And then have some kind of coordinate addition in the tick function...
node.each(gravity(.2 * e.alpha))
.each(collide(jitter))
.each(function(d) { d.fisheye = fisheye(d); })
.attr("r", function(d) { return d.fisheye.z * 8; })
.attr("transform", function(d) {
return "translate(" + d.x + d.fisheye.x + "," + d.y + d.fisheye.y + ")";
});
(the above doesn't work though - just produces invalid coordinates so all nodes end up at the top-left of the screen)
I am just starting on D3, so if anyone has any general suggestions on thing I might not be doing correctly/optimally, please let me know :)
I am trying to create a Force Directed graph with the nodes spaced out evenly (or close enough) around the center root node (noted by the larger size).
Here's an example of the layout I'm trying to achieve (I understand it won't be the same every time):
I have the following graph:
var width = $("#theVizness").width(),
height = $("#theVizness").height();
var color = d3.scale.ordinal().range(["#ff0000", "#fff000", "#ff4900"]);
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
var svg = d3.select("#theVizness").append("svg")
.attr("width", width)
.attr("height", height);
var loading = svg.append("text")
.attr("class", "loading")
.attr("x", width / 2)
.attr("y", height / 2)
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text("Loading...");
/*
ForceDirectData.json
{
"nodes":[
{"name":"File1.exe","colorGroup":0},
{"name":"File2.exe","colorGroup":0},
{"name":"File3.exe","colorGroup":0},
{"name":"File4.exe","colorGroup":0},
{"name":"File5.exe","colorGroup":0},
{"name":"File6.exe","colorGroup":0},
{"name":"File7.exe","colorGroup":0},
{"name":"File8.exe","colorGroup":0},
{"name":"File8.exe","colorGroup":0},
{"name":"File9.exe","colorGroup":0}
],
"links":[
{"source":1,"target":0,"value":10},
{"source":2,"target":0,"value":35},
{"source":3,"target":0,"value":50},
{"source":4,"target":0,"value":50},
{"source":5,"target":0,"value":65},
{"source":6,"target":0,"value":65},
{"source":7,"target":0,"value":81},
{"source":8,"target":0,"value":98},
{"source":9,"target":0,"value":100}
]
}
*/
d3.json("https://dl.dropboxusercontent.com/u/5772230/ForceDirectData.json", function (error, json) {
var nodes = json.nodes;
force.nodes(nodes)
.links(json.links)
.linkDistance(function (d) {
return d.value * 1.5;
})
.charge(function(d){
var charge = -500;
if (d.index === 0) charge = 0;
return charge;
})
.friction(0.4);
var link = svg.selectAll(".link")
.data(json.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", 1);
var files = svg.selectAll(".file")
.data(json.nodes)
.enter().append("circle")
.attr("class", "file")
.attr("r", 10)
.attr("fill", function (d) {
return color(d.colorGroup);
});
var totalNodes = files[0].length;
files.append("title")
.text(function (d) { return d.name; });
force.start();
for (var i = totalNodes * totalNodes; i > 0; --i) force.tick();
nodes[0].x = width / 2;
nodes[0].y = height / 2;
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; });
files.attr("cx", function (d) { return d.x; })
.attr("cy", function (d) { return d.y; })
.attr("class", function(d){
var classString = "file"
if (d.index === 0) classString += " rootFile";
return classString;
})
.attr("r", function(d){
var radius = 10;
if (d.index === 0) radius = radius * 2;
return radius;
});
force.on("tick", function() {
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; });
files.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
loading.remove();
});
JSFiddle
I have already tried getting close to this with the charge() method. I thought giving every node besides the root node a higher charge would accomplish this, but it did not.
What can I do to have the child nodes evenly spaced around the root node?
Yes, force layout is a perfect tool for situations like yours.
You just need to change a little initialization of the layout, like this
force.nodes(nodes)
.links(json.links)
.charge(function(d){
var charge = -500;
if (d.index === 0) charge = 10 * charge;
return charge;
});
and voila
Explanation. I had to remove settings for friction and linkDistance since they affected placement in a bad way. The charge for root node is 10 times larger so that all other nodes are dominantly pushed away from the root. Other nodes also repel each other, and the perfect symmetry is achieved at the end, as a result.
Jsfiddle is here.
I see from your code that you attempted to affect distance from the root node and other nodes by utilizing linkDistance that is dependant on data. However, it might be better (although counter-intuitive) to use linkStrength for that purpose, like this
force.nodes(nodes)
.links(json.links)
.linkStrength(function (d) {
return d.value / 100.0;
})
.charge(function(d){
var charge = -500;
if (d.index === 0) charge = 10 * charge;
return charge;
});
but you need to experiment.
For centering and fixing the root node, you can use this
nodes[0].fixed = true;
nodes[0].x = width / 2;
nodes[0].y = height / 2;
but before initialization of layout, like in this Jsfiddle.
so, here I am with another of my questions:
I have a network set up in D3. Its nodes are all unfixed but one. All unfixed can be dragged wherever you want, but within the svg. To provide this I test the new coordinates at the end of the script. But if I move the fixed node all the other are dragged together with this one.
My problem is, that the nodes seem to get their coordinates relative to the parent object, which, in this case, is a g-Elements that provides the group-dragging. So after dragging they still have the restrictions, but they are shifted together with the group. Any useful hints welcome :)
And here are parts of my code:
var node = group.selectAll(".node")
.data(reqNodes)
.enter().append("g")
.attr("transform", function(d) { return "translate(0,0)"; })
.attr("class", function(d) {
return d.fixed ? 'node fixed' : 'node not-fixed';
})
var CurrentGTransformX = 0;
var CurrentGTransformY = 0;
group.selectAll('.not-fixed')
.call(force.drag);
// attach a different drag handler to the fixed node
var groupDrag = d3.behavior.drag()
.on("drag", function(d) {
// mouse pos offset by starting node pos
var x = window.event.clientX - 420,
y = window.event.clientY - 260;
group.attr("transform", function(d) { return "translate(" + x + "," + y + ")"; });
//that was the line getting in conflict with the part in the end
CurrentGTransformX = x;
CurrentGTransformY = y;
});
group.call(groupDrag);
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)); });
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; });
node.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
});
edit: the transformation of the group is the reason why the restrictions in the end get other data than displayed. Any idea how to serve both functions in the proper way?