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> |