This force-directed graph shows labelled edges using v4 force simulation, including end arrow markers
This is the blocks url:
http://bl.ocks.org/fancellu/2c782394602a93921faff74e594d1bb1
license: gpl-3.0 | |
height: 600 |
This force-directed graph shows labelled edges using v4 force simulation, including end arrow markers
This is the blocks url:
http://bl.ocks.org/fancellu/2c782394602a93921faff74e594d1bb1
{ | |
"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" | |
} | |
] | |
} |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge"> | |
<meta name="viewport" content="width=device-width, initial-scale=1"> | |
<style type="text/css"> | |
.node {} | |
.link { stroke: #999; stroke-opacity: .6; stroke-width: 1px; } | |
</style> | |
</head> | |
<body> | |
<svg width="960" height="600"></svg> | |
<script src="http://d3js.org/d3.v4.min.js" type="text/javascript"></script> | |
<script src="http://d3js.org/d3-selection-multi.v1.js"></script> | |
<script type="text/javascript"> | |
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; | |
} | |
// function dragended(d) { | |
// if (!d3.event.active) simulation.alphaTarget(0); | |
// d.fx = undefined; | |
// d.fy = undefined; | |
// } | |
</script> | |
</body> | |
</html> |
Nice! I think it should be updated to work with
'https://d3js.org/d3.v7.min
.