成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

手把手教你從0手寫(xiě)一個(gè)力導(dǎo)向關(guān)系圖

開(kāi)發(fā) 前端
力導(dǎo)向圖大家都不陌生,力導(dǎo)向圖缺少不了力,而在數(shù)據(jù)量很大的情況下初始化節(jié)點(diǎn)以及對(duì)節(jié)點(diǎn)進(jìn)行拖動(dòng)時(shí)會(huì)導(dǎo)致整個(gè)力導(dǎo)圖都在一直在動(dòng)。

  下圖,是本次要講的項(xiàng)目動(dòng)態(tài)實(shí)例:

 

前言

力導(dǎo)向圖大家都不陌生,力導(dǎo)向圖缺少不了力,而在數(shù)據(jù)量很大的情況下初始化節(jié)點(diǎn)以及對(duì)節(jié)點(diǎn)進(jìn)行拖動(dòng)時(shí)會(huì)導(dǎo)致整個(gè)力導(dǎo)圖都在一直在動(dòng),密集的情況會(huì)更加嚴(yán)重,并且本著可以對(duì)點(diǎn)更好,靈活的控制,滿(mǎn)足不同的需求,所以打算自己實(shí)現(xiàn)一個(gè)簡(jiǎn)單的力導(dǎo)向圖,并在過(guò)程中對(duì)碰撞檢測(cè)進(jìn)行一次探索。

內(nèi)容包括

整體內(nèi)容分為兩個(gè)部分

使用d3.js 開(kāi)發(fā)力導(dǎo)向圖出現(xiàn)的問(wèn)題

  1. 兩點(diǎn)之間多條邊的處理

  2. 點(diǎn)的框選

  3. 點(diǎn)的刪除

  4. 縮略圖

  5. 主圖的拖拽、縮放與縮略圖

自己實(shí)現(xiàn)一個(gè)簡(jiǎn)單的拓?fù)鋱D

  1. 碰撞檢測(cè)

  2. 矩形與矩形的檢測(cè)

  3. 圓形與圓形

  4. 圓形與矩形

  5. 點(diǎn)的分配

  6. 碰撞后點(diǎn)的移動(dòng)

  7. 拖動(dòng)

一 使用d3.js 開(kāi)發(fā)力導(dǎo)向圖出現(xiàn)的問(wèn)題

兩點(diǎn)之間多條邊的處理

思路為 ,將兩點(diǎn)之間的線進(jìn)行分組,中間,左右分別為三組,分好組后,當(dāng)tick 進(jìn)行渲染時(shí),通過(guò)分組內(nèi)容的數(shù)量,對(duì)分組內(nèi)容改變path 的彎曲程度。

點(diǎn)的框選

拖拽中創(chuàng)建一個(gè)矩形框,拖拽后判斷中心點(diǎn)是否在矩形框中則為被框選中. 注: 位置需要與d3 縮放的scale 配合計(jì)算

刪除

點(diǎn)的刪除實(shí)際上 就是把 相關(guān)點(diǎn)與線全部刪除, 并且清空畫(huà)布后, 重新用刪除后的數(shù)據(jù)重新繪制。

縮略圖

縮略圖目前的邏輯是主圖的最大倍數(shù)作為背景,主圖的寬高作為縮略圖視野(藍(lán)框)的寬高。因?yàn)榭s略圖的dom 的寬高是css 定死的,viewbox 是實(shí)際寬高,所以給定主圖(正常)的寬高 會(huì)自動(dòng)縮放。在拖拽主圖的點(diǎn)與相應(yīng)操作時(shí),對(duì)縮略圖的點(diǎn)也進(jìn)行相應(yīng)的變動(dòng),實(shí)際上就是在縮略圖中又畫(huà)了一遍主圖的內(nèi)容 

  1. /** 
  2.       * @params 
  3.       * width 縮略圖寬度 
  4.       * height 縮略圖高度 
  5.       * mainWidth 主圖的寬度 
  6.       * mainHeight 主圖的高度 
  7.       * zoomMax 最大縮放比例 
  8.       *  
  9.       */ 
  10.       thumbSvg.attr('width', width) 
  11.          .attr('height', height).attr('viewBox', () => { 
  12.           // 縮略圖的寬高為 主圖的 最大縮略比例 
  13.            w = mainWidth * zoomMax; 
  14.            h = mainHeight * zoomMax; 
  15.            // 設(shè)置偏移 讓背景圖移至中心,縮略圖與主圖的差/ 2 就是需要移動(dòng)的距離 
  16.            x = -(w - mainWidth) / 2
  17.            y = -(h - mainHeight) / 2
  18.            return `${x} ${y} ${w} ${h}`; 
  19.          }); 
  20.        dragThumb.attr('width', mainWidth) 
  21.          .attr('height', mainHeight); 

主圖的拖拽、縮放與縮略圖

調(diào)用主圖的縮放時(shí)(zoom) 會(huì)得到縮放以及拖拽信息,縮略圖使用拖拽的信息,因?yàn)関iewbox 的原因,拖拽信息會(huì)自動(dòng)縮放。但是需要注意主圖的縮放會(huì)對(duì)translate 進(jìn)行變化 所以需要自己去處理 縮放過(guò)程中產(chǎn)生的位移

因?yàn)榭s放會(huì)造成 主圖的 translate 發(fā)生變化 與手動(dòng)拖拽造成的translate 會(huì)有差 所以 要扣除縮放造成的偏移

  1. /** 
  2.   * @params 
  3.   *  innerZoomInfo 縮略圖的縮放信息 
  4.   *  mainTransform 主圖的縮放信息 
  5.   *  mainWidth,mainHeight 主圖的寬高 
  6.   */ 
  7.      const { 
  8.        innerZoomInfo, mainWidth, mainHeight, 
  9.      } = this
  10.      // 如果傳入的 縮放值與之前記錄的縮放值不一致 則認(rèn)為發(fā)生了縮放 記錄發(fā)生縮放后偏移值 
  11.      if (!innerZoomInfo || innerZoomInfo.k !== mainTransform.k) { 
  12.        this.moveDiff = { 
  13.          x: (mainWidth - innerZoomInfo.k * mainWidth) / 2//縮放產(chǎn)生的 位移 
  14.          y: (mainHeight - innerZoomInfo.k * mainHeight) / 2
  15.        }; 
  16.      } 
  17.      const { x: diffX, y: diffY } = this.moveDiff; 
  18.      const { x, y, k } = mainTransform; // 主圖偏移以及縮放數(shù)據(jù) 
  19.      this.dragThumb 
  20.        .attr('width', mainWidth / k) 
  21.        .attr('height', mainHeight / k) 
  22.        .attr('transform', () => setTransform({ 
  23.          x: -((x - diffX) / k), // 這個(gè)地方應(yīng)該不能直接 除 k 這里的x,y 應(yīng)該是放大后的x,y應(yīng)該減去縮放的差值 再 除K 
  24.          y: -((y - diffY) / k), 
  25.        })); 

自己實(shí)現(xiàn)一個(gè)簡(jiǎn)單的拓?fù)鋱D

碰撞檢測(cè)

矩形與矩形的檢測(cè)

矩形與矩形的碰撞是最好檢測(cè)的

通過(guò)上面的圖基本就涵蓋了規(guī)則矩形相交的情況 圖可以得知 A:紅色矩形 B:綠色矩形 上下是通過(guò)Y,左右是通過(guò)X

  1. A.x < B.x + B.width && 
  2. A.x + A.width > B.x && 
  3. A.y < B.y + B.h && 
  4. A.h + A.y > B.y 

但是如果內(nèi)部是一個(gè)圓形的話(huà),那么如果 紫色的區(qū)域則會(huì)被判定為碰撞則 則準(zhǔn)確性有一定的偏差,需要有圓形的檢測(cè)

圓形與圓形

圓形與圓形的邏輯也比較簡(jiǎn)單,就是兩點(diǎn)之間的距離小于兩點(diǎn)半徑之和 則為碰撞 

  1. var a = dot2.x-dot1.x; 
  2.        var b = dot2.y-dot1.y; 
  3.        return Math.sqrt(a*a+b*b) < a.radius + b.radius; 

圓形與矩形

首先來(lái)看 矩形與圓形相交是什么樣,從圖所知矩形與圓形相交,表現(xiàn)為圓點(diǎn)距離矩形最近的點(diǎn)小于圓點(diǎn)半徑 則為相交 那么如何得到圓點(diǎn)距離矩形最近的點(diǎn)

從下圖就知道了 圓點(diǎn)的延伸是圓點(diǎn)邊的一點(diǎn)。crashX = 如果 圓點(diǎn)位于矩形 左側(cè) 矩形(rect).x; 右側(cè) = rect.x + rect.w 上下 圓點(diǎn)(circle).x

crashY = 如果 圓點(diǎn)位于矩形 左右 circle.y; 上 rect.y 上下 rect.y + h

那么兩點(diǎn)有了,可以得出兩點(diǎn)之間的距離套用圓與圓的公式 

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

上面就是基本的碰撞邏輯,更復(fù)雜的邏輯可以看下面參考文章 [1]

點(diǎn)的分配

點(diǎn)的位置的分配 就是確定中心點(diǎn)后,將關(guān)系最多的點(diǎn)作為中心點(diǎn),其關(guān)系點(diǎn)向四周分散,沒(méi)有關(guān)系的同級(jí)點(diǎn),則向中心點(diǎn)四周進(jìn)行分散,其關(guān)系點(diǎn)以確定后位置的點(diǎn)的坐標(biāo)向周?chē)稚ⅰ?/p>

根據(jù)三角形的正玄、余弦來(lái)得值;假設(shè)一個(gè)圓的圓心坐標(biāo)是(a,b),半徑為r,角度為d 則圓上每個(gè)點(diǎn)的坐標(biāo)可以通過(guò)下面的公式得到

  1. /* 
  2. * @params 
  3. * d 角度 
  4. * r 半徑長(zhǎng)度 
  5. */ 
  6. X = a + Math.cos(((Math.PI * 2) / 360) * d) * r; 
  7. Y = b + Math.sin(((Math.PI * 2) / 360) * d) * r; 

角度可以通過(guò) 關(guān)系邊進(jìn)行得到. d = 360/關(guān)系邊的數(shù)量,確定第一圈點(diǎn)的角度。拿到角度后 ,維持一個(gè)所有點(diǎn)坐標(biāo)的對(duì)象,再結(jié)合圓形與圓形碰撞檢測(cè),我們就可以遍歷 獲取所有點(diǎn)的坐標(biāo)了

  1. /* 
  2. * @params 
  3. * dotsLocations 所有點(diǎn)的坐標(biāo)信息 
  4. */ 
  5. initNodes() { 
  6.     const { x: centerX, y: centerY } = this.center; 
  7.     const { distance } = this
  8.     const getDeg = (all, now) => 360 / (all - (now || 0)); 
  9.     // 把中心點(diǎn)分配給線最多的點(diǎn) 
  10.     const centerdot = this.dots[0]; 
  11.     centerdot.x = centerX; 
  12.     centerdot.y = centerY; 
  13.     this.dotsLocations[centerdot.id] = { x: centerX, y: centerY }; 
  14.     this.dots.forEach((dot) => { 
  15.       const { x: outx, y: outy } = dot; 
  16.       if (!outx && !outy) { 
  17.        // 兄弟點(diǎn) (無(wú)關(guān)系的點(diǎn)) 默認(rèn)以中心店的10度進(jìn)行遍歷 
  18.         dot = this.getLocation(dot, centerX, centerY,10, distance).dot; 
  19.       } 
  20.       const { x: cx, y: cy } = dot; 
  21.       const dotsLength = dot.relationDots.length; 
  22.       let { distance: innerDistance } = this
  23.       // 獲取剩余點(diǎn)的角度 
  24.       let addDeg = getDeg(dotsLength); 
  25.       dot.relationDots.forEach((relationId, index) => { 
  26.         let relationDot = this.findDot(relationId); 
  27.         if (!relationDot.x && !relationDot.y) { 
  28.           const { 
  29.             dot: resultDot, 
  30.             isPlus, 
  31.             outerR, 
  32.           } = this.getLocation(relationDot, cx, cy, addDeg, innerDistance); 
  33.           if (isPlus) { 
  34.            // 如果第一圈遍歷完畢,則開(kāi)始以 半徑 * 2 為第二圈開(kāi)始遍歷 
  35.             innerDistance = outerR; 
  36.             addDeg = getDeg(dotsLength, index); 
  37.             addDeg += randomNumber(59);  //防止第一圈與第二圈的點(diǎn)所生成的角度一致 造成鏈接的線重疊在一起 
  38.           } 
  39.           relationDot = resultDot; 
  40.         } 
  41.       }); 
  42.     }); 
  43.   } 
  44.  
  45.    

 

  1. // 分配位置 
  2.   getLocation(dot, cx, cy, addDeg, distance) { 
  3.   // 由第一張圖 得知 -90度為最上面  從最上面開(kāi)始循環(huán) 
  4.     let outerDeg = -90
  5.     let outerR = distance; 
  6.     const { distance: addDistance } = this
  7.     let firsted; // 用于分布完后一周 
  8.     while (Object.keys(this.checkDotLocation(dot)).length !== 0) { 
  9.       outerDeg += addDeg; 
  10.       if (outerDeg > 360) { 
  11.       // 轉(zhuǎn)完一圈 隨機(jī)生成第二圈的角度再開(kāi)始對(duì)當(dāng)前點(diǎn)進(jìn)行定位 
  12.         addDeg = randomNumber(1035); 
  13.         outerDeg = addDeg; 
  14.         if (firsted) { 
  15.           outerR += addDistance; 
  16.         } 
  17.         firsted = true
  18.       } 
  19.       const innerLocation = getDegXy(cx, cy, outerDeg, outerR); 
  20.       dot = Object.assign(dot, innerLocation); 
  21.     } 
  22.     this.dotsLocations[dot.id] = { x: dot.x, y: dot.y }; 
  23.     return { 
  24.       dot, 
  25.       isPlus: firsted, 
  26.       outerR, 
  27.     }; 
  28.   } 


  1.  // 碰撞檢測(cè) 
  2.   checkDotLocation(circleA) { 
  3.     let repeat = false
  4.     if (!circleA.x || !circleA.y) return true
  5.     const { forceCollide } = this
  6.     console.log(this.dotsLocations) 
  7.     Object.keys(this.dotsLocations).forEach((key) => { 
  8.       if (key === circleA.id) { 
  9.         return
  10.       } 
  11.       const circleB = this.dotsLocations[key]; 
  12.       let isRepeat = Math.sqrt(Math.pow(circleA.x - circleB.x, 2) + Math.pow(circleA.y - circleB.y, 2)) < forceCollide * 2
  13.       if(isRepeat)repeat = true
  14.     }); 
  15.     return repeat; 
  16.   } 

生成時(shí)間與D3 的差不多

碰撞后點(diǎn)的移動(dòng) (力?)

碰撞后的邏輯呢 簡(jiǎn)單的就是已拖動(dòng)點(diǎn)為圓點(diǎn),計(jì)算碰撞點(diǎn)與圓點(diǎn)的夾角,再通過(guò)角度與距離得出碰撞后被碰撞點(diǎn)的x,y的坐標(biāo)

  1. changeLocation(data, x, y, eliminate) { 
  2.  // 先對(duì)原來(lái)的點(diǎn)進(jìn)行賦值 
  3.     data.x = x; 
  4.     data.y = y; 
  5.     // 對(duì)點(diǎn)的坐標(biāo)進(jìn)行賦值,使之后的碰撞使用新值進(jìn)行計(jì)算 
  6.     this.dotsLocations[data.id] = { x, y }; 
  7.     let crashDots = this.checkDotLocation(data); 
  8.     // 獲得所有被碰撞的點(diǎn) 
  9.     Object.keys(crashDots).forEach((crashId) => { 
  10.       if (eliminate === crashId) return// 碰撞后的碰撞防止 更改當(dāng)前拖拽元素 
  11.       const crashDot = this.findDot(crashId); 
  12.       // 獲取被碰撞的x,y 值 
  13.       const { x: crashX, y: crashY } = crashDot; 
  14.       // 此處的角度是要移動(dòng)的方向的角度 
  15.       let deg = getDeg(crashDot.x,crashDot.y,data.x,data.y); 
  16.       // - 180 的目的是為了 與上面的黑圖角度一致 
  17.       // 2是碰撞后  移動(dòng)2個(gè)像素的半徑 
  18.       const {x:endX,y:endY} = getDegXy(crashDot.x, crashDot.y, deg - 1802); 
  19.       // 講被碰撞的點(diǎn)作為圓點(diǎn) 改變值 并進(jìn)行碰撞點(diǎn)的碰撞的碰撞檢測(cè)(禁止套娃 ) 
  20.       this.changeLocation(crashDot, endX, endY, data.id); 
  21.     }); 
  22.   } 

獲取夾角角度

  1. function getDeg(x1,y1,x2,y2){ 
  2.   //中心點(diǎn) 
  3.   let cx = x1; 
  4.   let cy = y1; 
  5.  
  6.   //2個(gè)點(diǎn)之間的角度獲取 
  7.   let c1 = Math.atan2(y1 - cy, x1 - cx) * 180 / (Math.PI); 
  8.   let c2 = Math.atan2(y2 - cy, x2 - cx) * 180 / (Math.PI); 
  9.   let angle; 
  10.   c1 = c1 <= -90 ? (360 + c1) : c1; 
  11.   c2 = c2 <= -90 ? (360 + c2) : c2; 
  12.  
  13.   //夾角獲取 
  14.   angle = Math.floor(c2 - c1); 
  15.   angle = angle < 0 ? angle + 360 : angle; 
  16.   return angle; 

到此實(shí)現(xiàn)一個(gè)簡(jiǎn)單的拓?fù)鋱D就搞定了。使用我們自己的force 代替 d3.js 的效果,后期想要什么效果就可以自己再加了 如 拖動(dòng)主點(diǎn)相關(guān)點(diǎn)動(dòng),其他關(guān)聯(lián)點(diǎn)不動(dòng)的需求。tick方法需要自己手動(dòng)去調(diào)用了

  1. let force = new Force({ 
  2.           x: svgW / 2
  3.           y: svgH / 2
  4.           distance: 200
  5.           forceCollide:30
  6.         }); 
  7.         force.nodes(dot); 
  8.         force.initLines(line); 

拖動(dòng)

這邊的tick 是當(dāng) 點(diǎn)的xy 發(fā)生變化的時(shí)候 自己去重新構(gòu)建點(diǎn)和線。再實(shí)際項(xiàng)目中每一次拖動(dòng)就會(huì)構(gòu)建,會(huì)比較卡,可以丟到requestAnimationFrame 去調(diào)用 

  1. dotDoms.on("mousedown", function (d) { 
  2.         dragDom = { 
  3.           data: d, 
  4.           dom: this
  5.         }; 
  6.       }); 
  7.       d3.select("svg").on("mousemove", function (d) { 
  8.         if (!dragDom) return
  9.         const { offsetX: x, offsetY: y } = d3.event; 
  10.         if (x < -1 || y < -1 || x >= svgH - 10 || y >= svgH - 10) { 
  11.           //邊界 
  12.           dragDom = null
  13.           return
  14.         } 
  15.         force.changeLocation(dragDom.data, x, y); 
  16.         tick(); 
  17.       }); 
  18.       d3.select("svg").on("mouseup", function (d) { 
  19.         dragDom = null
  20.       }); 

小結(jié)

本章主要講述了使用d3 對(duì)力導(dǎo)向圖進(jìn)行開(kāi)發(fā)過(guò)程中,出現(xiàn)的問(wèn)題。以及以碰撞為基礎(chǔ)開(kāi)發(fā)的簡(jiǎn)單的力導(dǎo)向圖

 

 

責(zé)任編輯:張燕妮 來(lái)源: code秘密花園
相關(guān)推薦

2021-06-22 10:43:03

Webpack loader plugin

2019-10-29 15:46:07

區(qū)塊鏈區(qū)塊鏈技術(shù)

2019-08-26 09:25:23

RedisJavaLinux

2023-10-16 22:03:36

日志包多線程日志包

2021-12-10 18:19:55

指標(biāo)體系設(shè)計(jì)

2022-05-18 08:51:44

調(diào)用模板后端并行

2023-03-27 08:28:57

spring代碼,starter

2023-11-28 07:36:41

Shell腳本部署

2022-06-28 15:29:56

Python編程語(yǔ)言計(jì)時(shí)器

2022-08-25 14:41:51

集群搭建

2017-07-19 13:27:44

前端Javascript模板引擎

2014-01-22 09:19:57

JavaScript引擎

2011-01-10 14:41:26

2011-05-03 15:59:00

黑盒打印機(jī)

2025-05-07 00:31:30

2022-08-26 08:01:38

DashWebJavaScrip

2016-11-01 09:46:04

2022-09-22 12:38:46

antd form組件代碼

2021-07-14 09:00:00

JavaFX開(kāi)發(fā)應(yīng)用

2018-05-16 15:46:06

Python網(wǎng)絡(luò)爬蟲(chóng)PhantomJS
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 久久久久国产一区二区三区四区 | 国产精品久久久久久久久免费软件 | av在线一区二区三区 | 特黄特色大片免费视频观看 | 激情福利视频 | 欧美一区二区免费电影 | 激情久久网| 青青草华人在线视频 | 久久99蜜桃综合影院免费观看 | 欧美成人a∨高清免费观看 91伊人 | 91亚洲国产成人久久精品网站 | 欧美一区二区三区免费在线观看 | www.亚洲区 | 精品国产女人 | 亚洲美女av网站 | 国产无套一区二区三区久久 | 日韩视频观看 | 亚洲视频在线看 | 日韩久久中文字幕 | 欧美日韩国产一区二区三区 | 一级毛片观看 | 久久精选 | 久久久久久国产精品免费免费狐狸 | 日韩在线看片 | 久久精品中文 | 99精品久久99久久久久 | 一区二区三区视频播放 | 成人亚洲一区 | 日日日日日日bbbbb视频 | 一级毛片大全免费播放 | 国产精品一区二区三区免费观看 | 国外激情av| 国产免费一区二区 | 久久国产精品免费视频 | 久久草在线视频 | 欧美αv | 一本色道精品久久一区二区三区 | 亚洲精品久久视频 | 国产视频中文字幕 | 欧美色综合 | 91精品久久久久久久久久入口 |