前言中提到,很多小伙伴可能只知道force-direction的使用,不知道实现原理。今天我们自己实现一套力-方向的算法,然后做一些技术上的扩展。发散你的想法。什么是力导向算法?根据维基百科的介绍:force-directedalgorithm是指计算每个节点计算出重力和排斥力的合力,然后通过这个合力移动节点的位置。通过力导算法计算出位置,绘制相应的力导图。这个分布就是最优位置的分布图。echarts和d3js中也有强大的布局图。先看力向图。力导向算法是根据自然界中电子直接相互作用的原理,在自然界中实现的。两个电子离得太近会产生斥力,离得太远会产生引力,从而保持平衡状态,最终达到保持物体形状的目的。这就涉及到一个库仑定律(百科:就是静点电荷相互作用力的定律。1785年,法国科学家C,-A.deCoulomb通过实验得出,真空中两个静止点电荷之间的相互作用力与它们电荷的乘积,与它们的距离成正比,与功率成反比,力的方向在它们的连线上,同名电荷相互排斥,异名电荷相互吸引彼此),这里涉及到一个库仑公式,若假设电子q=1,则F=k/(r^2)*e(e为q1到q2的矢量半径;k为库仑常数(静电力常数))。那么这里的F可以假设为某个方向的瞬时速度,e只是代表正负方向。一些力导向图算法加入弹簧力使e有减速效果,但我们这里不加弹簧力。现在,主要是研究库仑公式。如果进一步简化的话,我们可以把F看成是一个函数的变化,从而尽可能的简化我们的代码。复杂的问题先简单化,再慢慢深化。终于明白了它的原理。实现逻辑如果我们想用代码来实现简化力向图的布局,我们需要几个步骤。设置点数据节点,链接数据链接。随机定位点。渲染视图执行力算法计算位置,渲染视图重复4次操作N次得到想要的力导向图形。在执行力算法的时候,这里我们把库仑公式简化为一次函数,所以要么减去一个数,要么加一个数,改变点的坐标。很容易理解,当然,其实我们应该加上电子力(库仑公式)和弹簧力(胡克定律),这样力导向的效果更接近自然界的结果。代码实现示意图:setdata/***@descsimulateddata*/functiongetData(num,exLink){constdata={nodes:newArray(num).fill(1),links:[]};数据。nodes=data.nodes.map((d,id)=>{return{id,name:d,position:[0,0],children:[]}});data.nodes.forEach((d,i)=>{//两者都连接到0if(d.id!==0){data.links.push({source:0,target:d.id,sourceNode:data.nodes[0],targetNode:d});}});//随机选择其中2个连接constrandomLink=()=>{data.nodes.sort(()=>0.5-Math.random());data.links.push({source:data.nodes[0].id,target:data.nodes[1].id,sourceNode:data.nodes[0],targetNode:data.nodes[1]});}for(leti=0;i{if(!obj[d.id]){obj[d.id]=d;}});data.links.forEach(d=>{obj[d.source].childs.push(d.targetNode);obj[d.target].childs.push(d.sourceNode);});返回数据;}随机定位/***@descgetrandomNumber*/functiongetRandom(min,max){returnMath.floor(min+Math.random()*(max-min));}/***@descShuffleorderlocation*@paramdatadata*@paramsize画布大小*/functionrandomPosition(data,size){const{nodes,links}=data;nodes.forEach(d=>{letx=getRandom(0,size);lety=getRandom(0,size);d.position=[x,y];});}渲染视图/***@descdrawing*@paramctxcanvascontext*@paramdatadata*@paramsizecanvassize*/functionrender(ctx,data,size){ctx.clearRect(0,0,size,size);//清除所有内容constbox=20;ctx.fillStyle='#FF0000';data.links.forEach(d=>{let{sourceNode,targetNode}=d;let[x1,y1]=sourceNode.position;让[x2,y2]=targetNode.position;ctx.beginPath();//创建一个新路径ctx.moveTo(x1,y1);//将画笔移动到指定坐标处ctx.lineTo(x2,y2);//从当前位置到指定坐标(200,50)画一条直线。ctx.closePath();ctx.stroke();//绘制路径});data.nodes.forEach(d=>{let[x,y]=d.position;ctx.fillText(d.id,x,y+box);ctx.fillRect(x-box/2,y-box/2,盒子,盒子);});}模拟力计算位置/***@desc力算法*/functionforce(data,ctx,size){const{nodes,links}=data;//需要参数constmaxInterval=300;//平衡位置间距constmaxOffset=10;//最大变化位移constminOffset=0;//最小变化位移constcount=100;//力乘以const衰减=40;//力衰减constdoforce=()=>{//计算开始nodes.forEach(d=>{let[x1,y1]=d.position;nodes.forEach(e=>{if(d.id===e.id){return;}let[x2,y2]=e.position;//计算两点之间的距离letinterval=Math.sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));//console.log('interval',d.id+'-'+e.id,interval);//力衰减变量letforceOffset=0;letx3,y3;//如果大于水平间距,则靠近,如果小于平衡间距,则排斥。这里利用相似三角形的原理计算第三个点的坐标if(interval>maxInterval){forceOffset=(interval-maxInterval)/attenuation;//力衰减forceOffset=forceOffset>maxOffset?最大偏移量:强制偏移量;forceOffset=forceOffset0){//如果小于水平和水平间距,分开forceOffset=(maxInterval-interval)/attenuation;//力衰减forceOffset=forceOffset>maxOffset?最大偏移量:强制偏移量;forceOffset=forceOffsetsize?x3-=10:空;x3<0?x3+=10:空;y3>尺寸?y3-=10:空;y3<0?y3+=10:空;e.position=[x3,y3];});})}让countForce=0;constforceRun=()=>{setTimeout(()=>{countForce++;if(countForce>count){return;}doforce();render(ctx,data,size);forceRun();},1000/30)//requestAnimationFrame(forceRun);}强制运行();}mainfunction/*你的浏览器不支持*/constsize=800;//1.获取数据常量数据=getData(30,0);//2.随机定位randomPosition(data,size);//3.渲染letcav=document.getElementById('forceMap');让ctx=cav.getContext('2d');渲染(ctx,数据,大小);//4.执行力算法force(data,ctx,size);最终效果:知识扩展这里,我们设置了最大位移maxOffset,最小位移minOffset,如果没有达到平衡点(两点之间的距离为maxInterval),它们就会相互靠近或者远离。距离变化比较剧烈。当然,其实我们应该加上电子力(库仑公式)和弹簧力(胡格拉姆定律),这样力导的效果更接近自然界的效果。扩展知识:这里我们是成对比较节点。如果我们只比较两个链接点,会是什么结果,变化如下?获取图形:此代码仅供大家学习使用。真正的力导向算法要比这个复杂得多,可以做很多优化。比如最新版的d3js中的force-directed算法就是使用四叉树算法来实现的。优化到此结束,欢迎指正!