So i've been working on a collapsible force-directed graph based of the following example.
Im trying to advance from that and add titles to each node. I've followed a similar answer here on stackoverflow, but I cannot implement the solution from that answer into the example above and other similar solutions.
Please can someone point me in the correct direction.
See project code below before implementation.
JS
var w = 600,
h = 600,
radius = 10,
node,
link,
root;
var force = d3.layout.force()
.on("tick", tick)
.charge(function(d) { return -500; })
.linkDistance(function(d) { return d.target._children ? 100 : 50; })
.size([w, h - 160]);
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
root = words[0]; //set root node
root.fixed = true;
root.x = w / 2;
root.y = h / 2 - 80;
update();
function update() {
var nodes = flatten(root),
links = d3.layout.tree().links(nodes);
// Restart the force layout.
force
.nodes(nodes)
.links(links)
.start();
// Update the links…
link = svg.selectAll(".link")
.data(links);
// Enter any new links.
link.enter().insert("svg:line", ".node")
.attr("class", "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; });
// Exit any old links.
link.exit().remove();
// Update the nodes…
node = svg.selectAll("circle.node")
.data(nodes)
.style("fill", color);
node.transition()
.attr("r", radius);
node.append("title")
.text(function(d) { return d.name; });
// Enter any new nodes.
node.enter().append("svg:circle")
.attr("class", "node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", radius)
.style("fill", color)
.on("click", click)
.call(force.drag);
// Exit any old nodes.
node.exit().remove();
}
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("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
// Color leaf nodes orange, and packages white or blue.
function color(d) {
if(d._children){
return "#95a5a6";
}else{
switch(d.group) {
case 'r': //adverb
return "#e74c3c";
break;
case 'n': //noun
return "#3498db";
break;
case 'v': //verb
return "#2ecc71";
break;
case 's': //adjective
return "#e78229";
break;
default:
return "#9b59b6";
}
}
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update();
}
// Returns a list of all nodes under the root.
function flatten(root) {
var nodes = [], i = 0;
function recurse(node) {
if (node.children) node.size = node.children.reduce(function(p, v) { return p + recurse(v); }, 0);
if (!node.id) node.id = ++i;
nodes.push(node);
return node.size;
}
root.size = recurse(root);
return nodes;
}
CSS
circle.node {
cursor: pointer;
stroke: #34495e;
stroke-width: 2px;
box-sizing: border-box;
stroke-location: inside;
}
line.link {
fill: none;
stroke: #34495e;
stroke-width: 1.5px;
}
HTML
<!DOCTYPE html>
<body>
<script src=".v2.min.js?2.9.6"></script>
<script>
var words = [
{
"group":"n",
"word":"main node",
"children":[
{
"group":"n",
"name":"sub node 1"
},
{
"group":"n",
"name":"sub node 2"
},
{
"group":"n",
"name":"sub node 3"
},
{
"group":"v",
"name":"sub node 4"
},
{
"group":"s",
"name":"sub node 5"
},
{
"group":"s",
"name":"sub node 6"
},
{
"group":"s",
"name":"sub node 7"
},
{
"group":"s",
"name":"sub node 8"
},
{
"group":"s",
"name":"sub node 9"
},
{
"group":"s",
"name":"sub node 10"
},
{
"group":"s",
"name":"sub node 11"
},
{
"group":"r",
"name":"sub node 12",
"children":[
{
"group":"r",
"name":"sub sub node 1"
},
{
"group":"r",
"name":"sub sub node 2"
},
{
"group":"r",
"name":"sub sub node 3"
}
]
}
]
}
]
</script>
</body>
JSFiddle
So i've been working on a collapsible force-directed graph based of the following example.
Im trying to advance from that and add titles to each node. I've followed a similar answer here on stackoverflow, but I cannot implement the solution from that answer into the example above and other similar solutions.
Please can someone point me in the correct direction.
See project code below before implementation.
JS
var w = 600,
h = 600,
radius = 10,
node,
link,
root;
var force = d3.layout.force()
.on("tick", tick)
.charge(function(d) { return -500; })
.linkDistance(function(d) { return d.target._children ? 100 : 50; })
.size([w, h - 160]);
var svg = d3.select("body").append("svg")
.attr("width", w)
.attr("height", h);
root = words[0]; //set root node
root.fixed = true;
root.x = w / 2;
root.y = h / 2 - 80;
update();
function update() {
var nodes = flatten(root),
links = d3.layout.tree().links(nodes);
// Restart the force layout.
force
.nodes(nodes)
.links(links)
.start();
// Update the links…
link = svg.selectAll(".link")
.data(links);
// Enter any new links.
link.enter().insert("svg:line", ".node")
.attr("class", "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; });
// Exit any old links.
link.exit().remove();
// Update the nodes…
node = svg.selectAll("circle.node")
.data(nodes)
.style("fill", color);
node.transition()
.attr("r", radius);
node.append("title")
.text(function(d) { return d.name; });
// Enter any new nodes.
node.enter().append("svg:circle")
.attr("class", "node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", radius)
.style("fill", color)
.on("click", click)
.call(force.drag);
// Exit any old nodes.
node.exit().remove();
}
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("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
}
// Color leaf nodes orange, and packages white or blue.
function color(d) {
if(d._children){
return "#95a5a6";
}else{
switch(d.group) {
case 'r': //adverb
return "#e74c3c";
break;
case 'n': //noun
return "#3498db";
break;
case 'v': //verb
return "#2ecc71";
break;
case 's': //adjective
return "#e78229";
break;
default:
return "#9b59b6";
}
}
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update();
}
// Returns a list of all nodes under the root.
function flatten(root) {
var nodes = [], i = 0;
function recurse(node) {
if (node.children) node.size = node.children.reduce(function(p, v) { return p + recurse(v); }, 0);
if (!node.id) node.id = ++i;
nodes.push(node);
return node.size;
}
root.size = recurse(root);
return nodes;
}
CSS
circle.node {
cursor: pointer;
stroke: #34495e;
stroke-width: 2px;
box-sizing: border-box;
stroke-location: inside;
}
line.link {
fill: none;
stroke: #34495e;
stroke-width: 1.5px;
}
HTML
<!DOCTYPE html>
<body>
<script src="http://d3js/d3.v2.min.js?2.9.6"></script>
<script>
var words = [
{
"group":"n",
"word":"main node",
"children":[
{
"group":"n",
"name":"sub node 1"
},
{
"group":"n",
"name":"sub node 2"
},
{
"group":"n",
"name":"sub node 3"
},
{
"group":"v",
"name":"sub node 4"
},
{
"group":"s",
"name":"sub node 5"
},
{
"group":"s",
"name":"sub node 6"
},
{
"group":"s",
"name":"sub node 7"
},
{
"group":"s",
"name":"sub node 8"
},
{
"group":"s",
"name":"sub node 9"
},
{
"group":"s",
"name":"sub node 10"
},
{
"group":"s",
"name":"sub node 11"
},
{
"group":"r",
"name":"sub node 12",
"children":[
{
"group":"r",
"name":"sub sub node 1"
},
{
"group":"r",
"name":"sub sub node 2"
},
{
"group":"r",
"name":"sub sub node 3"
}
]
}
]
}
]
</script>
</body>
JSFiddle
Share Improve this question edited May 23, 2017 at 10:30 CommunityBot 11 silver badge asked Nov 5, 2014 at 0:22 Oliver EvansOliver Evans 9892 gold badges20 silver badges45 bronze badges2 Answers
Reset to default 2Add titles to node like this.
title = svg.selectAll("text.title")
.data(nodes);
title.enter()
.append("text") //In your code you used title instead of text
.attr("class", "title")
.text(function(d) { return d.name; });
title.exit().remove();
Note that titles should be appended after circle nodes. Or else titles may cut off.
Also update the position of title in tick function.
title.attr("transform", function(d){ return "translate("+d.x+","+d.y+")"; });
Here is the jsfiddle
Interestingly, it is kind of working as a snippet here with your code as is, but only after you've collapsed or expanded a node. This should give you some idea of where your problem is. Basically, you're adding the title element to each node before the nodes actually exist. Move the
node.append("title")
.text(function(d) { return d.name; });
to after the
node.enter().append("svg:circle")
.attr("class", "node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", radius)
.style("fill", color)
.on("click", click)
.call(force.drag);
call.
Here is a snippet with that change pleted:
var words = [{
"group": "n",
"word": "main node",
"children": [{
"group": "n",
"name": "sub node 1"
}, {
"group": "n",
"name": "sub node 2"
}, {
"group": "n",
"name": "sub node 3"
}, {
"group": "v",
"name": "sub node 4"
}, {
"group": "s",
"name": "sub node 5"
}, {
"group": "s",
"name": "sub node 6"
}, {
"group": "s",
"name": "sub node 7"
}, {
"group": "s",
"name": "sub node 8"
}, {
"group": "s",
"name": "sub node 9"
}, {
"group": "s",
"name": "sub node 10"
}, {
"group": "s",
"name": "sub node 11"
}, {
"group": "r",
"name": "sub node 12",
"children": [{
"group": "r",
"name": "sub sub node 1"
}, {
"group": "r",
"name": "sub sub node 2"
}, {
"group": "r",
"name": "sub sub node 3"
}]
}]
}]
var w = 600,
h = 600,
radius = 10,
node,
link,
root;
var force = d3.layout.force()
.on("tick", tick)
.charge(function(d) {
return -500;
})
.linkDistance(function(d) {
return d.target._children ? 100 : 50;
})
.size([w, h - 160]);
var svg = d3.select("#viz").append("svg")
.attr("width", w)
.attr("height", h);
root = words[0]; //set root node
root.fixed = true;
root.x = w / 2;
root.y = h / 2 - 80;
update();
function update() {
var nodes = flatten(root),
links = d3.layout.tree().links(nodes);
// Restart the force layout.
force
.nodes(nodes)
.links(links)
.start();
// Update the links…
link = svg.selectAll(".link")
.data(links);
// Enter any new links.
link.enter().insert("svg:line", ".node")
.attr("class", "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;
});
// Exit any old links.
link.exit().remove();
// Update the nodes…
node = svg.selectAll("circle.node")
.data(nodes)
.style("fill", color);
node.transition()
.attr("r", radius);
// Enter any new nodes.
node.enter().append("svg:circle")
.attr("class", "node")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", radius)
.style("fill", color)
.on("click", click)
.call(force.drag);
node.append("title")
.text(function(d) {
return d.name;
});
// Exit any old nodes.
node.exit().remove();
}
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("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
}
// Color leaf nodes orange, and packages white or blue.
function color(d) {
if (d._children) {
return "#95a5a6";
} else {
switch (d.group) {
case 'r': //adverb
return "#e74c3c";
break;
case 'n': //noun
return "#3498db";
break;
case 'v': //verb
return "#2ecc71";
break;
case 's': //adjective
return "#e78229";
break;
default:
return "#9b59b6";
}
}
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update();
}
// Returns a list of all nodes under the root.
function flatten(root) {
var nodes = [],
i = 0;
function recurse(node) {
if (node.children) node.size = node.children.reduce(function(p, v) {
return p + recurse(v);
}, 0);
if (!node.id) node.id = ++i;
nodes.push(node);
return node.size;
}
root.size = recurse(root);
return nodes;
}
circle.node {
cursor: pointer;
stroke: #34495e;
stroke-width: 2px;
box-sizing: border-box;
stroke-location: inside;
}
line.link {
fill: none;
stroke: #34495e;
stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare./ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="viz"></div>
Note: This correction has an issue when expanding or collapsing a node, with the title being incorrectly set. This is because of the way d3 is binding the data to the DOM elements. By default d3 will bind based on index. Because adding and remove data can potentially change the order, the index values for the data could change, meaning that the relationship between DOM elements and data is broken. This can be corrected by explicitly setting the key for the binding of the data. You could use anything unique for the key, but for this example, I've just used the name
property you've defined. Like so:
var words = [{
"group": "n",
"word": "main node",
"children": [{
"group": "n",
"name": "sub node 1"
}, {
"group": "n",
"name": "sub node 2"
}, {
"group": "n",
"name": "sub node 3"
}, {
"group": "v",
"name": "sub node 4"
}, {
"group": "s",
"name": "sub node 5"
}, {
"group": "s",
"name": "sub node 6"
}, {
"group": "s",
"name": "sub node 7"
}, {
"group": "s",
"name": "sub node 8"
}, {
"group": "s",
"name": "sub node 9"
}, {
"group": "s",
"name": "sub node 10"
}, {
"group": "s",
"name": "sub node 11"
}, {
"group": "r",
"name": "sub node 12",
"children": [{
"group": "r",
"name": "sub sub node 1"
}, {
"group": "r",
"name": "sub sub node 2"
}, {
"group": "r",
"name": "sub sub node 3"
}]
}]
}]
var w = 600,
h = 600,
radius = 10,
node,
link,
root;
var force = d3.layout.force()
.on("tick", tick)
.charge(function(d) {
return -500;
})
.linkDistance(function(d) {
return d.target._children ? 100 : 50;
})
.size([w, h - 160]);
var svg = d3.select("#viz").append("svg")
.attr("width", w)
.attr("height", h);
root = words[0]; //set root node
root.fixed = true;
root.x = w / 2;
root.y = h / 2 - 80;
update();
function update() {
var nodes = flatten(root),
links = d3.layout.tree().links(nodes);
// Restart the force layout.
force
.nodes(nodes)
.links(links)
.start();
// Update the links…
link = svg.selectAll(".link")
.data(links);
// Enter any new links.
link.enter().insert("svg:line", ".node")
.attr("class", "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;
});
// Exit any old links.
link.exit().remove();
// Update the nodes…
node = svg.selectAll("circle.node")
.data(nodes, function(d) {
return d.name;
})
.style("fill", color);
node.transition()
.attr("r", radius);
// Enter any new nodes.
node.enter().append("svg:circle")
.attr("class", "node")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", radius)
.style("fill", color)
.on("click", click)
.call(force.drag);
node.append("title")
.text(function(d) {
return d.name;
});
// Exit any old nodes.
node.exit().remove();
}
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("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
}
// Color leaf nodes orange, and packages white or blue.
function color(d) {
if (d._children) {
return "#95a5a6";
} else {
switch (d.group) {
case 'r': //adverb
return "#e74c3c";
break;
case 'n': //noun
return "#3498db";
break;
case 'v': //verb
return "#2ecc71";
break;
case 's': //adjective
return "#e78229";
break;
default:
return "#9b59b6";
}
}
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update();
}
// Returns a list of all nodes under the root.
function flatten(root) {
var nodes = [],
i = 0;
function recurse(node) {
if (node.children) node.size = node.children.reduce(function(p, v) {
return p + recurse(v);
}, 0);
if (!node.id) node.id = ++i;
nodes.push(node);
return node.size;
}
root.size = recurse(root);
return nodes;
}
circle.node {
cursor: pointer;
stroke: #34495e;
stroke-width: 2px;
box-sizing: border-box;
stroke-location: inside;
}
line.link {
fill: none;
stroke: #34495e;
stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare./ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="viz"></div>
The difference here is the node.data
call:
node = svg.selectAll("circle.node")
.data(nodes, function(d) {
return d.name;
})
.style("fill", color);
The function in the .data(nodes, function...)
call instructs d3 to use whatever is returned from the function as the key (see https://github./mbostock/d3/wiki/Selections#data for more info). This will ensure that the correct graphical elements are removed, added and updated when collapsing and expanding nodes.