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>
相关文章:

canvas力导布局
老规矩,先上效果图 <html><head><style>* {margin: 0;padding: 0;}canvas {display: block;width: 100%;height: 100%;background: #000;}</style> </head><body><canvas id"network"></canvas> </…...

【网络安全】「漏洞原理」(二)SQL 注入漏洞之理论讲解
前言 严正声明:本博文所讨论的技术仅用于研究学习,旨在增强读者的信息安全意识,提高信息安全防护技能,严禁用于非法活动。任何个人、团体、组织不得用于非法目的,违法犯罪必将受到法律的严厉制裁。 【点击此处即可获…...
JavaScript中类的学习
一、JavaScript中的类 1.什么是类 类描述了一种代码的组织结构形式,不同的语言中对其实现形式各有差异。JavaScript中的类Class实际是一种描述对象之间引用关系的语法糖。 在Class语法糖出现之前,我们想重用一个功能模块,通常是用一个函数来…...

1600*A. Linova and Kingdom(DFS优先队列贪心)
Problem - 1336A - Codeforces Linova and Kingdom - 洛谷 解析: 开始认为分情况讨论 k 小于等于叶子结点和大于叶子结点的情况,然后选择深度最深的叶子结点和子孙数量最小的结点,但是发现如果把某一个非叶子结点选取,那么其子孙…...
gitlab git lfs的替代软件整理汇总及分析
文章目录 前言替代软件分析git-annexgit-fatgit-symgit-meida 总结 前言 git-lfs科普 Git LFS(Large File Storage)是一个Git扩展,用于管理大型文件。Git LFS通过将大型文件存储在Git仓库之外,从而加快了Git操作的速度。它使用指…...

IDEA 2023.2.2图文安装教程及下载
IDE 系列的第二个年度更新现已发布,涵盖 IntelliJ IDEA、WebStorm、PyCharm、DataGrip、GoLand、DataSpell 以及 All Products Pack 订阅中包含的其他工具。该版本还包括多项用户体验增强功能,例如 Search Everywhere(随处搜索)中…...

第六届“中国法研杯”司法人工智能挑战赛
解锁司法科技的未来 “中国法研杯”司法人工智能挑战赛(Legal AI Challenge,简称LAIC),是面向法院侧人工智能应用领域唯一权威比赛,大赛愿景是在拥有全球最大规模司法数据的中国,实现法律界、学术界、产业界…...

Springcloud中间件-----分布式搜索引擎 Elasticsearch
该笔记是根据黑马程序员的课来自己写了一遍的,b站有对应教程和资料 第一部分 第二部分 第三部分 预计看完跟着练习5小时足够 1.初识elasticsearch 1.1.了解ES 1.1.1.elasticsearch的作用 elasticsearch是一款非常强大的开源搜索引擎,具备非常多强大功能ÿ…...
基于深度学习的目标检测和语义分割:机器视觉中的最新进展
基于深度学习的目标检测和语义分割是机器视觉领域的两个重要任务,它们在图像处理、自动驾驶、医学影像分析和智能视频监控等应用中发挥着关键作用。以下是这两个领域的最新进展: 目标检测(Object Detection): 一阶段检…...

微信小程序报错request:fail -2:net::ERR_FAILED(生成中间证书)
微信小程序报错request:fail -2:net::ERR_FAILED-生成中间证书 前言一、检查网站ssl证书二、生成证书方法1.获取中间证书手动合并1.进入网站:[https://www.myssl.cn/tools/downloadchain.html](https://www.myssl.cn/tools/downloadchain.html)2.点击下一步3.手动合…...
Ubuntu更改时区
sudo apt install tzdata 进行安装时区,有很多时区可供选择。 然后执行:tzselect rootd75c94dcd226:/# date 2023年 10月 11日 星期三 06:25:12 UTC rootd75c94dcd226:/# tzselect Please identify a location so that time zone rules can be set correctly. Ple…...
0144 文件管理
目录 4.文件管理 4.1文件系统基础 4.2目录 4.3文件系统 部分习题 4.文件管理 4.1文件系统基础 4.2目录 4.3文件系统 部分习题 1.UNIX操作系统忠,输入/输出设备视为() A.普通文件 B.目录文件 C.索引文件 D.特殊文…...

python psutil库之——获取网络信息(网络接口信息、网络配置信息、以太网接口、ip信息、ip地址信息)
文章目录 使用Python psutil库获取网络信息安装psutil库获取网络连接信息查看所有网络连接过滤特定状态的连接 获取网络接口信息获取网络IO统计信息实例1实例2 总结 使用Python psutil库获取网络信息 Python的psutil库是一个跨平台库,能够方便地获取系统使用情况和…...

uniapp上echarts地图钻取
1: 预期效果 通过切换地图 , 实现地图的钻取效果 2: 实现原理以及核心方法/参数 一开始是想利用更换地图数据的形式进行地图钻取 , 这就意味着我们需要准备全国30多个省份的地图数据 , 由于一开始考虑需要适配小程序端 , 如此多的地图文件增加了程序的体积 , 如果使用接口调…...

scratch保护环境 2023年5月中国电子学会图形化编程 少儿编程 scratch编程等级考试一级真题和答案解析
目录 scratch保护环境 一、题目要求 1、准备工作 2、功能实现 二、案例分析...

RPC分布式网络通信框架项目
文章目录 对比单机聊天服务器、集群聊天服务器以及分布式聊天服务器RPC通信原理使用Protobuf做数据的序列化,相比较于json,有哪些优点?环境配置使用项目代码工程目录vscode远程开发Linux项目muduo网络库编程示例CMake构建项目集成编译环境Lin…...

Navicat如何连接远程服务器的MySQL
参考:https://blog.csdn.net/a648119398/article/details/122420906 1.Navicat for Mysql 2.腾讯云轻量级服务器一台(Centos 7) 3.Mysql 8.0.24(远程服务器内安装的) 4.Xshell7(连接操作远程服务器) 一、修…...

【计算机网络笔记】计算机网络的结构
系列文章目录 什么是计算机网络? 什么是网络协议? 文章目录 系列文章目录网络边缘接入网络数字用户线路 (DSL)电缆网络典型家庭网络的接入机构(企业)接入网络 (Ethernet)无线接入网络 网络核心Internet结构最后 计算机网络的结构…...

排序算法-插入排序法(InsertSort)
排序算法-插入排序法(InsertSort) 1、说明 插入排序法是将数组中的元素逐一与已排序好的数据进行比较,先将前两个元素排序好,再将第三个元素插入适当的位置,也就是说这三个元素仍然是已排序好的,接着将第…...

RuntimeError: “slow_conv2d_cpu“ not implemented for ‘Half‘
RuntimeError: “slow_conv2d_cpu” not implemented for ‘Half’ 背景 测试语音识别模型whisper时,出现上述错误!! 测试代码如下: import whispermodel whisper.load_model("base") # print(model)# load audio an…...

(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...

现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...

人机融合智能 | “人智交互”跨学科新领域
本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...

【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)
前言: 双亲委派机制对于面试这块来说非常重要,在实际开发中也是经常遇见需要打破双亲委派的需求,今天我们一起来探索一下什么是双亲委派机制,在此之前我们先介绍一下类的加载器。 目录 编辑 前言: 类加载器 1. …...
LLaMA-Factory 微调 Qwen2-VL 进行人脸情感识别(二)
在上一篇文章中,我们详细介绍了如何使用LLaMA-Factory框架对Qwen2-VL大模型进行微调,以实现人脸情感识别的功能。本篇文章将聚焦于微调完成后,如何调用这个模型进行人脸情感识别的具体代码实现,包括详细的步骤和注释。 模型调用步骤 环境准备:确保安装了必要的Python库。…...

Python环境安装与虚拟环境配置详解
本文档旨在为Python开发者提供一站式的环境安装与虚拟环境配置指南,适用于Windows、macOS和Linux系统。无论你是初学者还是有经验的开发者,都能在此找到适合自己的环境搭建方法和常见问题的解决方案。 快速开始 一分钟快速安装与虚拟环境配置 # macOS/…...

Linux操作系统共享Windows操作系统的文件
目录 一、共享文件 二、挂载 一、共享文件 点击虚拟机选项-设置 点击选项,设置文件夹共享为总是启用,点击添加,可添加需要共享的文件夹 查询是否共享成功 ls /mnt/hgfs 如果显示Download(这是我共享的文件夹)&…...