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…...
【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型
摘要 拍照搜题系统采用“三层管道(多模态 OCR → 语义检索 → 答案渲染)、两级检索(倒排 BM25 向量 HNSW)并以大语言模型兜底”的整体框架: 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后,分别用…...
Linux-07 ubuntu 的 chrome 启动不了
文章目录 问题原因解决步骤一、卸载旧版chrome二、重新安装chorme三、启动不了,报错如下四、启动不了,解决如下 总结 问题原因 在应用中可以看到chrome,但是打不开(说明:原来的ubuntu系统出问题了,这个是备用的硬盘&a…...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...
RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...
Java + Spring Boot + Mybatis 实现批量插入
在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法:使用 MyBatis 的 <foreach> 标签和批处理模式(ExecutorType.BATCH)。 方法一:使用 XML 的 <foreach> 标签ÿ…...
云原生安全实战:API网关Kong的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关(API Gateway) API网关是微服务架构中的核心组件,负责统一管理所有API的流量入口。它像一座…...
Golang——6、指针和结构体
指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...
通过MicroSip配置自己的freeswitch服务器进行调试记录
之前用docker安装的freeswitch的,启动是正常的, 但用下面的Microsip连接不上 主要原因有可能一下几个 1、通过下面命令可以看 [rootlocalhost default]# docker exec -it freeswitch fs_cli -x "sofia status profile internal"Name …...
命令行关闭Windows防火墙
命令行关闭Windows防火墙 引言一、防火墙:被低估的"智能安检员"二、优先尝试!90%问题无需关闭防火墙方案1:程序白名单(解决软件误拦截)方案2:开放特定端口(解决网游/开发端口不通)三、命令行极速关闭方案方法一:PowerShell(推荐Win10/11)方法二:CMD命令…...
32位寻址与64位寻址
32位寻址与64位寻址 32位寻址是什么? 32位寻址是指计算机的CPU、内存或总线系统使用32位二进制数来标识和访问内存中的存储单元(地址),其核心含义与能力如下: 1. 核心定义 地址位宽:CPU或内存控制器用32位…...
