使用 Canvas 和 JavaScript 使多人关系图具有拖放和事件等交互性
一些多人关系图谱是分析数据的可视化工具,对分析数据有很大作用,下面手写一个简单的demo
<!DOCTYPE html> <html> <head> <title>多人关系图</title> <style> canvas { border: 1px solid #000; } </style> </head> <body> <canvas id="myCanvas" width="1900" height="800"></canvas> <script> var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); // 画布背景 ctx.fillStyle = "#fff"; ctx.fillRect(0, 0, canvas.width, canvas.height); // 定义节点 var nodes = [ { id: "A", x: 0, y: 0 }, { id: "B", x: 0, y: 0 }, { id: "C", x: 0, y: 0 }, { id: "D", x: 0, y: 0 }, { id: "E", x: 0, y: 0 }, { id: "F", x: 0, y: 0 }, { id: "G", x: 0, y: 0 }, { id: "H", x: 0, y: 0 }, { id: "I", x: 0, y: 0 }, { id: "J", x: 0, y: 0 }, { id: "K", x: 0, y: 0 }, { id: "L", x: 0, y: 0 }, { id: "M", x: 0, y: 0 }, { id: "N", x: 0, y: 0 }, { id: "O", x: 0, y: 0 }, { id: "P", x: 0, y: 0 }, { id: "Q", x: 0, y: 0 }, { id: "R", x: 0, y: 0 }, { id: "S", x: 0, y: 0 }, { id: "T", x: 0, y: 0 }, { id: "U", x: 0, y: 0 }, { id: "V", x: 0, y: 0 }, { id: "W", x: 0, y: 0 }, { id: "X", x: 0, y: 0 }, { id: "Y", x: 0, y: 0 }, { id: "Z", x: 0, y: 0 }, ]; nodesArea = [ ['A','B','C','D','E'], ['F','G','H'], ['I','J','K','L'], ['M','N','O','P','Q'], ['R','S','T','U','V','W'], ['X','Y','Z'], ] nodesAreaDataX = [-1,1,-1,1,-1.2,1.2,] // 模拟2134象限x 是基于默认初始值位置(可以设画布中间),负数为初始左,正为右,数值越大偏移越多 nodesAreaDataY = [1,1,-1,-1,1.2,-1.2,] // 模拟2134象限y 同上 nodes.forEach((item,index) => { nodesArea.forEach((val,num) => { if(val.includes(item.id)) { let jo = 1.5 if(index%2){ // 奇数偶数切换 jo = 1 } else { jo = -1 } // if(index > 30&&index < 60) { // // index = index - 30 // } // if(index > 60&&index < 90) { // index = index - 60 // } item.x = 600+nodesAreaDataX[num]*120+(10*index*jo)+Math.round(Math.random()*50); item.y = 300+nodesAreaDataY[num]*120+(5*index*jo)+Math.round(Math.random()*20); } }) }) console.log(nodes,'nodes'); // 定义连线 var links = [ { source: "A", target: "B" }, { source: "B", target: "C" }, { source: "C", target: "A" }, { source: "G", target: "C" }, { source: "G", target: "K" }, { source: "F", target: "A" }, { source: "A", target: "H" }, { source: "C", target: "M" }, { source: "I", target: "A" }, { source: "G", target: "A" }, { source: "O", target: "Q" }, { source: "J", target: "M" }, { source: "P", target: "L" }, { source: "X", target: "Y" }, { source: "Y", target: "Z" }, { source: "O", target: "R" }, { source: "U", target: "V" }, { source: "X", target: "C" }, { source: "Y", target: "A" }, { source: "Z", target: "V" }, { source: "S", target: "T" }, { source: "Z", target: "G" }, { source: "Z", target: "H" }, ]; // 绘制节点和连线 draw(); // 添加拖动事件 var selectedNode = null; canvas.addEventListener("mousedown", function(event) { selectedNode = getNodeAtPosition(event.offsetX, event.offsetY); }); canvas.addEventListener("mousemove", function(event) { if (selectedNode != null) { selectedNode.x = event.offsetX; selectedNode.y = event.offsetY; draw(); } }); canvas.addEventListener("mouseup", function(event) { selectedNode = null; }); // 获取位置上的节点 function getNodeAtPosition(x, y) { for (var i = 0; i < nodes.length; i++) { var node = nodes[i]; var distance = Math.sqrt((node.x - x) * (node.x - x) + (node.y - y) * (node.y - y)); if (distance <= 20) { return node; } } return null; } // 绘制节点和连线的函数 function draw() { // 清空画布 ctx.clearRect(0, 0, canvas.width, canvas.height); // 绘制连线 ctx.strokeStyle = "#000"; ctx.lineWidth = 1; links.forEach(function(link) { var sourceNode = nodes.find(function(node) { return node.id == link.source }); var targetNode = nodes.find(function(node) { return node.id == link.target }); ctx.beginPath(); ctx.moveTo(sourceNode.x, sourceNode.y); ctx.lineTo(targetNode.x, targetNode.y); ctx.stroke(); }); // 绘制节点 // ctx.fillStyle = 'rgba(255,153,0,1.00)'; nodes.forEach(function(node) { ctx.beginPath(); // 绘制文本 new Promise((resolve, reject) => { ctx.arc(node.x, node.y, 10, 0, 2 * Math.PI); ctx.fillStyle = 'rgba(255,153,0,1.00)'; ctx.fill(); resolve(); }).then((res) => { ctx.font = "12px Arial"; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillStyle = 'rgba(0,191,255,1.00)'; ctx.fillText(node.id, node.x, node.y); }); }); } </script> </body> </html>
更多具体的欢迎交流开发,不使用插件的情况下,这种支撑数据应该不会太多,大量数据需要使用插件,支撑十万百万数据。设想过一种立体渲染模式,应该能支撑很多数据,设想原型复杂,没有实际操作,不亚于一个插件库,有大神有兴趣可以交流。
长风破浪会有时,直挂云帆济沧海