图扑 HT for Web 总线式拓扑图的可视化实现

在图形用户界面(GUI)设计中,自定义连线技术不仅提升了用户体验,还为复杂数据可视化开辟了新的可能性。该功能点允许用户灵活地在界面元素之间创建视觉连接,使流程图、思维导图和网络拓扑图等信息呈现更加直观和动态。

图扑软件自研 HT for Web 产品框架中,ht.Edge 节点用于表示节点间的连线关系。熟悉 HT 的用户应该了解 ht.Edge 内置了多种连线类型,能满足一般拓扑图需求,但在特殊情况下,这些默认类型可能无法满足需求。为此,HT 提供了自定义连线功能,允许开发者根据具体需求创建特殊的连线类型,实现更灵活的图形表示。

自定义连线

图扑 HT 框架提供灵活的自定义连线功能,开发者可以通过调用 ht.Default.setEdgeType(type, func, mutual) 方法来创建独特的连线类型。以下是该方法的参数详解:

■type:自定义连线类型的名称,与 style 中的 edge.type 属性相对应。

■func:计算连线路径信息的函数,接收四个参数:

gap:多条连线成捆时,本连线对象对应中心连线的间距。

edge:当前连线对象。

graphView:当前对应拓扑组件对象。

sameSourceWithFirstEdge:boolean 类型,该连线是否与同组的第一条连线同源。

■mutual:决定该连线类型是否会影响同一起始或结束节点上的其他连线。

接下来,我们深入分析一种常见的拓扑关系实现步骤,即"横-竖-横"的连线方式。

下面是一段定义上图连线类型的示例代码。代码很简单,首先获取起始节点和目标节点的信息,然后根据这两个节点的坐标,按照预定的规则计算出连线的路径点。

定义好连线类型后,只需通过 edge.s('edge.type', 'horizontal-vertical') 这段简单的代码行,就能将 edge 对象的连线设置为我们刚刚定义的类型。由此一来,即可看到令人满意的效果,大幅提升图形的可读性和美观度。

总线拓扑

总线拓扑是一种网络结构,所有设备(如计算机、打印机等)都连接到一个共同的通信介质上,通常是一根电缆,这个介质被称为"总线"(bus)。总线拓扑在工业控制和嵌入式系统等特定领域中被广泛应用。在图扑 HT 框架中,我们可以利用 ht.Shape 组件绘制总线,并通过 ht.Edge 组件将各个设备节点连接到总线上。这些连接的视觉表现可通过自定义连线类型灵活定义,从而实现精确的总线拓扑图表示。

上面展示的是一个总线的示例效果,可以直观看到所有设备都连接到了总线上。在具体实现过程中,最具挑战性的问题是:如何计算出总线上距离目标节点坐标最近的点?

计算节点到总线距离

总线通常由多条直线段组成,因此计算某一节点到总线的最短距离可按以下思路进行:

将总线分割为多段直线

总线由多个直线段构成,可以取总线上相邻两点构成一条直线。具体实现时,遍历 points 数据,获取 points[index] 和 points[index+1] 作为线段的两个端点。注意,如果设置了 segments,其中 1 代表新路径的起点,所以当 segments[index+1] 为 1 时应跳过。

计算点到每条直线的距离

获取每条直线段后,计算节点坐标到各线段的距离,并将距离值存入一个集合中

获取最短距离

从距离集合中找出最小值,即为节点到总线的最短距离。

基于上述思路,我们可以实现一个总线连线类型。以下是具体的实现代码:

// 计算点到直线的距离,返回结果是个对象结构
var pointToInsideLine = function (p1, p2, p) {
var x1 = p1.x,
y1 = p1.y,
x2 = p2.x,
y2 = p2.y,
x = p.x,
y = p.y,
result = {},
dx = x2 - x1,
dy = y2 - y1,
d = Math.sqrt(dx * dx + dy * dy),
ca = dx / d, // cosine
sa = dy / d, // sine
mX = (-x1 + x) * ca + (-y1 + y) * sa;
result.x = x1 + mX * ca;
result.y = y1 + mX * sa;
if (!isPointInLine(result, p1, p2)) {
result.x = Math.abs(result.x - p1.x) < Math.abs(result.x - p2.x) ? p1.x : p2.x;
result.y = Math.abs(result.y - p1.y) < Math.abs(result.y - p2.y) ? p1.y : p2.y;
}
dx = x - result.x;
dy = y - result.y;
result.z = Math.sqrt(dx * dx + dy * dy);
return result;
};
// 判断点是否在线上
var isPointInLine = function (p, p1, p2) {
return p.x >= Math.min(p1.x, p2.x) &&
p.x <= Math.max(p1.x, p2.x) &&
p.y >= Math.min(p1.y, p2.y) &&
p.y <= Math.max(p1.y, p2.y);
};
// 注册连线类型
ht.Default.setEdgeType('bus', function (edge) {
var source = edge.getSourceAgent(),
target = edge.getTargetAgent();
var targetP = target.p();
var points = source.getPoints().toArray();
var segments = source.getSegments();
var beginPoint;
for (let i = 0; i < points.length - 1; i++) {
if (segments) {
if (segments[i + 1] === 1) continue;
}
const point1 = points[i];
const point2 = points[i + 1];
const minPosition = pointToInsideLine(point1, point2, targetP);
if (!beginPoint || minPosition.z < beginPoint.z) {
beginPoint = minPosition;
}
}
return {
points: new ht.List([ beginPoint, targetP ]),
segments: new ht.List([1, 2])
};
});

执行上述代码后,我们将得到如下效果:

从上图可以清楚看出,示例成功获取了节点到总线的最近点,并绘制了相应的连线节点。值得注意的是,对于直线段而言,节点在直线上的投影点即为其距总线最近的点。

视觉美感优化

虽然示例已实现了基础总线效果,但由于拓扑图采用 2.5D 效果,仅计算投影点可能无法呈现理想的视觉效果。为了增强视觉表现,我们可以考虑让连线旋转一定角度。为此,我们可以在现有功能的基础上添加旋转代码,使连线与整体图形更加协调,提升视觉美感。

ht.Default.setEdgeType('bus', function (edge) {
var source = edge.getSourceAgent(),
target = edge.getTargetAgent();
var targetP = target.p();
var points = source.getPoints().toArray();
var segments = source.getSegments();
var beginPoint, linePoints;
for (let i = 0; i < points.length - 1; i++) {
if (segments) {
if (segments[i + 1] === 1) continue;
}
const point1 = points[i];
const point2 = points[i + 1];
const minPosition = pointToInsideLine(point1, point2, targetP);
if (!beginPoint || minPosition.z < beginPoint.z) {
beginPoint = minPosition;
linePoints = [point1, point2]
}
}
var rotation = angleBetweenLineAndHorizontal(linePoints[0], linePoints[1]);
var rotatePoint = findIntersection([rotatePointAroundAnotherPoint(beginPoint, targetP, rotation), targetP], linePoints);
if(isPointInLine(rotatePoint, linePoints[0], linePoints[1])){
beginPoint = rotatePoint;
}
return {
points: new ht.List([
beginPoint, targetP
]),
segments: new ht.List([1, 2])
};
});
/**
* 计算两点之间直线与水平线的夹角
*/
function angleBetweenLineAndHorizontal(p1, p2) {
if (new ht.Math.Vector2(p1.x, p1.y).length() > new ht.Math.Vector2(p2.x, p2.y).length()) {
var p = p2;
p2 = p1;
p1 = p;
}
var x1 = p1.x,
y1 = p1.y,
x2 = p2.x,
y2 = p2.y;
var dx = x2 - x1;
var dy = y2 - y1;
var angleRadians = Math.atan2(dy, dx); // 计算夹角(弧度)
var angleDegrees = angleRadians * (180 / Math.PI); // 弧度转角
// 确保角度在 0 到 360 之间
if (angleDegrees < 0) {
angleDegrees += 360;
}
return angleDegrees;
}
function rotatePointAroundAnotherPoint(point, center, angleDegrees) {
var angleRadians = angleDegrees * (Math.PI / 180);
var cosTheta = Math.cos(angleRadians);
var sinTheta = Math.sin(angleRadians);
var translatedX = point.x - center.x;
var translatedY = point.y - center.y;
var rotatedX = translatedX * cosTheta - translatedY * sinTheta;
var rotatedY = translatedX * sinTheta + translatedY * cosTheta;
var finalX = rotatedX + center.x;
var finalY = rotatedY + center.y;
return { x: finalX, y: finalY };
}
/**
* 给定两个点,计算直线的系数 A, B, C
* 直线方程:Ax + By = C
*/
function getLineEquation(x1, y1, x2, y2) {
var A = y2 - y1;
var B = x1 - x2;
var C = A * x1 + B * y1;
return { A, B, C };
}
/**
* 计算两条直线的交点
*/
function calculateIntersection(line1, line2) {
var { A: A1, B: B1, C: C1 } = line1;
var { A: A2, B: B2, C: C2 } = line2;
var determinant = A1 * B2 - A2 * B1;
if (determinant === 0) {
// 平行或重合
return null;
} else {
var x = (C1 * B2 - C2 * B1) / determinant;
var y = (A1 * C2 - A2 * C1) / determinant;
return { x, y };
}
}
/**
* 找到两条线的交点,或者延长线的交点
*/
function findIntersection(line1Points, line2Points) {
var [p1, p2] = line1Points;
var [p3, p4] = line2Points;
var line1 = getLineEquation(p1.x, p1.y, p2.x, p2.y);
var line2 = getLineEquation(p3.x, p3.y, p4.x, p4.y);
var intersection = calculateIntersection(line1, line2);
return intersection;
}

实现的最终效果如下:

图扑软件 HT 自定义连线功能为图形交互设计开辟了广阔的新天地。从基本的"横-竖-横"连线到复杂的总线拓扑图,不仅提升了数据可视化的灵活性,还大幅增强了用户体验。通过精细调整连线的旋转角度和投影点,在 2.5D 效果中呈现更加美观和直观的拓扑关系。

不仅适用于网络结构的展示,还可扩展到各种复杂系统的可视化中。为设计师和开发者提供了强大的工具,帮助他们创造出更加丰富、富有表现力的图形界面。

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

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

相关文章

大语言模型中的梯度值:深入理解与应用

1. 摘要 ​ 梯度是微积分中的一个基本概念&#xff0c;在机器学习和深度学习中扮演着至关重要的角色。特别是在大语言模型&#xff08;LLM&#xff09;的训练过程中&#xff0c;梯度指导着模型参数的优化方向。 本报告首先由浅入深地介绍梯度的概念&#xff0c;包括其数学定义…

Linux的用户管理

Linux系统是一个多用户多任务的操作系统&#xff0c;任何一个要使用系统资源的用户&#xff0c;都必须首先向系统管理员申请一个账号&#xff0c;然后以这个账号的身份进入系统 root用户可以创建多个普通用户 一、添加用户 基本语法&#xff1a;useradd 用户名 当创建用户成…

C++第十七讲:map和set封装

C第十七讲&#xff1a;map和set封装 1.源码发现不同2.Mymap && Myset2.1红黑树的源码更改2.2迭代器的实现2.2.1源码的迭代器区别2.2.2const iterator的实现 2.3insert的实现2.4operator[]的理解 这一讲比较困难&#xff0c;我们首先会通过看map和set底层的源码&#xf…

Day9 25/2/22 SAT

【一周刷爆LeetCode&#xff0c;算法大神左神&#xff08;左程云&#xff09;耗时100天打造算法与数据结构基础到高级全家桶教程&#xff0c;直击BTAJ等一线大厂必问算法面试题真题详解&#xff08;马士兵&#xff09;】https://www.bilibili.com/video/BV13g41157hK?p4&v…

OpenCV的形态学操作

在计算机视觉中&#xff0c;形态学操作是一种基于集合论的图像处理技术&#xff0c;主要用于分析和处理图像的形状特征。OpenCV 提供了 cv2.morphologyEx() 函数&#xff0c;用于执行多种高级形态学操作。 kernel np.ones((15, 15), np.uint8) 1. 开运算&#xff08;Opening&…

【Python爬虫(50)】从0到1:打造分布式爬虫项目全攻略

【Python爬虫】专栏简介&#xff1a;本专栏是 Python 爬虫领域的集大成之作&#xff0c;共 100 章节。从 Python 基础语法、爬虫入门知识讲起&#xff0c;深入探讨反爬虫、多线程、分布式等进阶技术。以大量实例为支撑&#xff0c;覆盖网页、图片、音频等各类数据爬取&#xff…

KylinSP3 | 防火墙和麒麟安全增强设置KySec

一、系统防火墙原理 麒麟操作系统从V10版本开始&#xff0c;默认使用了Firewalld防火墙&#xff0c;Firewalld是能提供动态管理的防火墙&#xff0c;支持网络/防火墙区域&#xff0c;用于定义网络连接或接口的信任级别。支持IPv4和IPv6防火墙设置、以太网桥接和IP集。将运行时…

【NLP 23、预训练语言模型】

人类发明后悔&#xff0c;来证明拥有的珍贵 —— 25.1.15 Bert的优势&#xff1a;① 预训练思想 ② Transformer模型结构 一、传统方法 VS 预训练方式 Pre-train&#xff1a; ① 收集海量无标注文本数据 ② 进行模型预训练&#xff0c;并在任务模型中使用 Fine-tune&#xff1a…

嵌入式硬件基础知识

1.电阻(主要是贴片电阻) 01 基础课程-电阻 1.电阻封装 2.相关参数 1.功率额定值&#xff1a; 电阻能够长期承受的最大功率&#xff0c;功率过大可能导致电阻过热或损坏。封装尺寸越大&#xff0c;散热能力越强&#xff0c;功率额定值通常越高。 2.容差&#xff1a; 电阻…

VMware建立linux虚拟机

本文适用于初学者&#xff0c;帮助初学者学习如何创建虚拟机&#xff0c;了解在创建过程中各个选项的含义。 环境如下&#xff1a; CentOS版本&#xff1a; CentOS 7.9&#xff08;2009&#xff09; 软件&#xff1a; VMware Workstation 17 Pro 17.5.0 build-22583795 1.配…

DeepSeek+Kimi 一键生成100种PPT

一 简介 PPT在工作中经常用到&#xff0c;无论是给老板汇报&#xff0c;还是同事、朋友之间的分享&#xff0c;或是去见投资人:) &#xff0c;都离不开它&#xff0c;然而写PPT经常让人感觉不胜其烦&#xff0c;无论是逻辑的展开、还是页面的布局、字体、配图&#xff0c;都像个…

循环神经网络rnn

1.了解词嵌入层的作用 2.了解循环网络层的作用 1.词嵌入层 将文本进行数值化,词嵌入层首先会根据输入的词的数量构建一个词向量矩阵&#xff0c;例如:我们有 100 个词&#xff0c;每个词希望转换成 128 维度的向量&#xff0c;那么构建的矩阵形状即为:100*128&#xff0c;输入…

雷池WAF动态防护技术实测

作者&#xff1b; Hacker / 0xh4ck3r 介绍 长亭雷池&#xff08;SafeLine&#xff09;是由北京长亭科技有限公司耗时近10年研发并推出的Web应用防火墙&#xff08;WAF&#xff09;&#xff0c;其核心检测能力由智能语义分析算法驱动。雷池旨在为用户提供高质量的Web攻击防护、…

MATLAB应用介绍

MATLAB 数据分析 MATLAB 在数据分析方面的强大功能和优势&#xff0c;涵盖数据处理、分析、可视化、结果分享等多个环节&#xff0c;为工程师和科学家提供了全面的数据分析解决方案。 MATLAB 数据分析功能概述&#xff1a;工程师和科学家利用 MATLAB 整理、清理和分析来自气候学…

玩机日记 14 飞牛fnOS部署qBittorrent、AList、Jellyfin,实现下载、存取、刮削、观看一体的家庭影音中心

目录 观前提示&#xff1a; 1、前置条件 2、安装配置qBittorrent 简单配置 延时启动 配置AList的离线下载 配置qBittorrent不走代理 3、安装配置Jellyfin 建立媒体库目录 安装Jellyfin 配置Jellyfin媒体库 打开硬件解码 启用备用字体 配置Jellyfin的SSL 观前提示&…

基于全志T527+FPGA全国产异步LED显示屏控制卡/屏幕拼接解决方案

T527FPGA方案&#xff1a; 内置8核Cortex-A55&#xff0c;主频最高1.8Ghz&#xff1b;G57 MC1 GPU&#xff0c;2Tops算力NPU&#xff1b;同时内置1RISC-V2DSP核&#xff0c;拥有4K高清解码强大性能&#xff0c;配备多种显示接口与2千兆以太网口&#xff0c;4RS485&#xff08;…

电脑键盘知识

1、键盘四大功能区 1. 功能区 2. 主要信息输入区 3. 编辑区 4. 数字键盘区 笔记本电脑键盘的功能区&#xff0c;使用前需先按Fn键 1.1、功能区 ESC&#xff1a;退出 F1&#xff1a;显示帮助信息 F2&#xff1a;重命名 F4&#xff1a;重复上一步操作 F5&#xff1a;刷新网页 …

代码审计入门学习

简介 HadSky轻论坛程序为个人原创PHP系统&#xff0c;作者为蒲乐天&#xff0c;后端基于puyuetianPHP框架驱动&#xff0c;前端基于 puyuetianUI框架驱动&#xff0c;默认编辑器为puyuetianEditor富文本编辑器&#xff0c;其他非原创框架及驱动JQuery.js 及Font-Awesome字体库…

基于 C++ Qt 的 Fluent Design 组件库 QFluentWidgets

简介 QFluentWidgets 是一个基于 Qt 的 Fluent Designer 组件库&#xff0c;内置超过 150 个开箱即用的 Fluent Designer 组件&#xff0c;支持亮暗主题无缝切换和自定义主题色。 编译示例 以 Qt5 为例&#xff08;Qt6 也支持&#xff09;&#xff0c;将 libQFluentWidgets.d…

架构思维:分布式缓存_提升系统性能的关键手段(上)

文章目录 引言一、缓存的特点二、缓存的关键指标-命中率三、缓存的使用场景四、缓存的种类与应用五、缓存存储&#xff1a;哈希表实现六、分布式缓存与一致性哈希七、优化缓存性能总结 引言 分布式架构 缓存技术作为架构设计中重要的性能优化手段&#xff0c;在现代互联网系统…