Reputation: 3833
I have a working D3 example. I use the force and everything works fine. But I have a small issue. Let's say I have 4 Nodes. Just see this picture: https://i.sstatic.net/HwniH.png and now when I click on the Node "AKZO NV" then I want to get: https://i.sstatic.net/85oBp.png with the old nodes and links.
So at the end I want to have 7 Nodes. And the "AKZO NV" shall be focused. All Nodes still have their links and the "AKZO NV" shall have two. I think you now know what I want.
So I already have this code. Working like a charm but it's not properly adding the new nodes and links. I think there is a small issue with the order of the commands.
Any ideas are welcome:
var alreadyThere = false;
var nodeCircles = {};
var svg, link, node;
var force = d3.layout.force();
var nodes, links;
var width = 700, height = 400;
var boxIDName = "#main-rightinfo";
var JSONFORMERGING;
function createRealGraph(jsonData){
//console.log(jsonData);
if (alreadyThere == false){
JSONFORMERGING=jsonData;
initializeGraph(jsonData);
}else{
update(JSONFORMERGING.concat(jsonData));
}
alreadyThere = true;
}
function update(jsonData) {
console.log(jsonData);
jsonData.forEach(function(link) {
link.source = nodeCircles[link.source] || (nodeCircles[link.source] = {name: link.sourceName, ID: link.source, class: link.sourceClass});
link.target = nodeCircles[link.target] || (nodeCircles[link.target] = {name: link.targetName, ID: link.target, class: link.targetClass});
});
link = link.data(links);
link.enter().insert("line")
.attr("class", "link");
node = node.data(nodes);
node.enter().append("g")
.attr("class", "node")
.attr("r", 5)
.call(force.drag);
force
.nodes(d3.values(nodeCircles))
.links(jsonData)
.start();
nodes = force.nodes();
links = force.links();
}
function initializeGraph(jsonData){
jsonData.forEach(function(link) {
link.source = nodeCircles[link.source] || (nodeCircles[link.source] = {name: link.sourceName, ID: link.source, class: link.sourceClass});
link.target = nodeCircles[link.target] || (nodeCircles[link.target] = {name: link.targetName, ID: link.target, class: link.targetClass});
});
force
.nodes(d3.values(nodeCircles))
.links(jsonData)
.size([width, height])
.linkDistance(60)
.charge(-200)
.on("tick", tick)
.start();
nodes = force.nodes();
links = force.links();
svg = d3.select("#main-right")
.append("svg")
.attr("width", width)
.attr("height", height);
svg
.append("svg:defs").selectAll("marker")
.data(["end"])
.enter().append("svg:marker")
.attr("id", String)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 27)
.attr("refY", -0.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5")
.attr('fill', '#00b');
link = svg.selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", "link")
.attr("marker-end", "url(#end)");
node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", function(d) {click(d);})
.on("dblclick", function(d) {dblclick(d);})
.call(force.drag);
node
.append("image")
.attr("xlink:href", function(d) {if (d.class == "Person") {return "pics/node_person.png";} else {return "pics/node_appln.png";} })
.attr("x", -20)
.attr("y", -20)
.attr("width", 40)
.attr("height", 40);
node
.append("text")
.attr("x", 19)
.attr("dy", ".25em")
.text( function(d) {return d.name; });
function tick() {
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 + ")"; });
}
}
Upvotes: 0
Views: 860
Reputation: 3455
I'm working on the same problem and got a "solution". It is still a prototype, but maybe it helps you.
Every mouse click on a node calls a function and detects new nodes. They are all saved in nodes
and all active, it means all nodes on the display are in activeNodes
. All nodes belonging to the path from the root node to the clicked one are stored in pathNodes
.The result is, that only the active path is displayed inclusivly the children of the clicked one.
Hopefully it is clearly explained. For abetter understanding please look up the source code at: http://github.com/nextlevelshit/d3_nested_nodes
For something to play with check out: http://dailysh.it/github/d3_nested_nodes/
Here the snippet of my code:
/**
* Triggering mouse click start
*/
function mousedown() {
if (mousedown_node !== null) {
var pathNodes = findPathNodesTo(mousedown_node);
var point = d3.mouse(this),
node = {
id: nodes.length,
parent: mousedown_node.id
};
node.x = point[0];
node.y = point[1];
var newNodes = findNodesbyParentId(mousedown_node.id),
startingPoint = {
x: mousedown_node.x,
y: mousedown_node.y
};
for (var i = 0; i < pathNodes.length; i++) {
newNodes.push(pathNodes[i]);
pathNodes[i].path = true;
}
var removeNodes = activeNodes.diff(newNodes);
var addNodes = newNodes.diff(pathNodes).diff(activeNodes);
for (var i = 0; i < removeNodes.length; i++) {
removeNode(removeNodes[i].id);
}
for (var i = 0; i < addNodes.length; i++) {
addNodes[i].x = startingPoint.x;
addNodes[i].y = startingPoint.y;
activeNodes.push(addNodes[i]);
activeLinks.push({source: findNode(addNodes[i].parent), target: findNode(addNodes[i].id)});
}
// TODO: Find a smoother way do delay popping out new nodes
});
}
restart();
}
The last problem is to find a smooth way for popping out new nodes...
Upvotes: 0
Reputation: 3833
You just need to test if it's NOT an object. That works like a charm:
jsonData.forEach(function(link) {
if (typeof(link.source) != "object"){
link.source = nodeCircles[link.source] || (nodeCircles[link.source] = {name: link.sourceName, ID: link.source, class: link.sourceClass});
}
if (typeof(link.target) != "object"){
link.target = nodeCircles[link.target] || (nodeCircles[link.target] = {name: link.targetName, ID: link.target, class: link.targetClass});
}
});
Upvotes: 1