canvas力导布局

老规矩,先上效果图

<html><head><style>* {margin: 0;padding: 0;}canvas {display: block;width: 100%;height: 100%;background: #000;}</style>
</head><body><canvas id="network"></canvas>
</body>
<script>class TaskQueue {constructor() {this.taskList = []this.hasTaskDone = falsethis.status = 'do' // do or stopthis.requestAnimationFrame = nullthis.requestAnimationFrameDrawBind = this.requestAnimationFrameDraw.bind(this)}addTask(func) {this.taskList.push(func)if (this.requestAnimationFrame === null) {this.addRequestAnimationFrame()this.do()}}do() {this.status = 'do'new Promise(res => {this.taskList[0] && this.taskList[0]()this.taskList.shift()this.hasTaskDone = trueres()}).then(() => {if (this.status === 'do' && this.taskList.length) {this.do()}})}stop() {this.status = 'stop'}requestAnimationFrameDraw() {this.stop()if (this.hasTaskDone && this.reDraw) {this.hasTaskDone = falsethis.reDraw()}if (this.taskList.length) {this.addRequestAnimationFrame()this.do()} else {this.clearRequestAnimationFrame()}}addRequestAnimationFrame() {this.requestAnimationFrame = window.requestAnimationFrame(this.requestAnimationFrameDrawBind)}clearRequestAnimationFrame() {window.cancelAnimationFrame(this.requestAnimationFrame)this.requestAnimationFrame = null}removeEvent() {this.stop()this.clearRequestAnimationFrame()}}class Layout extends TaskQueue {constructor(opt) {super(opt)this.qIndex = opt.layout.qIndexthis.fStableL = opt.layout.fStableLthis.fIndex = opt.layout.fIndexthis.count = opt.layout.count || opt.nodes.length * Math.ceil(opt.nodes.length / 5)this.countForce = 0}doLayout() {this.countForce++if (this.countForce >= this.count) {return}// 计算开始this.forceComputed(this.arc, this.line)setTimeout(() => {this.addTask(() => {this.doLayout();})})}forceComputed(nodes) {nodes.forEach(item => {item.translateX = 0item.translateY = 0})nodes.forEach((curNode, index) => {// 库仑力计算for (let i = index + 1; i < nodes.length; i++) {let otherNode = nodes[i]if (otherNode) {this.computedXYByQ(curNode, otherNode)}}// 弹簧力计算if (curNode.fromArcs?.length) {curNode.fromArcs.forEach(id => {let fromNode = nodes.filter(node => {return node.id === id})[0]if (fromNode) {this.computedXYByK(curNode, fromNode)}})}// 中心拉力if (curNode.fromArcs?.length) {this.computedXYByK(curNode, {xy: {x: this.canvas.width / 2,y: this.canvas.height / 2}})}})// let maxTranslate = 1// nodes.forEach(item => {//     if(item.translateX && Math.abs(item.translateX) > maxTranslate){//         maxTranslate = Math.abs(item.translateX)//     }//     if(item.translateY && Math.abs(item.translateY) > maxTranslate){//         maxTranslate = Math.abs(item.translateY)//     }// })// nodes.forEach(item => {//     if(item.translateX){//         item.x += item.translateX / maxTranslate//     }//     if(item.translateY){//         item.y += item.translateY / maxTranslate//     }// })nodes.forEach(item => {if (item.translateX) {item.xy.x += item.translateX}if (item.translateY) {item.xy.y += item.translateY}})}computedXYByQ(node1, node2) {let x1 = node1.xy.xlet y1 = node1.xy.ylet x2 = node2.xy.xlet y2 = node2.xy.ylet xl = x2 - x1let yl = y2 - y1let angle = Math.PIif (!xl) {if (y2 > y1) {angle = -Math.PI / 2} else {angle = Math.PI / 2}} else if (!yl) {if (x2 > x1) {angle = 0} else {angle = Math.PI}} else {angle = Math.atan(yl / xl)}let r = Math.sqrt(Math.pow(xl, 2) + Math.pow(yl, 2))if (r < 1) {r = 1}// 库仑力 r越大,库仑力越小let node1Q = (node1.fromNodes?.length || 0) + (node1.toNodes?.length || 0) + 1let node2Q = (node2.fromNodes?.length || 0) + (node2.toNodes?.length || 0) + 1let f = this.qIndex * node1Q * node2Q / Math.pow(r, 2)let fx = f * Math.cos(angle)let fy = f * Math.sin(angle)node1.translateX = node1.translateXnode1.translateY = node1.translateYnode2.translateX = node2.translateXnode2.translateY = node2.translateY// node1.translateX -= fx// node2.translateX += fx// node1.translateY -= fy// node2.translateY += fyif (x2 > x1) {if (fx > 0) {node1.translateX -= fxnode2.translateX += fx} else {node1.translateX += fxnode2.translateX -= fx}} else {if (fx > 0) {node1.translateX += fxnode2.translateX -= fx} else {node1.translateX -= fxnode2.translateX += fx}}if (y2 > y1) {if (fy > 0) {node1.translateY -= fynode2.translateY += fy} else {node1.translateY += fynode2.translateY -= fy}} else {if (fy > 0) {node1.translateY += fynode2.translateY -= fy} else {node1.translateY -= fynode2.translateY += fy}}}computedXYByK(node1, node2) {let x1 = node1.xy.xlet y1 = node1.xy.ylet x2 = node2.xy.xlet y2 = node2.xy.ylet xl = x2 - x1let yl = y2 - y1let angle = Math.PIif (!xl) {if (y2 > y1) {angle = -Math.PI / 2} else {angle = Math.PI / 2}} else if (!yl) {if (x2 > x1) {angle = 0} else {angle = Math.PI}} else {angle = Math.atan(yl / xl)}let r = Math.sqrt(Math.pow(xl, 2) + Math.pow(yl, 2))if (r > this.fStableL * 2) {r = this.fStableL * 2} else if (r < 1) {r = 1}// 弹簧力let f = this.fIndex * (r - this.fStableL)let fx = f * Math.cos(angle)let fy = f * Math.sin(angle)node1.translateX = node1.translateXnode1.translateY = node1.translateYnode2.translateX = node2.translateXnode2.translateY = node2.translateYif (f > 0) {// 拉力if (x2 > x1) {if (fx > 0) {node1.translateX += fxnode2.translateX -= fx} else {node1.translateX -= fxnode2.translateX += fx}} else {if (fx > 0) {node1.translateX -= fxnode2.translateX += fx} else {node1.translateX += fxnode2.translateX -= fx}}if (y2 > y1) {if (fy > 0) {node1.translateY += fynode2.translateY -= fy} else {node1.translateY -= fynode2.translateY += fy}} else {if (fy > 0) {node1.translateY -= fynode2.translateY += fy} else {node1.translateY += fynode2.translateY -= fy}}} else {// 弹力if (x2 > x1) {if (fx > 0) {node1.translateX -= fxnode2.translateX += fx} else {node1.translateX += fxnode2.translateX -= fx}} else {if (fx > 0) {node1.translateX += fxnode2.translateX -= fx} else {node1.translateX -= fxnode2.translateX += fx}}if (y2 > y1) {if (fy > 0) {node1.translateY -= fynode2.translateY += fy} else {node1.translateY += fynode2.translateY -= fy}} else {if (fy > 0) {node1.translateY += fynode2.translateY -= fy} else {node1.translateY -= fynode2.translateY += fy}}}}}class View extends Layout {constructor(opt) {super(opt)this.canvas = opt.canvasthis.dpr = window.devicePixelRatio || 1this.nodes = opt.nodesthis.paths = opt.pathsthis.circleStyle = opt.circleStylethis.lineStyle = opt.lineStylethis.line = []this.arc = []this.init()}init() {if (!this.canvas) {return}if (this.canvas.width !== Math.floor(this.canvas.offsetWidth * this.dpr) || this.canvas.height !== Math.floor(this.canvas.offsetHeight * this.dpr)) {this.canvas.width = Math.floor(this.canvas.offsetWidth * this.dpr)this.canvas.height = Math.floor(this.canvas.offsetHeight * this.dpr)}this.ctx = this.canvas.getContext('2d')this.addData(this.nodes, this.paths)}addData(nodes, paths) {if (nodes && nodes.length) {this.addArc(nodes)}if (paths && paths.length) {this.addLine(paths)}super.countForce = 0super.doLayout()}addArc(nodes) {// 数据多时可以考虑将初始化随机坐标范围与数据量做等比函数nodes.forEach(node => {this.arc.push({id: node.id,fromArcs: [],toArcs: [],xy: {x: this.rand(0, this.canvas.width),y: this.rand(0, this.canvas.height)}})})}addLine(paths) {paths.forEach(path => {let fromArc = this.arc.filter(node => {return node.id === path.from})[0]let toArc = this.arc.filter(node => {return node.id === path.to})[0]fromArc.toArcs.push(toArc.id)toArc.fromArcs.push(fromArc.id)if (fromArc && toArc) {this.line.push({id: path.id,from: path.from,to: path.to,fromArc,toArc})}})}reDraw() {this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height)this.draw()}draw() {this.line.forEach(item => {this.drawLine(item)})this.arc.forEach(item => {this.drawArc(item)})}drawLine(data) {this.ctx.save()this.ctx.translate(this.canvas.width / 2, this.canvas.height / 2)this.ctx.scale(this.scaleC, this.scaleC)this.ctx.translate(-this.canvas.width / 2, -this.canvas.height / 2)this.ctx.beginPath()this.ctx.lineWidth = this.lineStyle.widththis.ctx.strokeStyle = this.lineStyle.colorthis.ctx.moveTo(data.fromArc.xy.x, data.fromArc.xy.y)this.ctx.lineTo(data.toArc.xy.x, data.toArc.xy.y)this.ctx.stroke()this.ctx.closePath()this.ctx.restore()}drawArc(data) {this.ctx.save()this.ctx.translate(this.canvas.width / 2, this.canvas.height / 2)this.ctx.scale(this.scaleC, this.scaleC)this.ctx.translate(-this.canvas.width / 2, -this.canvas.height / 2)this.ctx.beginPath()this.ctx.fillStyle = this.circleStyle.backgroundthis.ctx.arc(data.xy.x, data.xy.y, this.circleStyle.r, 0, 2 * Math.PI)this.ctx.fill()this.ctx.closePath()this.ctx.restore()}rand = (n, m) => {var c = m - n + 1return Math.floor(Math.random() * c + n)}}// 测试数据let data = {"nodes": [{"id": "36"},{"id": "50"},{"id": "20077"},{"id": "1090"},{"id": "1078"},{"id": "10007"},{"id": "20039"},{"id": "1074"},{"id": "20058"},{"id": "1062"},{"id": "10001"},{"id": "20076"},{"id": "1089"},{"id": "20038"},{"id": "1068"},{"id": "20057"},{"id": "1081"},{"id": "20070"},{"id": "1034"},{"id": "1077"},{"id": "10002"},{"id": "10003"},{"id": "20069"},{"id": "1002"},{"id": "47"},{"id": "10010"},{"id": "14"},{"id": "42"},{"id": "94"},{"id": "16"},{"id": "41"},{"id": "64"},{"id": "20002"},{"id": "73"},{"id": "1001"},{"id": "10009"},{"id": "10008"},{"id": "10006"},{"id": "10005"},{"id": "10004"},{"id": "33"},{"id": "10"},{"id": "18"},{"id": "70"},{"id": "98"},{"id": "20"},{"id": "24"},{"id": "20001"}],"paths": [{"id": "606","from": "50","to": "36"},{"id": "346","from": "20077","to": "1090"},{"id": "343","from": "1078","to": "10007"},{"id": "382","from": "20039","to": "1074"},{"id": "419","from": "20058","to": "1062"},{"id": "344","from": "1078","to": "10001"},{"id": "356","from": "20076","to": "1089"},{"id": "439","from": "20038","to": "1068"},{"id": "417","from": "20057","to": "1081"},{"id": "358","from": "20070","to": "1078"},{"id": "438","from": "20038","to": "1034"},{"id": "248","from": "1077","to": "10002"},{"id": "249","from": "1077","to": "10003"},{"id": "364","from": "20069","to": "1077"},{"id": "4797","from": "1002","to": "10003"},{"id": "4787","from": "1002","to": "10002"},{"id": "223","from": "1002","to": "10003"},{"id": "222","from": "1002","to": "10002"},{"id": "2659","from": "1002","to": "47"},{"id": "4777","from": "1002","to": "10001"},{"id": "4867","from": "1002","to": "10010"},{"id": "1466","from": "14","to": "1002"},{"id": "1437","from": "42","to": "1002"},{"id": "1414","from": "94","to": "1002"},{"id": "1411","from": "16","to": "1002"},{"id": "1395","from": "16","to": "1002"},{"id": "1382","from": "41","to": "1002"},{"id": "1377","from": "64","to": "1002"},{"id": "436","from": "20002","to": "1002"},{"id": "2658","from": "73","to": "1002"},{"id": "4856","from": "1001","to": "10009"},{"id": "4846","from": "1001","to": "10008"},{"id": "4836","from": "1001","to": "10007"},{"id": "4826","from": "1001","to": "10006"},{"id": "4816","from": "1001","to": "10005"},{"id": "4806","from": "1001","to": "10004"},{"id": "4796","from": "1001","to": "10003"},{"id": "4786","from": "1001","to": "10002"},{"id": "4776","from": "1001","to": "10001"},{"id": "221","from": "1001","to": "10001"},{"id": "4866","from": "1001","to": "10010"},{"id": "1469","from": "33","to": "1001"},{"id": "1459","from": "10","to": "1001"},{"id": "1448","from": "18","to": "1001"},{"id": "1406","from": "70","to": "1001"},{"id": "1396","from": "47","to": "1001"},{"id": "1369","from": "98","to": "1001"},{"id": "1365","from": "20","to": "1001"},{"id": "1363","from": "24","to": "1001"},{"id": "406","from": "20001","to": "1001"}]}// canvas domconst canvas = document.getElementById('network');new View({canvas,nodes: data.nodes,paths: data.paths,circleStyle: {r: 10,background: '#FFFFFF'},lineStyle: {width: 1,color: '#FFFFFF'},layout: {qIndex: 2000, // 库仑力系数,值越大,库仑力越大fStableL: 80,fIndex: 0.1, // 拉力系数,数值越大,力越大}})
</script></html>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/102136.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何选择高防CDN和高防IP?

目录 前言 一、对高防CDN的选择 1. 加速性能 2. 抗攻击能力 3. 全球覆盖能力 4. 可靠性和稳定性 二、对高防IP的选择 1. 防御能力 2. 服务质量 3. 安全性 4. 价格 三、高防CDN和高防IP的优缺点对比 1. 高防CDN的优缺点 2. 高防IP的优缺点 总结 前言 随着互联网…

U盘怎么设置为只读?U盘怎么只读加密?

当将U盘设置为只读模式时&#xff0c;将只能查看其中数据&#xff0c;无法对其中数据进行编辑、复制、删除等操作。那么&#xff0c;怎么将U盘设置成只读呢&#xff1f; U盘如何设置成只读&#xff1f; 有些U盘带有写保护开关&#xff0c;当打开时&#xff0c;U盘就会处于只读…

京东商品品牌数据采集接口,京东商品详情数据接口,京东API接口

采集京东商品品牌数据的方法如下&#xff1a; 打开网页。在首页【输入框】中输入目标网址批量输入多个关键词并搜索。创建【循环列表】&#xff0c;采集所有商品列表中的数据。编辑字段。创建【循环翻页】&#xff0c;采集多页数据。设置滚动和修改【循环翻页】XPath。启动采集…

使用docker安装db2

使用docker安装db2 1. 前言1.1 关于docker的安装1.2 安装db2版本选择参考 2. 拉取镜像3. 启动镜像4. 进入容器&#xff0c;切换用户4.1 进入容器4.2 切换用户4.3 其他命令 5. 可视化工具DBeaver连接db25.1 连接5.2 简单使用 1. 前言 1.1 关于docker的安装 关于Linux上docker的…

【考研408常用数据结构】C/C++实现代码汇总

文章目录 前言数组多维数组的原理、作用稀疏数组 链表单向链表的增删改查的具体实现思路约瑟夫环问题&#xff08;可不学&#xff09;双向链表 树二叉搜索树中序线索二叉树哈夫曼树的编码与译码红黑树B树B树 堆顺序与链式结构队列实现优先队列排序算法&#xff08;重点&#xf…

第五节 C++ 循环结构(算法)

文章目录 前言介绍1. for 语句1.1 语法结构1.2 语法流程的执行过程1.2.1 案例 1:循环的正序输入和倒序输入1.2.2 案例2 : 求1~n的平方数1.2.3 案例 3: 求输入a和b,求a~b区间数. 1.3 for 循环案例练习1.3.1 求最大值与最小值1.3.2 计算奇数和和偶数和1.3.3 计算平均气温与最高气…

Stable diffusion 用DeOldify给黑白照片、视频上色

老照片常常因为当时的技术限制而只有黑白版本。然而现代的 AI 技术,如 DeOldify,可以让这些照片重现色彩。 本教程将详细介绍如何使用 DeOldify 来给老照片上色。. 之前介绍过基于虚拟环境的 基于DeOldify的给黑白照片、视频上色,本次介绍对于新手比较友好的在Stable diff…

04-RocketMQ源码解读

目录汇总&#xff1a;RocketMQ从入门到精通汇总 上一篇&#xff1a;03-RocketMQ高级原理 这一部分&#xff0c;我们开始深入RocketMQ的源码。源码的解读是个非常困难的过程&#xff0c;每个人的理解程度都会不一样&#xff0c;也不太可能通过讲解把其中的细节全部讲明白。我们今…

13私有化属性的一些注意点

目录 一、解释一下几个私有化访问的方式&#xff01;&#xff01;&#xff01; 1.类内部访问&#xff1a; 2.子类访问 3.模块内其他位置访问 4.跨模块访问 二、_y受保护属性的访问 只有跨模块这块需要注意一下&#xff01; 这个是可以访问的 但是下面这个是不可以的 …

Python皮卡丘

系列文章 序号文章目录直达链接1浪漫520表白代码https://want595.blog.csdn.net/article/details/1306668812满屏表白代码https://want595.blog.csdn.net/article/details/1297945183跳动的爱心https://want595.blog.csdn.net/article/details/1295031234漂浮爱心https://want…

Element UI怎么安装呢?

安装 :::warning 注意 后续演示将会在 Vue CLI 搭建的 Vue 项目上进行操作。如需要请查看 Vue CLI 安装 ::: 通过 YARN 命令安装 $ yarn add element-ui完整引入 代表一次性引入所有组件&#xff0c;比较省心省事&#xff0c;但是项目的打包体积也会跟着变大。 // main.js…

3.简单场景构建

在新建的项目中&#xff0c;默认存在 Main Camera 和 Directional Light两个对象。若是缺失&#xff0c;可通过选择菜单中的 Game Object->Camera 和 Geme Object->Light->Directional Light进行创建。 1.添加地形及底图 通过在Cesium面板中选择 Cesium World Terrai…

VAE模型(详细推导+实例代码)

文章目录 EM算法思路E步M步直观感觉 GMM模型VAEVAE思想从GMM到VAE公式推导重参数VAE神经网络另一个视角的VAE思想为什么引入encoder为什么要重参数噪声与重建 Discrete VAE 本文会从EM算法&#xff0c;GMM模型一步一步的的推导&#xff0c;在过渡到VAE模型&#xff0c;如果有熟…

Excel·VBA使用ADO合并工作簿

之前文章《ExcelVBA合并工作簿&#xff08;7&#xff0c;合并子文件夹同名工作簿中同名工作表&#xff0c;纵向汇总数据&#xff09;》处理合并工作簿问题&#xff0c;代码运行速度比较慢 而《ExcelVBA使用ADO读取工作簿工作表数据》读取数据非常快&#xff0c;那么是否可以使用…

1200*C1. k-LCM (easy version)(找规律)

Problem - 1497C1 - Codeforces 解析&#xff1a; 找规律即可&#xff0c;分为偶数的一半是偶数、偶数的一半是奇数、奇数三种情况 分别为 &#xff08;n/2&#xff0c;n/4&#xff0c;n/4&#xff09;&#xff08;n/2-1&#xff0c;n/2-1&#xff0c;2&#xff09;&#xff08…

常见算法-双骰子游戏(Craps)

常见算法-双骰子游戏&#xff08;Craps&#xff09; 1、说明 一个简单的双骰子游戏&#xff0c;游戏规则如下&#xff1a; 玩家掷两个骰子&#xff0c;点数为1到6&#xff0c; 如果第一次点数和为7或11&#xff0c;则玩家胜&#xff0c;如果点数和为2、3或12&#xff0c;则…

Nlopt在matlab中的配置教程

step1&#xff1a;克隆代码并编译 编译的前提是已经安装好MinGW64 # 使用镜像加速 git clone https://gitclone.com/github.com/stevengj/nloptcd nlopt mkdir build cd build cmake -G"MinGW Makefiles" .. cmake --build .# 注意此处博主在mingw安装目录将mingw3…

windows10下 iperf3测试带宽

iperf3下载网址&#xff1a;iPerf - Download iPerf3 and original iPerf pre-compiled binaries 可以用来测试TCP以及UDP带宽质量 通俗来说是用来测试网速的 准备&#xff1a;两台设备 1. 根据自己的设备选择下载工具&#xff08;两台都要有&#xff0c;这里我用的Window…

vue实现瀑布流

1、在 src 目录下创建 component文件夹&#xff0c;在文件夹中创建 vue文件。 2、在 Vue文件中写入以下内容 <div class"pubu"><div class"left"><div class"pubu-item" v-for"item in left" :key"item.id"…

react antd table表格点击一行选中数据的方法

一、前言 antd的table&#xff0c;默认是点击左边的单选/复选按钮&#xff0c;才能选中一行数据&#xff1b; 现在想实现点击右边的部分&#xff0c;也可以触发操作选中这行数据。 可以使用onRow实现&#xff0c;样例如下。 二、代码 1.表格样式部分 //表格table样式部分{…