UNPKG

force-graph

Version:

2D force-directed graph rendered on HTML5 canvas

68 lines (54 loc) 2.49 kB
<head> <style> body { margin: 0; } </style> <script src="//cdn.jsdelivr.net/npm/force-graph"></script> <!--<script src="../../dist/force-graph.js"></script>--> </head> <body> <div id="graph"></div> <script> fetch('../datasets/miserables.json').then(res => res.json()).then(data => { const Graph = new ForceGraph(document.getElementById('graph')) .graphData(data) .nodeId('id') .nodeLabel('id') .nodeAutoColorBy('group') .linkCanvasObjectMode(() => 'after') .linkCanvasObject((link, ctx) => { const MAX_FONT_SIZE = 4; const LABEL_NODE_MARGIN = Graph.nodeRelSize() * 1.5; const start = link.source; const end = link.target; // ignore unbound links if (typeof start !== 'object' || typeof end !== 'object') return; // calculate label positioning const textPos = Object.assign(...['x', 'y'].map(c => ({ [c]: start[c] + (end[c] - start[c]) / 2 // calc middle point }))); const relLink = { x: end.x - start.x, y: end.y - start.y }; const maxTextLength = Math.sqrt(Math.pow(relLink.x, 2) + Math.pow(relLink.y, 2)) - LABEL_NODE_MARGIN * 2; let textAngle = Math.atan2(relLink.y, relLink.x); // maintain label vertical orientation for legibility if (textAngle > Math.PI / 2) textAngle = -(Math.PI - textAngle); if (textAngle < -Math.PI / 2) textAngle = -(-Math.PI - textAngle); const label = `${link.source.id} > ${link.target.id}`; // estimate fontSize to fit in link length ctx.font = '1px Sans-Serif'; const fontSize = Math.min(MAX_FONT_SIZE, maxTextLength / ctx.measureText(label).width); ctx.font = `${fontSize}px Sans-Serif`; const textWidth = ctx.measureText(label).width; const bckgDimensions = [textWidth, fontSize].map(n => n + fontSize * 0.2); // some padding // draw text label (with background rect) ctx.save(); ctx.translate(textPos.x, textPos.y); ctx.rotate(textAngle); ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; ctx.fillRect(- bckgDimensions[0] / 2, - bckgDimensions[1] / 2, ...bckgDimensions); ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillStyle = 'darkgrey'; ctx.fillText(label, 0, 0); ctx.restore(); }); }); </script> </body>