下图是本次要讲的项目动态示例:前言力向图大家都很熟悉了。力导向图不能缺少力。一般情况下,在初始化节点和拖动节点时,整个力图会不断移动,密集的情况会比较严重,本着更好更灵活地控制点以满足不同的需求,所以我打算实现一个自己做一个简单的力向图,过程中对碰撞检测做一个探索。内容包括整体内容分为两部分使用d3.js开发力向图问题两点之间多条边的处理点缩略图的框选点删除主图和图片的拖拽缩放缩略图实现一个简单的拓扑图碰撞检测矩形和矩形的检测圆和圆的检测圆和矩形的点赋值碰撞后点的移动使用d3.js拖动力导向图两点之间的问题多条边处理思路是将两点之间的连线分成中间和左右三组。分组后,在tick渲染时,根据分组内容的多少,对分组内容改变路径的弯曲程度。通过拖拽点创建一个矩形框,然后判断中心点是否在矩形框内,由框选中。注意:位置需要结合d3的scale来计算。点的删除其实就是删除相关点的所有线条和线条,清空画布后,用删除的数据重新绘制。Thumbnail缩略图目前的逻辑是主图的最大倍数作为背景,主图的宽高作为缩略图视图(蓝框)的宽高。因为缩略图dom的宽高是css固定的,而viewbox是实际的宽高,所以给定主图(normal)的宽高会自动缩放。当拖动主图的点和相应的操作时,缩略图的点也随之改变。其实在缩略图里面又画了主图的内容/***@params*widththumbnail图片宽度*heightThumbnail图片高度*mainWidth主图宽度*mainHeight主图高度*zoomMax最大缩放比例**/thumbSvg.attr('width',width).attr('height',height).attr('viewBox',()=>{//缩略图的宽高是主图的最大缩略图比例w=mainWidth*zoomMax;h=mainHeight*zoomMax;//设置背景图向中心移动的偏移量,缩略图与主图的差值/2为距离x=-(w-mainWidth)/2;y=-(h-mainHeight)/2;return`${x}${y}${w}${h}`;});dragThumb.attr('width',mainWidth).attr('height',mainHeight);主图的拖动、缩放和缩略图调用主图的缩放(zoom)时,会得到缩放和拖动信息,Thumbnails使用拖动信息,因为viewbox,拖动信息会自动缩放。但是需要注意的是,主图的缩放会改变平移,所以需要自己处理缩放过程中产生的位移,因为缩放会导致主图的平移发生改变,而平移导致通过手动拖动会有所不同,因此必须扣除缩放引起的偏移。shift/***@params*innerZoomInfo缩略图缩放信息*mainTransform主图缩放信息*mainWidth,mainHeight主图宽高*/const{innerZoomInfo,mainWidth,mainHeight,}=this;//如果传入如果缩放值与之前记录的缩放值不一致,则认为发生了缩放。if(!innerZoomInfo||innerZoomInfo.k!==mainTransform.k){this.moveDiff={x:(mainWidth-innerZoomInfo.k*mainWidth)/2,//缩放产生的位移y:(mainHeight-innerZoomInfo.k*mainHeight)/2,};}const{x:diffX,y:diffY}=this.moveDiff;const{x,y,k}=mainTransform;//主图偏移缩放数据this.dragThumb.attr('width',mainWidth/k).attr('height',mainHeight/k).attr('transform',()=>setTransform({x:-((x-diffX)/k),//这个地方应该不能直接除k其中x,y应该是放大后的x,y应该减去缩放差再除ky:-((y-diffY)/k),}));实现一个简单的拓扑图碰撞检测自己的情况图可以看出A:红色矩形B:绿色矩形上下穿过Y,左右穿过XA.xB.x&&A.yB.y但是如果里面是一个圆,那么如果紫色区域会被判定为碰撞,精度会有一定的偏差,而圆检测圆和圆圈都需要shape和circle逻辑也比较简单,就是两点之间的距离小于两点半径之和就是碰撞vara=dot2.x-dot1.x;varb=dot2.y-dot1.y;returnMath.sqrt(a*a+b*b)360/(all-(now||0));//放中心点分配给线最多的点constcenterdot=this.dots[0];centerdot.x=centerX;centerdot.y=centerY;this.dotsLocations[centerdot.id]={x:centerX,y:centerY};这。dots.forEach((dot)=>{const{x:outx,y:outy}=dot;if(!outx&&!outy){//兄弟点(无关点)默认在centralstore的10度处遍历dot=this.getLocation(dot,centerX,centerY,10,distance).dot;}const{x:cx,y:cy}=dot;constdotsLength=dot.relationDots.length;let{distance:innerDistance}=this;//获取剩余点的角度{const{dot:resultDot,isPlus,outerR,}=this.getLocation(relationDot,cx,cy,addDeg,innerDistance);if(isPlus){//如果遍历第一个圆,开始第二个圆,半径*2开始遍历innerDistance=outerR;addDeg=getDeg(dotsLength,index);addDeg+=randomNumber(5,9);//防止第一个圆和第二个圆的点生成的角度不一致,导致连线重合在一起}relationDot=resultDot;}});});}//AssignlocationgetLocation(dot,cx,cy,addDeg,distance){//从第一张图我们知道-90度为顶部,从顶部开始循环letouterDeg=-90;letouterR=distance;const{distance:addDistance}=this;letfirsted;//分发完成后使用一周while(Object.keys(this.checkDotLocation(dot)).length!==0){outerDeg+=addDeg;if(outerDeg>360){//经过一轮随机生成第二个圆的角度,开始定位当前点addDeg=randomNumber(10,35);outerDeg=addDeg;if(firsted){outerR+=addDistance;}firsted=true;}constinnerLocation=getDegXy(cx,cy,outerDeg,outerR);dot=Object.assign(dot,innerLocation);}this.dotsLocations[dot.id]={x:dot.x,y:dot.y};return{dot,isPlus:firsted,outerR,};}//碰撞检测checkDotLocation(circleA){letrepeat=false;if(!circleA.x||!circleA.y)returntrue;const{forceCollide}=this;console.log(this.dotsLocations)Object.keys(this.dotsLocations)。forEach((key)=>{if(key===circleA.id){return;}constcircleB=this.dotsLocations[key];letisRepeat=Math.sqrt(Math.pow(circleA.x-circleB.x,2)+Math.pow(circleA.y-circleB.y,2)){if(eliminate===crashId)return;//碰撞后防碰撞改变当前拖动elementconstcrashDot=this.findDot(crashId);//获取碰撞的x和y值const{x:crashX,y:crashY}=crashDot;//这里的角度是letdeg移动方向的角度=getDeg(crashDot.x,crashDot.y,data.x,data.y);//-180的目的是为了和上面黑图的角度一致//2是移动2个像素后的半径thecollisionconst{x:endX,y:endY}=getDegXy(crashDot.x,crashDot.y,deg-180,2);//以碰撞点为点改变值,进行碰撞检测碰撞点碰撞(娃娃娃娃禁止)this.changeLocation(crashDot,endX,endY,data.id);});}获取夹角函数getDeg(x1,y1,x2,y2){//中心点letcx=x1;letcy=y1;//2得到点之间的夹角letc1=Math.atan2(y1-cy,x1-cx)*180/(Math.PI);letc2=Math.atan2(y2-cy,x2-cx)*180/(数学.PI);letangle;c1=c1<=-90?(360+c1):c1;c2=c2<=-90?(360+c2):c2;//夹角获取angle=Math.floor(c2-c1);angle=angle<0?angle+360:angle;returnangle;}这里是一个简单的拓扑地图完成了。用我们自己的力代替d3.js的效果。稍后,您可以添加任何您想要的效果,例如拖动主要点移动,其他相关点保持静止。tick方法需要手动调用letforce=newForce({x:svgW/2,y:svgH/2,distance:200,forceCollide:30,});force.nodes(dot);force.initLines(line);拖这里的勾是当点的xy改变的时候重建点和线。实际项目中,每次拖动都会build,卡住。你可以把它丢进requestAnimationFrame调用dotDoms.on("mousedown",function(d){dragDom={data:d,dom:this,};});d3.select("svg").on("mousemove",function(d){if(!dragDom)return;const{offsetX:x,offsetY:y}=d3.event;if(x<-1||y<-1||x>=svgH-10||y>=svgH-10){//边界dragDom=null;return;}force.changeLocation(dragDom.data,x,y);tick();});d3.select("svg").on("mouseup",函数(d){dragDom=null;});小结本章主要描述使用d3开发力向图过程中出现的问题。以及在碰撞之上开发的简单力导向图