I am trying to restrict the node movement within the specified boundary. But I m not succeeded yet. I know that some changes are required in the ticked method, I am confused. Beginner with D3 library.
I have integrated the below Directed graph in my angular code.
http://bl.ocks.org/fancellu/2c782394602a93921faff74e594d1bb1
html tag:
<svg width="960" height="600"></svg>
Javascript code:
var colors = d3.scaleOrdinal(d3.schemeCategory10);
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
node,
link;
svg.append('defs').append('marker')
.attrs({
'id': 'arrowhead',
'viewBox': '-0 -5 10 10',
'refX': 13,
'refY': 0,
'orient': 'auto',
'markerWidth': 13,
'markerHeight': 13,
'xoverflow': 'visible'
})
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#999')
.style('stroke', 'none');
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) { return d.id; }).distance(100).strength(1))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
d3.json("graph.json", function (error, graph) {
if (error) throw error;
update(graph.links, graph.nodes);
})
function update(links, nodes) {
link = svg.selectAll(".link")
.data(links)
.enter()
.append("line")
.attr("class", "link")
.attr('marker-end', 'url(#arrowhead)')
link.append("title")
.text(function (d) { return d.type; });
edgepaths = svg.selectAll(".edgepath")
.data(links)
.enter()
.append('path')
.attrs({
'class': 'edgepath',
'fill-opacity': 0,
'stroke-opacity': 0,
'id': function (d, i) { return 'edgepath' + i }
})
.style("pointer-events", "none");
edgelabels = svg.selectAll(".edgelabel")
.data(links)
.enter()
.append('text')
.style("pointer-events", "none")
.attrs({
'class': 'edgelabel',
'id': function (d, i) { return 'edgelabel' + i },
'font-size': 10,
'fill': '#aaa'
});
edgelabels.append('textPath')
.attr('xlink:href', function (d, i) { return '#edgepath' + i })
.style("text-anchor", "middle")
.style("pointer-events", "none")
.attr("startOffset", "50%")
.text(function (d) { return d.type });
node = svg.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
//.on("end", dragended)
);
node.append("circle")
.attr("r", 5)
.style("fill", function (d, i) { return colors(i); })
node.append("title")
.text(function (d) { return d.id; });
node.append("text")
.attr("dy", -3)
.text(function (d) { return d.name + ":" + d.label; });
simulation
.nodes(nodes)
.on("tick", ticked);
simulation.force("link")
.links(links);
}
function ticked() {
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 + ")"; });
edgepaths.attr('d', function (d) {
return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y;
});
edgelabels.attr('transform', function (d) {
if (d.target.x < d.source.x) {
var bbox = this.getBBox();
rx = bbox.x + bbox.width / 2;
ry = bbox.y + bbox.height / 2;
return 'rotate(180 ' + rx + ' ' + ry + ')';
}
else {
return 'rotate(0)';
}
});
}
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart()
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
Just add a function to constrain the allowed x/y values to your bounds:
function checkBounds(d){
if (d.x < 0) d.x = 0;
if (d.x > width) d.x = width;
if (d.y < 0) d.y = 0;
if (d.y > width) d.y = height;
}
Then call it in the tick method:
link
.attr("x1", function (d) {
checkBounds(d.source);
return d.source.x;
})
.attr("y1", function (d) {
return d.source.y;
})
.attr("x2", function (d) {
checkBounds(d.target);
return d.target.x;
})
.attr("y2", function (d) {
return d.target.y;
});
node
.attr("transform", function (d) {
checkBounds(d);
return "translate(" + d.x + ", " + d.y + ")";
});
Running example:
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
.node {}
.link { stroke: #999; stroke-opacity: .6; stroke-width: 1px; }
svg {border: 1px solid black}
</style>
</head>
<body>
<script src="https://d3js.org/d3.v4.min.js" type="text/javascript"></script>
<script src="https://d3js.org/d3-selection-multi.v1.js"></script>
<svg width="400" height="400"></svg>
<script>
var colors = d3.scaleOrdinal(d3.schemeCategory10);
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height"),
node,
link;
svg.append('defs').append('marker')
.attrs({
'id': 'arrowhead',
'viewBox': '-0 -5 10 10',
'refX': 13,
'refY': 0,
'orient': 'auto',
'markerWidth': 13,
'markerHeight': 13,
'xoverflow': 'visible'
})
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#999')
.style('stroke', 'none');
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) { return d.id; }).distance(100).strength(1))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
var graph = {
"nodes": [
{
"name": "Peter",
"label": "Person",
"id": 1
},
{
"name": "Michael",
"label": "Person",
"id": 2
},
{
"name": "Neo4j",
"label": "Database",
"id": 3
},
{
"name": "Graph Database",
"label": "Database",
"id": 4
}
],
"links": [
{
"source": 1,
"target": 2,
"type": "KNOWS",
"since": 2010
},
{
"source": 1,
"target": 3,
"type": "FOUNDED"
},
{
"source": 2,
"target": 3,
"type": "WORKS_ON"
},
{
"source": 3,
"target": 4,
"type": "IS_A"
}
]
};
update(graph.links, graph.nodes);
function update(links, nodes) {
link = svg.selectAll(".link")
.data(links)
.enter()
.append("line")
.attr("class", "link")
.attr('marker-end', 'url(#arrowhead)')
link.append("title")
.text(function (d) { return d.type; });
edgepaths = svg.selectAll(".edgepath")
.data(links)
.enter()
.append('path')
.attrs({
'class': 'edgepath',
'fill-opacity': 0,
'stroke-opacity': 0,
'id': function (d, i) { return 'edgepath' + i }
})
.style("pointer-events", "none");
edgelabels = svg.selectAll(".edgelabel")
.data(links)
.enter()
.append('text')
.style("pointer-events", "none")
.attrs({
'class': 'edgelabel',
'id': function (d, i) { return 'edgelabel' + i },
'font-size': 10,
'fill': '#aaa'
});
edgelabels.append('textPath')
.attr('xlink:href', function (d, i) { return '#edgepath' + i })
.style("text-anchor", "middle")
.style("pointer-events", "none")
.attr("startOffset", "50%")
.text(function (d) { return d.type });
node = svg.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
//.on("end", dragended)
);
node.append("circle")
.attr("r", 5)
.style("fill", function (d, i) { return colors(i); })
node.append("title")
.text(function (d) { return d.id; });
node.append("text")
.attr("dy", -3)
.text(function (d) { return d.name + ":" + d.label; });
simulation.nodes(nodes)
.on("tick", ticked);
simulation.force("link")
.links(links);
}
function ticked() {
link
.attr("x1", function (d) {
checkBounds(d.source);
return d.source.x;
})
.attr("y1", function (d) {
return d.source.y;
})
.attr("x2", function (d) {
checkBounds(d.target);
return d.target.x;
})
.attr("y2", function (d) {
return d.target.y;
});
node
.attr("transform", function (d) {
checkBounds(d);
return "translate(" + d.x + ", " + d.y + ")";
});
edgepaths.attr('d', function (d) {
return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y;
});
edgelabels.attr('transform', function (d) {
if (d.target.x < d.source.x) {
var bbox = this.getBBox();
rx = bbox.x + bbox.width / 2;
ry = bbox.y + bbox.height / 2;
return 'rotate(180 ' + rx + ' ' + ry + ')';
}
else {
return 'rotate(0)';
}
});
}
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart()
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function checkBounds(d){
if (d.x < 0) d.x = 0;
if (d.x > width) d.x = width;
if (d.y < 0) d.y = 0;
if (d.y > width) d.y = height;
}
</script>
</body>
</html>
Related
I'm trying to build a Force-Directed Graph with Link Highlighting.
Initially the graph will look like the following:
But after hovering on a particular node I want it to become like the following (only connected nodes and edges will be highlighted):
I've tried the following code. But the highlighting part is not working.
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var color = d3.scaleOrdinal(d3.schemeCategory20);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function (d) { return d.id; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
d3.json("miserables.json", function (error, graph) {
if (error) throw error;
var link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter()
.append("line")
.attr("stroke-width", function (d) { return Math.sqrt(d.value); });
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("g")
.data(graph.nodes)
.enter()
.append("g")
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.call(
d3
.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended)
);
var circles = node.append("circle")
.attr("r", 10)
.attr("fill", function (d) { return color(d.group); });
var lables = node.append("text")
.text(function (d) {
return d.id;
})
.attr("x", 0)
.attr("dy", ".35em")
.attr("text-anchor", "middle");
node.append("title")
.text(function (d) { return d.id; });
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
function ticked() {
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 + ")";
})
}
});
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
graph.links.forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
linkedByIndex[d.target.index + "," + d.source.index] = 1;
});
function neighboring(a, b) {
return a.index == b.index || linkedByIndex[a.index + "," + b.index];
}
function mouseover(d) {
d3.selectAll("link").transition().duration(500)
.style("opacity", function (o) {
return o.source === d || o.target === d ? 1 : 0;
});
d3.selectAll("node").transition().duration(500)
.style("opacity", function (o) {
return neighboring(d, o) ? 1 : 0;
});
}
function mouseout() {
d3.selectAll("link").transition().duration(500)
.style("opacity", 1);
d3.selectAll("node").transition().duration(500)
.style("opacity", 1);
}
The full code can be found at https://plnkr.co/edit/BA9OfjVQjOwyHDkh
Could you please help me find the issue here and make it work?
using d3.js to visualize 50 nodes and their links, some of the nodes doesn't appear within the window (they are out of screen) - unless I drag them in:
This is my code, any suggestion how to make all the nodes appear on load? :
var width = window.innerWidth;
var height = window.innerHeight - 100;
var svg = d3.select("#sharedActivityGraph")
.append("svg")
.attr("width", width)
.attr("height", height);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink())
.force("charge", d3.forceManyBody().strength(-600))
.force("center", d3.forceCenter(width / 2, height / 2));
var links = svg.selectAll("foo")
.data(gSharedActivityGraphEdges)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", function (e) { return e.width });
var color = d3.scaleOrdinal(d3.schemeCategory20);
var node = svg.selectAll("foo")
.data(gSharedActivityGraphNodes)
.enter()
.append("g")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
node.on('mouseover', function (d) {
console.log(d);
SharedActivityShowInfo(d);
node.filter(function (d1) { return (d !== d1
&& d1.adjacents.indexOf(d.id) == -1);
}).select("image").style("opacity", 0.2);
node.filter(function (d1) { return (d == d1
|| d1.adjacents.indexOf(d.id) !== -1);
}).select("image").style("opacity", 1);
})
.on('mouseout', function () {
SharedActivityHideInfo();
node.select("image").style("opacity", 1);
});
var nodeCircle = node.append("circle")
.attr("r", function (d) { return 0.5 * Math.max(d.width, d.height) })
.attr("stroke", "gray")
.attr("stroke-width", "2px")
.attr("fill", "white");
var nodeImage = node.append("image")
.attr("xlink:href", function (d) { return d.image })
.attr("height", function (d) { return d.height + "" })
.attr("width", function (d) { return d.width + "" })
.attr("x", function (d) {return -0.5 * d.width })
.attr("y", function (d) {return -0.5 * d.height })
.attr("clip-path", function (d) { return "circle(" + (0.48 * Math.max(d.width, d.height)) + "px)"});
simulation.nodes(gSharedActivityGraphNodes);
simulation
.force("link")
.links(gSharedActivityGraphEdges);
simulation.on("tick", function() {
links.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 + ")"});
});
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
Ended up with adding the following lines:
node.attr("cx", function(d) { return d.x = Math.max(d.width, Math.min(width - d.width, d.x)); })
.attr("cy", function(d) { return d.y = Math.max(d.height, Math.min(height - heightDelta - d.height, d.y)); });
below the line:
node.attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"});
#TomShanley thanks for the ref!
I have been working with Force-Directed Graph but I am still new to D3js and Javascript.
I want to able to click on the node and the infobox popup on the page and print some information about that node (from JSON).
Example of my json file:
{"directed": false, "graph": {},
"nodes": [{"id": 0, "name":"Ant" , "info":"Small Animal"},
{"id": 1 , "name":"Apple" , "info":"Fruit"},
{"id": 2 , "name":"Bat" , "info":"Fly Animal"},
{"id": 3 , "name":"Boat" , "info":"Vehicle"},
{"id": 4 , "name":"Banana" , "info":"Long cute Fruit"},
{"id": 5 , "name":"Cat" , "info":"Best Animal"}],
"links": [{"source": 0, "target": 0 , "weight":1}, {"source": 0, "target": 2, "weight": 0.3},
{"source": 0, "target": 5, "weight":0.8}, {"source": 1, "target": 1, "weight":1},
{"source": 1, "target": 4, "weight":0.5}, {"source": 2, "target": 2, "weight":1},
{"source": 3, "target": 3, "weight":1}, {"source": 4, "target": 4, "weight":1},
{"source": 5, "target": 5, "weight":1}],
"multigraph": false}
So when I click on a node. It should popup something like:
Name: Ant
Info: Small Animal
Connected to: Bat with 0.3 weight , Cat with 0.8 weight
My graph code were pretty much like the example of force-directed that I link above.
Here's a quick implementation, it builds your "infobox" using SVG:
var tip;
node.on("click", function(d){
if (tip) tip.remove();
tip = svg.append("g")
.attr("transform", "translate(" + d.x + "," + d.y + ")");
var rect = tip.append("rect")
.style("fill", "white")
.style("stroke", "steelblue");
tip.append("text")
.text("Name: " + d.name)
.attr("dy", "1em")
.attr("x", 5);
tip.append("text")
.text("Info: " + d.info)
.attr("dy", "2em")
.attr("x", 5);
var con = graph.links
.filter(function(d1){
return d1.source.id === d.id;
})
.map(function(d1){
return d1.target.name + " with weight " + d1.weight;
})
tip.append("text")
.text("Connected to: " + con.join(","))
.attr("dy", "3em")
.attr("x", 5);
var bbox = tip.node().getBBox();
rect.attr("width", bbox.width + 5)
.attr("height", bbox.height + 5)
});
Running code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.links line {
stroke: #999;
stroke-opacity: 0.6;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
text {
font-family: sans-serif;
}
</style>
<svg width="960" height="600"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var svg = d3.select("svg"),
width = +svg.attr("width"),
height = +svg.attr("height");
var color = d3.scaleOrdinal(d3.schemeCategory20);
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) { return d.id; }))
.force("charge", d3.forceManyBody())
.force("center", d3.forceCenter(width / 2, height / 2));
d3.json("https://jsonblob.com/api/15daa79f-7573-11e8-b9d7-1b0997147957", function(error, graph) {
if (error) throw error;
link = svg.append("g")
.attr("class", "links")
.selectAll("line")
.data(graph.links)
.enter().append("line")
.attr("stroke-width", function(d) { return d.weight * 3; });
var node = svg.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", 5)
.attr("fill", function(d) { return color(d.group); })
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
var tip;
svg.on("click", function(){
if (tip) tip.remove();
});
node.on("click", function(d){
d3.event.stopPropagation();
if (tip) tip.remove();
tip = svg.append("g")
.attr("transform", "translate(" + d.x + "," + d.y + ")");
var rect = tip.append("rect")
.style("fill", "white")
.style("stroke", "steelblue");
tip.append("text")
.text("Name: " + d.name)
.attr("dy", "1em")
.attr("x", 5);
tip.append("text")
.text("Info: " + d.info)
.attr("dy", "2em")
.attr("x", 5);
var con = graph.links
.filter(function(d1){
return d1.source.id === d.id;
})
.map(function(d1){
return d1.target.name + " with weight " + d1.weight;
})
tip.append("text")
.text("Connected to: " + con.join(","))
.attr("dy", "3em")
.attr("x", 5);
var bbox = tip.node().getBBox();
rect.attr("width", bbox.width + 5)
.attr("height", bbox.height + 5)
});
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation.force("link")
.links(graph.links);
function ticked() {
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; });
}
});
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
</script>
You can add click event on node using .on('click', function(d){ });
For example:
node.on("click", function(d){
console.log(d);
// here you can access data of node using d.key
alert("You clicked on node " + d.name);
});
What I'm running now is showing a root node and bunch of nodes connected to it via d3 line element. I'd like to add a caption to the line, since I have it defined and can freely grab it from d.label (in the nodes section in JSON). I'm using the following code:
var width = 500,
height = 500;
var force = d3.layout.force()
.size([width, height])
.charge(-800)
.linkDistance(d => d.distance)
.on("tick", tick);
var svg = d3.select("#target").append("svg")
.attr("width", width)
.attr("height", height)
.attr("class", "mainsvg");
var link = svg.selectAll(".link"),
node = svg.selectAll(".node");
d3.json("test.json", function(error, graph) {
if (error) throw error;
force
.nodes(graph.nodes)
.links(graph.links)
.start();
link = link.data(graph.links)
.enter().append("line")
.attr("class", "link");
node = node.data(graph.nodes)
.enter().append("a")
node.append("g")
.attr("class", "node");
node.append("circle")
.attr("class", "circle")
.attr("r", function(d) { return d.r })
.attr("fill", function(d) { return d.color })
.attr("stroke", function(d){ return d.color });
});
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; });
svg.selectAll(".circle").attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
svg.selectAll(".text").attr("x", function(d) { return d.x+d.xoffs; })
.attr("y", function(d) { return d.y+d.yoffs; });
}
Now I've tried using normal <text> elements appended to the edgepath in this fashion:
var edgepaths = svg.selectAll(".edgepath")
.data(graph.links)
.enter()
.append('path')
.attr({'d': function(d) {return 'M '+d.source.x+' '+d.source.y+' L '+ d.target.x +' '+d.target.y},
'class':'edgepath',
'fill-opacity':0,
'stroke-opacity':0,
'fill':'blue',
'stroke':'red',
'id':function(d,i) {return 'edgepath'+i}})
.style("pointer-events", "none");
var edgelabels = svg.selectAll(".edgelabel")
.data(graph.links)
.enter()
.append('text')
.style("pointer-events", "none")
.attr({'class':'edgelabel',
'id':function(d,i){return 'edgelabel'+i},
'dx':80,
'dy':10,
'font-size':10,
'fill':'black'});
edgelabels.append('textPath')
.attr('xlink:href',function(d,i) {return '#edgepath'+i})
.style("pointer-events", "none")
.text(function(d){return d.label});
However, the results I get are weird to say at best. Some labels are too close together, some are missing, but all of them are in the wrong place. How would I move them where they should be? To keep something in mind, I'm looking for a simple way to add caption to the paths I already have drawn out, nothing else. So the method I tried may have been too much, I'm not sure.
The problem is the dx in your edgelabels, that's always the same:
'dx': 80,
Assuming that you're using the code with the link distance (as in your last question), the solution is simple:
'dx':function(d){return d.distance/2},
Check the demo:
var nodes = [{
name: "a"
}, {
name: "b"
}, {
name: "c"
}, {
name: "d"
}, {
name: "e"
}, {
name: "f"
}];
var links = [{
source: 1,
target: 0,
distance: 80,
label: "foo"
}, {
source: 2,
target: 0,
distance: 40,
label: "bar"
}, {
source: 3,
target: 0,
distance: 180,
label: "baz"
}, {
source: 4,
target: 0,
distance: 80,
label: "foobar"
}, {
source: 5,
target: 0,
distance: 90,
label: "foobaz"
}]
var width = 400
height = 300;
var force = d3.layout.force()
.nodes(nodes)
.links(links)
.size([width, height])
.linkDistance(d => d.distance)
.charge(-1000)
.on("tick", tick)
.start();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var link = svg.selectAll(".link")
.data(force.links())
.enter().append("line")
.attr("stroke", "black")
.attr("class", "link");
var edgepaths = svg.selectAll(".edgepath")
.data(force.links())
.enter()
.append('path')
.attr({
'd': function(d) {
return 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y
},
'class': 'edgepath',
'fill-opacity': 0,
'stroke-opacity': 0,
'fill': 'blue',
'stroke': 'red',
'id': function(d, i) {
return 'edgepath' + i
}
})
.style("pointer-events", "none");
var edgelabels = svg.selectAll(".edgelabel")
.data(force.links())
.enter()
.append('text')
.style("pointer-events", "none")
.attr({
'class': 'edgelabel',
'id': function(d, i) {
return 'edgelabel' + i
},
'dx': function(d) {
return d.distance / 2
},
'dy': 10,
'font-size': 12,
'fill': 'black'
});
edgelabels.append('textPath')
.attr('xlink:href', function(d, i) {
return '#edgepath' + i
})
.style("pointer-events", "none")
.text(function(d) {
return d.label
});
var node = svg.selectAll(".node")
.data(force.nodes())
.enter().append("g")
.attr("class", "node")
.call(force.drag);
node.append("circle")
.attr("r", (d, i) => i ? 10 : 16)
.style("fill", (d, i) => i ? "teal" : "brown");
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 + ")";
});
edgepaths.attr('d', function(d) {
var path = 'M ' + d.source.x + ' ' + d.source.y + ' L ' + d.target.x + ' ' + d.target.y;
return path
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
text cannot be a child of path see this question path is not a container... per SVG spec.
best practice would be to first append g elements to your SVG as containers for the bound data, then append the path and text elements to the g so that they are siblings. Then you have complete control over relative positioning, etc.
I hava tried some methods, such as d3 Node Labeling proposed by mbostock. But it doesn't work, my text still doesn't move with the nodes, they are static.
(Coded by using CoffeeScript)
vis = d3.select(selection).append("svg")
.attr("width", width)
.attr("height", height)
linksG = vis.append("g").attr("class", "links")
nodesG = vis.append("g").attr("class", "nodes")
labelG = vis.append("g").attr("class", "labels")
nodeP = nodesG.selectAll("circle.node")
.data(curNodesDataP)
nodeP.enter().append("circle")
.attr("class", "node")
.attr("r", 15)
.style("fill", (d) -> nodeColors(d.id))
.style("stroke", (d) -> strokeFor(d))
.style("stroke-width", 1.0)
.call(force.drag)
text = labelG.selectAll("text")
.data(curNodesDataP)
text.enter().append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.text((d) -> d.id)
I have troubled that several days, and how can i repair it? Thanks!
--zzcheng
You are appending the links, nodes and labels in separate g groups. So you should update the position of each group in tick function (Link group, Node group and label Group).
Here is a sample fiddle for the same. Note that I am updating position of label group also in the tick function.
var w = 900,
h = 400;
var circleWidth = 5;
var nodes = [{
"name": "Matteo"
}, {
"name": "Daniele"
}, {
"name": "Marco"
}, {
"name": "Lucio"
}, {
"name": "Davide"
}];
var links = [{
source: nodes[0],
target: nodes[1]
}, {
source: nodes[1],
target: nodes[2]
}, {
source: nodes[0],
target: nodes[3]
}, {
source: nodes[4],
target: nodes[2]
}, {
source: nodes[2],
target: nodes[3]
}];
var vis = d3.select("body")
.append("svg:svg")
.attr("class", "stage")
.attr("width", w)
.attr("height", h);
var force = d3.layout.force()
.nodes(nodes)
.links([])
.gravity(0.1)
.charge(-1000)
.size([w, h]);
var link = vis.selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", "link")
.attr("stroke", "#CCC")
.attr("fill", "none");
var node = vis.selectAll("circle.node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
var labels = vis.selectAll("text.label")
.data(nodes)
.enter().append("g")
.attr("class", "label");
//CIRCLE
node.append("svg:circle")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", circleWidth)
.attr("fill", "pink");
//TEXT
labels.append("text")
.text(function(d, i) {
return d.name;
})
.attr("x", function(d, i) {
return circleWidth + 5;
})
.attr("y", function(d, i) {
if (i > 0) {
return circleWidth + 0
} else {
return 8
}
})
.attr("font-family", "Bree Serif")
.attr("fill", "green")
.attr("font-size", "1em")
.attr("text-anchor", function(d, i) {
if (i > 0) {
return "beginning";
} else {
return "end"
}
})
force.on("tick", function(e) {
node.attr("transform", function(d, i) {
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;
});
labels.attr("transform", function(d, i) {
return "translate(" + d.x + "," + d.y + ")";
});
});
force.start();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Other possible and more efficient way would be to group nodes and labels together. Here is a sample fiddle in which nodes and labels are grouped and updated position of the node group and link group only in tick function.
var w = 900,
h = 400;
var circleWidth = 5;
var nodes = [{
"name": "Matteo"
}, {
"name": "Daniele"
}, {
"name": "Marco"
}, {
"name": "Lucio"
}, {
"name": "Davide"
}];
var links = [{
source: nodes[0],
target: nodes[1]
}, {
source: nodes[1],
target: nodes[2]
}, {
source: nodes[0],
target: nodes[3]
}, {
source: nodes[4],
target: nodes[2]
}, {
source: nodes[2],
target: nodes[3]
}];
var vis = d3.select("body")
.append("svg:svg")
.attr("class", "stage")
.attr("width", w)
.attr("height", h);
var force = d3.layout.force()
.nodes(nodes)
.links([])
.gravity(0.1)
.charge(-1000)
.size([w, h]);
var link = vis.selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", "link")
.attr("stroke", "#CCC")
.attr("fill", "none");
var node = vis.selectAll("circle.node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.call(force.drag);
//CIRCLE
node.append("svg:circle")
.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
})
.attr("r", circleWidth)
.attr("fill", "pink");
//TEXT
node.append("text")
.text(function(d, i) {
return d.name;
})
.attr("x", function(d, i) {
return circleWidth + 5;
})
.attr("y", function(d, i) {
if (i > 0) {
return circleWidth + 0
} else {
return 8
}
})
.attr("font-family", "Bree Serif")
.attr("fill", "green")
.attr("font-size", "1em")
.attr("text-anchor", function(d, i) {
if (i > 0) {
return "beginning";
} else {
return "end"
}
})
force.on("tick", function(e) {
node.attr("transform", function(d, i) {
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;
})
});
force.start();
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>