我的编程空间,编程开发者的网络收藏夹
学习永远不晚

从0手写一个力导向关系图的方法教程

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

从0手写一个力导向关系图的方法教程

这篇文章主要讲解了“从0手写一个力导向关系图的方法教程”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“从0手写一个力导向关系图的方法教程”吧!

下图,是本次要讲的项目动态实例:

从0手写一个力导向关系图的方法教程

前言

力导向图大家都不陌生,力导向图缺少不了力,而在数据量很大的情况下初始化节点以及对节点进行拖动时会导致整个力导图都在一直在动,密集的情况会更加严重,并且本着可以对点更好,灵活的控制,满足不同的需求,所以打算自己实现一个简单的力导向图,并在过程中对碰撞检测进行一次探索。

内容包括

整体内容分为两个部分

使用d3.js 开发力导向图出现的问题
  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. 两点之间多条边的处理

  3. 点的框选

  4. 点的删除

  5. 缩略图

  6. 主图的拖拽、缩放与缩略图

自己实现一个简单的拓扑图
  1. 鸿蒙官方战略合作共建——HarmonyOS技术社区

  2. 碰撞检测

  3. 矩形与矩形的检测

  4. 圆形与圆形

  5. 圆形与矩形

  6. 点的分配

  7. 碰撞后点的移动

  8. 拖动

一 使用d3.js 开发力导向图出现的问题

两点之间多条边的处理

从0手写一个力导向关系图的方法教程

思路为 ,将两点之间的线进行分组,中间,左右分别为三组,分好组后,当tick 进行渲染时,通过分组内容的数量,对分组内容改变path 的弯曲程度。

点的框选

拖拽中创建一个矩形框,拖拽后判断中心点是否在矩形框中则为被框选中. 注: 位置需要与d3 缩放的scale 配合计算

删除

点的删除实际上 就是把 相关点与线全部删除, 并且清空画布后, 重新用删除后的数据重新绘制。

缩略图

从0手写一个力导向关系图的方法教程

缩略图目前的逻辑是主图的最大倍数作为背景,主图的宽高作为缩略图视野(蓝框)的宽高。因为缩略图的dom 的宽高是css 定死的,viewbox 是实际宽高,所以给定主图(正常)的宽高 会自动缩放。在拖拽主图的点与相应操作时,对缩略图的点也进行相应的变动,实际上就是在缩略图中又画了一遍主图的内容 

       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) 会得到缩放以及拖拽信息,缩略图使用拖拽的信息,因为viewbox 的原因,拖拽信息会自动缩放。但是需要注意主图的缩放会对translate 进行变化 所以需要自己去处理 缩放过程中产生的位移

因为缩放会造成 主图的 translate 发生变化 与手动拖拽造成的translate 会有差 所以 要扣除缩放造成的偏移

      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应该减去缩放的差值 再 除K          y: -((y - diffY) / k),        }));

自己实现一个简单的拓扑图

碰撞检测
矩形与矩形的检测

矩形与矩形的碰撞是最好检测的

从0手写一个力导向关系图的方法教程

通过上面的图基本就涵盖了规则矩形相交的情况 图可以得知 A:红色矩形 B:绿色矩形 上下是通过Y,左右是通过X

A.x < B.x + B.width && A.x + A.width > B.x && A.y < B.y + B.h && A.h + A.y > B.y

从0手写一个力导向关系图的方法教程

但是如果内部是一个圆形的话,那么如果 紫色的区域则会被判定为碰撞则 则准确性有一定的偏差,需要有圆形的检测

圆形与圆形

从0手写一个力导向关系图的方法教程

圆形与圆形的逻辑也比较简单,就是两点之间的距离小于两点半径之和 则为碰撞 

var a = dot2.x-dot1.x;        var b = dot2.y-dot1.y;        return Math.sqrt(a*a+b*b) < a.radius + b.radius;
圆形与矩形

首先来看 矩形与圆形相交是什么样,从图所知矩形与圆形相交,表现为圆点距离矩形最近的点小于圆点半径 则为相交 那么如何得到圆点距离矩形最近的点

从0手写一个力导向关系图的方法教程

从下图就知道了 圆点的延伸是圆点边的一点。crashX = 如果 圆点位于矩形 左侧 矩形(rect).x; 右侧 = rect.x + rect.w 上下 圆点(circle).x

crashY = 如果 圆点位于矩形 左右 circle.y; 上 rect.y 上下 rect.y + h

那么两点有了,可以得出两点之间的距离套用圆与圆的公式 

var a = crash.x-dot1.x;       var b = crash.y-dot1.y;       return Math.sqrt(a*a+b*b) < a.radius;

从0手写一个力导向关系图的方法教程

上面就是基本的碰撞逻辑,更复杂的逻辑可以看下面参考文章 [1]

点的分配

从0手写一个力导向关系图的方法教程从0手写一个力导向关系图的方法教程

点的位置的分配 就是确定中心点后,将关系最多的点作为中心点,其关系点向四周分散,没有关系的同级点,则向中心点四周进行分散,其关系点以确定后位置的点的坐标向周围分散。

根据三角形的正玄、余弦来得值;假设一个圆的圆心坐标是(a,b),半径为r,角度为d 则圆上每个点的坐标可以通过下面的公式得到

 X = a + Math.cos(((Math.PI * 2) / 360) * d) * r; Y = b + Math.sin(((Math.PI * 2) / 360) * d) * r;

角度可以通过 关系边进行得到. d = 360/关系边的数量,确定第一圈点的角度。拿到角度后 ,维持一个所有点坐标的对象,再结合圆形与圆形碰撞检测,我们就可以遍历 获取所有点的坐标了

从0手写一个力导向关系图的方法教程

 initNodes() {     const { x: centerX, y: centerY } = this.center;     const { distance } = this;     const getDeg = (all, now) => 360 / (all - (now || 0));     // 把中心点分配给线最多的点     const centerdot = this.dots[0];     centerdot.x = centerX;     centerdot.y = centerY;     this.dotsLocations[centerdot.id] = { x: centerX, y: centerY };     this.dots.forEach((dot) => {       const { x: outx, y: outy } = dot;       if (!outx && !outy) {        // 兄弟点 (无关系的点) 默认以中心店的10度进行遍历         dot = this.getLocation(dot, centerX, centerY,10, distance).dot;       }       const { x: cx, y: cy } = dot;       const dotsLength = dot.relationDots.length;       let { distance: innerDistance } = this;       // 获取剩余点的角度       let addDeg = getDeg(dotsLength);       dot.relationDots.forEach((relationId, index) => {         let relationDot = this.findDot(relationId);         if (!relationDot.x && !relationDot.y) {           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;         }       });     });   }
// 分配位置   getLocation(dot, cx, cy, addDeg, distance) {   // 由第一张图 得知 -90度为最上面  从最上面开始循环     let outerDeg = -90;     let outerR = distance;     const { distance: addDistance } = this;     let firsted; // 用于分布完后一周     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;       }       const innerLocation = 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) {     let repeat = false;     if (!circleA.x || !circleA.y) return true;     const { forceCollide } = this;     console.log(this.dotsLocations)     Object.keys(this.dotsLocations).forEach((key) => {       if (key === circleA.id) {         return;       }       const circleB = this.dotsLocations[key];       let isRepeat = Math.sqrt(Math.pow(circleA.x - circleB.x, 2) + Math.pow(circleA.y - circleB.y, 2)) < forceCollide * 2;       if(isRepeat)repeat = true;     });     return repeat;   } }

生成时间与D3 的差不多

碰撞后点的移动 (力?)

碰撞后的逻辑呢 简单的就是已拖动点为圆点,计算碰撞点与圆点的夹角,再通过角度与距离得出碰撞后被碰撞点的x,y的坐标

changeLocation(data, x, y, eliminate) {  // 先对原来的点进行赋值     data.x = x;     data.y = y;     // 对点的坐标进行赋值,使之后的碰撞使用新值进行计算     this.dotsLocations[data.id] = { x, y };     let crashDots = this.checkDotLocation(data);     // 获得所有被碰撞的点     Object.keys(crashDots).forEach((crashId) => {       if (eliminate === crashId) return; // 碰撞后的碰撞防止 更改当前拖拽元素       const crashDot = this.findDot(crashId);       // 获取被碰撞的x,y 值       const { x: crashX, y: crashY } = crashDot;       // 此处的角度是要移动的方向的角度       let deg = getDeg(crashDot.x,crashDot.y,data.x,data.y);       // - 180 的目的是为了 与上面的黑图角度一致       // 2是碰撞后  移动2个像素的半径       const {x:endX,y:endY} = getDegXy(crashDot.x, crashDot.y, deg - 180, 2);       // 讲被碰撞的点作为圆点 改变值 并进行碰撞点的碰撞的碰撞检测(禁止套娃 )       this.changeLocation(crashDot, endX, endY, data.id);     });   }

获取夹角角度

function getDeg(x1,y1,x2,y2){   //中心点   let cx = x1;   let cy = y1;    //2个点之间的角度获取   let c1 = Math.atan2(y1 - cy, x1 - cx) * 180 / (Math.PI);   let c2 = Math.atan2(y2 - cy, x2 - cx) * 180 / (Math.PI);   let angle;   c1 = c1 <= -90 ? (360 + c1) : c1;   c2 = c2 <= -90 ? (360 + c2) : c2;    //夹角获取   angle = Math.floor(c2 - c1);   angle = angle < 0 ? angle + 360 : angle;   return angle; }

到此实现一个简单的拓扑图就搞定了。使用我们自己的force 代替 d3.js 的效果,后期想要什么效果就可以自己再加了 如 拖动主点相关点动,其他关联点不动的需求。tick方法需要自己手动去调用了

let force = new Force({           x: svgW / 2,           y: svgH / 2,           distance: 200,           forceCollide:30,         });         force.nodes(dot);         force.initLines(line);
拖动

这边的tick 是当 点的xy 发生变化的时候 自己去重新构建点和线。再实际项目中每一次拖动就会构建,会比较卡,可以丢到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", function (d) {         dragDom = null;       });

感谢各位的阅读,以上就是“从0手写一个力导向关系图的方法教程”的内容了,经过本文的学习后,相信大家对从0手写一个力导向关系图的方法教程这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

从0手写一个力导向关系图的方法教程

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Win8系统电脑插入手机提示这台计算机连接的前一个USB设备不正常的解决方法图文教程

有时候我们会把手机数据数据传输到Win8系统电脑中,可是有些用户说手机插上电脑后,在电脑桌面右下角却出现了“无法识别的USB设备,跟这台计算机连接的前一个USB设备工作不正常,Windows无法识别它”的提示,导致连
2022-06-04

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录