69 lines
No EOL
2.4 KiB
HTML
Executable file
69 lines
No EOL
2.4 KiB
HTML
Executable file
<head>
|
|
<style> body { margin: 0; } </style>
|
|
|
|
<script src="//unpkg.com/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 = 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> |