作为企业的发展,通过运营的有效管理,增加收入、降低成本,取得更好的经济效益,是核心所在,在电信企业同样如此。电信企业的利润大体上是由业务收入和成本决定的,而收入和成本又可进一步分别分解表达为不同的形式,其中在每用户平均收入(ARPU)的比对上,可以很清楚地分析出各个时间段的流量对比,并且相应地制定出对策。实现上可以通过 2D 的形式来展示相应的流程,而 Hightopo(以下简称 HT )的 HT for Web 产品上提供了丰富的 2D 组态 可帮助我们快速上手,本系统的 ARPU 分析图也是通过 HT 搭建而成。
通过 HT 的 2D 组态矢量图标绘制了三个水池,并且通过管道的水滴动画,串联起了动画流程。
矢量图适用于很多场合,其特点是放大后图像不会失真,可以适应不同分辨率的屏幕都不会模糊,不仅可以实现完美的跨平台,在大屏展示上的效果就不言而喻了。而 HT 的 2D 组态上有一套完整的矢量图标,例如系统里所实现的水池,是通过 ht.Default.setImage() 注册一个自定义的矢量图标 pool,而这个矢量图标是通过 comps 定义了几个 type 为 shpe 的自定义图形,我们可以通过 points 和 segments 来定义出想要的效果,而这是对应于 ht.Shape 里的属性,points 是绘制矢量图形的点,而 segments 是描述点连接的样式。通过自定义的方式绘制出矢量图标 pool 后,我们还需要对所需用到的波动移动坐标做数据绑定,方便后续来控制水波的水平移动,对于所需控制的变量我们给它起了一个属性名称 offsetX,对应的是所绘制的 comps 组件里各个自定义图形 shape 所涉及的矩形区域 rect 的横坐标 x。
绘制其中一个自定义矢量图形 shape 的具体实现代码如下:
ht.Default.setImage('pool', { dataBindings: [ { attr: 'offsetX', valueType: 'Number', defaultValue: 0 } ], width: 800, height: 200, clip: true, comps: [ { type: 'shape', background: 'rgb(51, 153, 255)', rect: { func: function(data, view) { var x = data.a('offsetX') || 0; return [250 - x, 140, 300, 60]; }, value: [250, 140, 300, 60] }, points: [250, 148, 290, 163, 333, 140, 421, 148, 510, 157, 550, 150, 550, 150, 550, 200, 250, 200], segments: [1, 4, 4, 2, 2] }, ... ] });
绘制成的图形叠加在一起的效果:
水池的水波晃动实现的实质是绘制的各个自定义矢量图形 shape 的横坐标错位平移来达到一种水波的效果,我们可以通过不限定其平移的活动范围来看一下这个原理实现的效果:
很显然对于平移没有边界限定是不行的,通过对于边界限定了一个范围,并在这个范围内定义了一个动画对象 anim,然后通过 HT 的动画函数ht.Default.startAnim()来启动这个动画效果:
// 水池晃动动画 updatePoolDeep(pools) { // 设置每次位置水池晃动波纹偏移的值 let offsetDlt = 2; // 设置水池晃动波纹动画对象 let anim = { frames: Infinity, interval: 50, action: () => { pools.each((p) => { // 设置水池晃动波纹偏移方向 let offsetFlag = p.a('offsetFlag') || 1; // 根据偏移方向取水池晃动波纹偏移值 let offset = (p.a('offsetX') || 0) + offsetDlt * offsetFlag; // 对水池晃动波纹限定边界 if (offset > 50) { offset = 50; offsetFlag = -1; } else if (offset < -50) { offset = -50; offsetFlag = 1; } // 对水池晃动波纹的偏移值和偏移方向进行数据绑定设置值 p.a('offsetX', offset); p.a('offsetFlag', offsetFlag); }); } }; // 开启动画 ht.Default.startAnim(anim); }
为了使水池里面的效果更加地真实一点,我们在矢量图标的上面注册绘制了一张水纹的矢量图片,最后实现的水池晃动效果如下:
在各种行业的业务需求上,2D 视图的流动效果是必不可少的,不仅可以用来表述活动的流程次序,也可以表达出两两互相关联的效果。其实现的方式也多种多样,而本系统是采用自己封装了一个在矩形管道内随机生成水滴的流动效果动画。通过构造一个流动类,类里面定义了基本的一些创建水滴节点、初始化水滴位置以及水滴动画的进行。
对于水滴节点的创建,定义了一个方法:
createNode() { let node; // 判断水池中是否有遗留,存在则设定节点为水池删除的最后一个节点 if (this._pool.length) { node = this._pool.pop(); } else { // 创建新的水滴节点 node = new ht.Node(); // 设置水滴图片 node.setImage(WATER_IMG); } // 取出流动对应的第一条管道 let firstPath = this.option.paths[0]; // 设置随机的偏移量 let offset = Math.floor(Math.random() * OFFSET_MAX * 2) - OFFSET_MAX; // 设置水滴的位置 node.p(this.getStartPositon(firstPath.rect, firstPath.orientation, offset)); // 设置水滴的朝向角度 node.setRotation(this.getRotation(firstPath.orientation)); // 设置水滴的数据绑定 node.a({ // 流动管道 pathIndex: 0, // 动画步进 step: Math.random() * 2 + 3, // 偏移量 offset: offset }); return node; }
流动类里定义了一个设置水滴在规定的矩形 rect 里流动的方法,其参数所表示的意义为:
getStartPositon(rect, orientation, offset) { // 水滴流动的矩形区域 rect 的坐标位置 let { x, y, width, height } = rect; // 判断水滴朝向位置,并相应地进行位置的偏移 switch (orientation) { case TOP: return { x: x + width / 2 + offset, y: y + height }; case RIGHT: return { x: x, y: y + height / 2 + offset }; case BOTTOM: return { x: x + width / 2 + offset, y: y }; case LEFT: return { x: x + width, y: y + height / 2 + offset }; } }
根据水滴的朝向 orientation,还设置了它的旋转方法:
getRotation(orientation) { switch (orientation) { case TOP: return Math.PI; case RIGHT: return - Math.PI / 2; case BOTTOM: return 0; case LEFT: return Math.PI / 2; } }
很多场合下,不同于小弹窗的实现,如果需要一个模糊状态的弹窗窗口,我们可以通过叠加一张背景透明的图纸来达到这种效果。
创建另外一个弹窗图纸的 GraphView 取名为 g2dPop,通过点击事件来渲染加载这张图纸呈现:
其实现的监听代码逻辑如下:
// 开启主图纸事件监听 this.g2d.mi(this.handleInteractive, this); // 开启弹窗图纸事件监听 this.g2dPop.mi(this.popHandleInteractive, this); // 主图纸监听事件 handleInteractive(e) { const {kind, data} = e; // 监听事件为点击图元 if (kind === 'clickData') { // 获取图元标签 let tag = data.getTag(); if (!tag) return; // 判断图元的标签 if (tag.indexOf('poolClick') >= 0) { // 反序列化弹窗图纸 ht.Default.xhrLoad('displays/pop.json', json => { if (!json) return; this.g2dPopDm.deserialize(json); }); } } } // 弹窗监听事件 popHandleInteractive(e) { const {kind, data} = e; // 监听事件为点击图元 if (kind === 'clickData') { // 获取图元标签 let tag = data.getTag(); if (!tag) return; // 判断图元的标签 if (tag === 'back') { // 清除弹窗图纸 this.g2dPopDm.clear(); } } }
2D 组态上实现的矢量图标可以运用在许多的场合,不仅可以在电信企业表达用户数据流量的水池效果,在很多工业上的工艺流程也可以得以体现,例如 PID-进料系统可视化界面,丰富的 2D 组态可以搭建许多好玩的场景,HT 自身丰富的 2D 组态 更是能帮助用户快速上手实现不一样的可视化系统!
2019 我们也更新了数百个工业互联网 2D/3D 可视化案例集,在这里你能发现许多新奇的实例,也能发掘出不一样的工业互联网:https://mp.weixin.qq.com/s/ZbhB6LO2kBRPrRIfHlKGQA
同时,你也可以查看更多案例及效果:https://www.hightopo.com/demos/index.html