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…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...

C++实现分布式网络通信框架RPC(3)--rpc调用端
目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中,我们已经大致实现了rpc服务端的各项功能代…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...

04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
什么?连接服务器也能可视化显示界面?:基于X11 Forwarding + CentOS + MobaXterm实战指南
文章目录 什么是X11?环境准备实战步骤1️⃣ 服务器端配置(CentOS)2️⃣ 客户端配置(MobaXterm)3️⃣ 验证X11 Forwarding4️⃣ 运行自定义GUI程序(Python示例)5️⃣ 成功效果
听写流程自动化实践,轻量级教育辅助
随着智能教育工具的发展,越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式,也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建,…...

技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...

莫兰迪高级灰总结计划简约商务通用PPT模版
莫兰迪高级灰总结计划简约商务通用PPT模版,莫兰迪调色板清新简约工作汇报PPT模版,莫兰迪时尚风极简设计PPT模版,大学生毕业论文答辩PPT模版,莫兰迪配色总结计划简约商务通用PPT模版,莫兰迪商务汇报PPT模版,…...