HT for Web (Hightopo) 使用心得(2)- 2D 图纸、节点、连线 与基本动画

概括来说,用 HT for Web 做可视化主要分为两部分,也就是 2D 和 3D。这两部分需要单独创建。在它们被创建完成后,我们再把它们集成到一起。

HT for Web 的 2D 部分主要是指 ht.graph.GraphView (简称 GraphView,也就是 2D 图纸)。所谓 2D 图纸其本质是一个 canvas。我们可以在上面进行基本图形的绘制和编辑,进行连线布局,或者渲染动画。GraphView 可以脱离开 3D 单独使用,比如用于创建普通网页,组态软件,组织图,流程图等。

本节我们主要以一个示例说明一下 2D 图纸的基本概念、功能及用法。其主要包括以下几部分:

  • 创建2D 图纸 -(GraphView)
  • 添加节点 -(ht.Node)
  • 坐标系与坐标转换
  • 添加连线 - (ht.Edge)
  • 动画 - ht.Default.startAnim()
  • 创建2D 图纸 -(GraphView)

创建2D 图纸 -(GraphView)

通过 new ht.graph.GraphView() 便可创建一张图纸。创建完图纸后,对于 HT 视图组件,可以通过 GraphView.addToDOM() 方法将其添加到 DOM 中。addToDOM() 方法本身接收一个参数。如果为空,则默认添加到 body 下面。也可以通过传递一个 div 将 2D 图纸固定到页面的某个位置。

对于新创建的图纸,其默认具有缩放、平移、编辑,以及框选等属性。我们可以根据需要对其进行启用或禁用。

与其他视图组件一样,如果不指定 DataModel, GraphView 自身也会创建一个空的 DataModel 容器用来管理所有添加到其里面的各种图元。通过 GraphView.getDataModel() 或 GraphView.dm() 可以获取该容器。对于 GraphView,它支持通过 DataModel.setBackground() 来配置图纸的背景颜色。

/*************** 创建一张 2D 图纸,添加到 body 下,并配置各种属性 ******************/
const gv = new ht.graph.GraphView();
gv.addToDOM();
gv.setZoomable(true); // 可缩放,默认:true
gv.setPannable(true); // 可平移,默认:true
gv.setEditable(true); // 图纸上的 Node 是否可编辑,默认:true
gv.setRectSelectable(true); // 是否允许对 Node 进行框选,默认:true
const dm = gv.getDataModel(); // 获取图纸的 DataModel,简写形式:gv.dm()
dm.setBackground('#DADADA'); // 同 dm.setBackground('rgba(218, 218, 218, 1)');

为什么要通过 DataModel 来设置图纸的背景而不是直接操作 GraphView 本身?

在第一节我们提到过,为了能将我们创建的 2D/3D 数据保存与恢复,可以通过对 DataModel 进行序列化与反序列化来实现。注意,这里的序列化操作针对的是 DataModel 而不是视图组件。因此,像如 GraphView 这种视图组件,其背景颜色这种显示属性需要通过配置到 DataModel 才能保存下来。而像缩放,平移等操作属性,则需要根据项目运行需要单独配置。

添加节点 -(ht.Node)

有了 2D 图纸的 DataModel,我们便可以向其添加节点或者叫图元。这里我们添加了两个机柜图标。

/**************** 分别创建两个 HT 节点并添加到图纸中 ************************/
const server1 = new ht.Node();
server1.setSize(40, 100); // 节点宽高。应当根据图片比例设置,不然会出现拉伸效果
server1.setPosition(100, 100); // 节点位置。左上角为(0,0)坐标
server1.setImage('assets/server.png'); // 节点图片
server1.setName('Server 1'); // 显示名称
dm.add(server1); // 添加到 DataModel中,也就是添加到图纸中
const server2 = new ht.Node();
server2.setSize(40, 100);
server2.setPosition(250, 100);
server2.setImage('assets/server.png');
server2.setName('Server 2');
dm.add(server2); // 注意一定要添加到 DataModel 中

在传统前端(Vue, React, HTML)的开发过程中,要添加一个节点,我们往往需要先手动创建该节点(如添加一个 icon)并添加到 HTML 下面。然后通过数据绑定对该节点进行操作。而在 HT 中,我们只需要将新增的节点添加到 DataModel 下面。HT 的视图组件会监听 DataModel 的变化,自动触发重新渲染操作。我们对节点的所有样式风格配置和操作都可以直接在节点上进行。

节点配置

ht.Node(简称为 Node)是 2D 图纸和 3D 场景呈现节点图元的基础类,它继承自 ht.Data。在 ht.Data 的基础上,其又新增了位置,大小,旋转,缩放,吸附等属性。这些属性都可以通过 set*/get* 来设置和获取。

在 GraphView 中,新增一个 ht.Node 节点,默认其会显示一个电脑图片。我们可以通过 setImage(image) 方法来更改。如在上例的代码中,我们找了一个机柜照片并将它给了新增的节点。

由于机柜图片可能很大,我们可以通过 setSize(width, height) 来对节点大小进行控制。通过配置不同的宽高还可以实现拉伸效果。如果不想要拉伸效果,则可以根据原始图片宽高比来设置节点的大小。

坐标系与坐标转换

添加的节点如果不指定位置,其默认会被放到(x: 0, y: 0)点。通过 Node.setPosition(x, y) 或 Node.p(x, y) 可以控制节点的位置。通过 Node.getPosition() 或 Node.p() 可以获取其坐标。在 GraphView 中,如果不手动修改节点的坐标,其在 GraphView 中的位置便是固定不变的。

由于 GraphView 本身具有缩放、平移等属性,因此其里面的节点显示在浏览器上的位置是不固定的。因此我们可以知道 GraphView 的坐标系与浏览器的坐标系是不一样的。

实际上,GraphView 采用的是一个相对坐标系,其方向与 canvas 一致,即左上角为(0, 0)点。往右为 x 轴的正方向,往下为 y 轴的正方向。通过以下两个方法可以在 GraphView 坐标与浏览器坐标之间进行转换。

GraphView.getLogicalPoint(event): 传入 HTML 事件对象,将浏览器坐标转换为 GraphView 中的逻辑坐标

GraphView.getScreenPoint(point, y): 传入 GraphView 中的坐标,转换为浏览器坐标

添加连线 - (ht.Edge)

在添加了两个机柜图标之后,我们现在用连线将他们连接起来。HT 中的连线是用 ht.Edge 实现的。

ht.Edge 类型(简称 Edge)用于连接起始和目标两个 Node 节点,两个节点间可以有多条 Edge 存在,也允许起始和目标为同一节点。 可通过 new ht.Edge(source, target) 直接在构造函数中传入 source 和 target 节点对象,也可构建 Edge 对象之后再分别设置。

getSource() 和 setScource(node) 获取和设置起始节点

getTarget() 和 setTarget(node) 获取和设置目标节点

isLooped() 判断连线的起始和目标是否为同一节点

/****************************** 创建连线 *************************************/
const edges = [];
// 创建三条连线连接 server1 和 server2
edges.push(createEdge(4, 'green', 6, 'yellow', [20, 10]));
edges.push(createEdge(3, '#fff', 3, '#000', [10, 10])); // 黑白线
edges.push(createEdge(10, 'rgb(51,153,255)', 5, 'rgb(242,83,75)', [5, 10])); // 红蓝线
function createEdge(width, color, dashWidth, dashColor, pattern) {
const edge = new ht.Edge(server1, server2);
edge.s({
'edge.width': width, // 连线宽度
'edge.gap': 30, // 连线与连线的距离
'edge.color': color, // 连线颜色。也可使用 rgb 或16进制颜色
'edge.dash': true, // 是否使用虚线
'edge.dash.width': dashWidth, // 虚线宽度
'edge.dash.color': dashColor, // 虚线颜色
'edge.dash.pattern': pattern, // 虚线与连线的占比。[虚线, 连线]
'edge.offset': 0, // 偏移
});
dm.add(edge); // 注意一定要添加到 DataModel 中
return edge;
}
// 创建第四条连线用于 server1 自连线
const edge = new ht.Edge();
edge.setSource(server1);
edge.setTarget(server1);
edge.s({
'edge.width': 5, // 连线宽度
'edge.gap': 30, // 连线与连线的距离
'edge.color': 'pink', // 连线颜色。也可使用 rgb 或16进制颜色
'edge.dash': true, // 是否使用虚线
'edge.dash.width': 5, // 虚线宽度
'edge.dash.color': 'purple', // 虚线颜色
'edge.dash.pattern': [10, 10], // 虚线与连线的占比。[虚线, 连线]
'edge.offset': 0, // 偏移
});
dm.add(edge); // 注意一定要添加到 DataModel 中
edges.push(edge);

上面的代码创建了4条 Edge,其中前三条分别是连接 server1 和 server2,第四条是 server1 自连接。ht.Edge 的属性主要是由 edge.s() 方法来配置,该方法是 edge.setStyle() 的简写形式。edge.s() 主要用来配置节点内置的属性。HT for Web 引擎会根据属性键值来渲染不同的效果。如果想要设置自定义属性,需要使用 edge.a() 来实现。

与 ht.Node 相同的是,ht.Edge 也是由 ht.Data 扩展而来。但在大部分情况下,其又有自己的特点:

需要设置起始节点和目标节点。二者可以为同一个节点(如上例最后一条 Edge)。

缺少起始或目标节点的 Edge 不会在图纸上显示。

Edge 会跟随节点移动。也就是说当我们拖动起始节点和目标节点的时候,其所相关的 Edge 会跟随移动。

由于 Edge 的位置由两个端点决定,因此 Edge 不支持 getPosition()/setPosition() 方法。同样的,其宽度是在 edge.s() 中配置的,因此 Edge 也不支持 getSize()/setSize() 方法。

除了上面示例中的配置,ht.Edge 还支持自定义连线类型。如果嫌麻烦,也可以使用 HT 内置的十几种连线类型,如 edge.s('edge.type', 'boundary') 就代表连线仅连接到图元矩形边缘。如果要使用内置连线类型,需要引入连线类型插件:

<script src="../../lib/plugin/ht-edgetype.js"></script>

在创建了连线后,如何让它们流动起来呢?这里就用到了动画功能。

动画 - ht.Default.startAnim()

要实现动画功能,不外乎有这样几个关键属性:动画播放时长,播放过程中的属性变化,播放完的回调事件。

HT 支持多种方式来实现动画。这里我们选择一种比较常用的。先来看代码:

/****************************** 连线动画 *************************************/
const animParams = {
// frames: 12, // 动画帧数
// interval: 10, // 动画帧间隔毫秒数
duration: 2000, // 动画播放时长
easing: function(t){ return t * t; }, // 动画缓动函数,默认采用`ht.Default.animEasing`
finishFunc: function(){
ht.Default.startAnim(animParams);
}, // 动画结束后调用的函数。
action: function(v, t){ // action函数必须提供,实现动画过程中的属性变化。v代表通过easing(t)函数运算后的值,t代表当前动画进行的进度,范围:[0~1]
edges.forEach((edge, index) => {
const direction = index%2 == 0 ? 1 : -1;
edge.s('edge.dash.offset', t * 20 * direction);
});
}
};
ht.Default.startAnim(animParams);

这里 HT 用于播放动画的方法是 ht.Default.startAnim(animParams),该方法会返回一个 anim 对象,可调用anim.stop(true) 终止动画。同时 anim 还具有 anim.pause() 和 anim.resume() 方法可用来中断和继续动画功能, 以及 anim.isRunning() 函数判断动画是否正在进行。

该方法所使用的参数也很简单:

  • duration: 动画播放时长。如果想精确地控制动画播放帧数及帧间隔,我们也可以用 frames + interval 的形式来控制动画播放。
  • easing: 缓动函数。使用数学公式来控制动画播放的速度与快慢。可参考
  • finishFunc: 播放完回调。在动画结束后,该方法会被执行。
  • action: 动作控制。节点的哪些属性需要变化都是在这里定义。如控制节点的位置,旋转,大小等。在动画执行过程中的每一帧都会调用一次该方法。其中的两个参数分别是:
  • t: 代表当前动画进行的进度。其范围是从开始执行0到执行结束1。该值的变化随着时间前进,是相对均匀的。如果我们想要动画匀速执行,这里可以用 t 来实现。
  • v: 代表通过 easing(t) 函数运算后的值。其范围在大部分情况下也是从0到1。但是这里的 v 值在变化上由 easing(t) 函数决定,不一定是均匀的。对于有的缓动效果,如 easeOutBack () ,其 v 值在中间阶段就可能大于1。如果想要在动画中使用缓动效果,这里就需要使用 v 参数来控制属性变化。

小结

本节我们主要介绍了 HT for Web 图纸的创建与基本配置。在 GraphView 中,我们可以向其 DataModel 添加 ht.Node 来绘制机柜。通过 Node.setPosition(x, y) 或 Node.p(x, y) 方法可以控制节点的位置,并通过 Node.getPosition() 或 Node.p() 方法来获取其坐标。同时,GraphView 中的坐标系与浏览器坐标系不同,我们可以使用 GraphView.getLogicalPoint(event) 和 GraphView.getScreenPoint(point, y) 方法在两种坐标系之间进行转换。

对于连线,我们使用 ht.Edge 类型表示。创建 ht.Edge 对象时需要传入起始节点和目标节点,同时可以通过 edge.s() 方法配置其属性,如宽度、颜色、虚线等。在创建多条连线后,我们可以使用 ht.Default.startAnim(animParams) 方法来实现连线动画,其中 animParams 是一个对象,包含动画播放时长、缓动函数、动画结束回调函数和动作控制函数等参数。

相关推荐
C语言魔术师几秒前
【小游戏篇】三子棋游戏
前端·算法·游戏
匹马夕阳1 小时前
Vue 3中导航守卫(Navigation Guard)结合Axios实现token认证机制
前端·javascript·vue.js
你熬夜了吗?1 小时前
日历热力图,月度数据可视化图表(日活跃图、格子图)vue组件
前端·vue.js·信息可视化
桂月二二7 小时前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
hunter2062069 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb9 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角9 小时前
CSS 颜色
前端·css
九酒9 小时前
从UI稿到代码优化,看Trae AI 编辑器如何帮助开发者提效
前端·trae
浪浪山小白兔10 小时前
HTML5 新表单属性详解
前端·html·html5
lee57610 小时前
npm run dev 时直接打开Chrome浏览器
前端·chrome·npm