在前面《电信网络拓扑图自动布局之总线》一文中,我们重点介绍了自定义 EdgeType 的使用,概括了实现总线效果的设计思路,那么今天话题是基于 HT for Web 的曲线布局(ShapeLayout)。
ShapeLayout 从字面上的意思理解,就是根据曲线路径来布局节点,省去手动布局节点的繁琐操作,还能保证平滑整齐地排布,这是手动调整很难做到的。ShapeLayout 结合前面提到的总线,是最普遍的应用。
http://www.hightopo.com/demo/EdgeType/ShapeLayout-Oval.html
我们先来看看最简单的圆和椭圆是如何实现自动布局的。我们知道在几何学中,圆和椭圆是可以用三角函数老表示,那么我们就可以将圆或者椭圆分成若干份,通过三角函数就可以算出圆或椭圆上的一点,将节点放到计算出来的点的位置,这样就可以达到自动布局的效果。具体的核心代码如下:
var radians = Math.PI * 2 / nodeCount, w = width / 2, h = height / 2, a = Math.max(w, h), b = Math.min(w, h), x, y, rad, node; if (shape === 'circle') a = b = Math.min(a, b); for (var i = 0; i < nodeCount; i++) { rad = radians * i; x = a * Math.cos(rad) + position.x + offset.x; y = b * Math.sin(rad) + position.y + offset.y; node = this._nodes[i]; if (!node) continue; if (!anim) node.setPosition({ x: x, y: y }); else { anim.action = function(pBegin, pEnd, v) { this.setPosition({ x: pBegin.x + (pEnd.x - pBegin.x) * v, y: pBegin.y + (pEnd.y - pBegin.y) * v }); }.bind(node, node.getPosition(), { x: x, y: y }); ht.Default.startAnim(anim); } }
当然,会有人会问,对椭圆按照角度平均分成若干份计算出来的位置并不是等距的,没错,确实不是等距的,这这边就简单处理了,如果要弧度等距的话,那这个就真麻烦了,在这边就不做阐述了,也没办法阐述,因为我也不懂。
http://www.hightopo.com/demo/EdgeType/ShapeLayout.html
如上图的例子,节点沿着某条曲线均匀布局,那么这种不是特殊形状的连线组合是怎么实现自动布局的呢?其实也很简单,在前面总线章节中就有提到,将曲线分割若干小线段,每次计算固定长度,当判断落点在某条线段上的时候,就可以将问题转换为求线段上一点的数学问题,和总线一样,曲线的切割精度需要用户来定义,在不同的应用场景中,需求可能不太一样。
preP = beginP; var nodeIndex = 0, indexLength, node; for (; i < pointsCount;) { p = this._calculationPoints[i]; indexLength = padding + resolution * nodeIndex; if (p.totalLength < indexLength) { preP = p; i++; continue; } node = this._nodes[nodeIndex++]; if (!node) break; dis = indexLength - preP.totalLength; tP = getPointWithLength(dis, preP.point, p.point); p = { x: tP.x + position.x + offset.x - width / 2, y: tP.y + position.y + offset.y - height / 2 }; if (!anim) node.setPosition(p); else { anim.action = function(pBegin, pEnd, v) { this.setPosition({ x: pBegin.x + (pEnd.x - pBegin.x) * v, y: pBegin.y + (pEnd.y - pBegin.y) * v }); }.bind(node, node.getPosition(), p); ht.Default.startAnim(anim); } preP = { point: tP, distance: dis, totalLength: indexLength }; }
以上就是非特殊形状的连线组合的核心代码,这也只是代码片段,可能理解起来还是会比较吃力的,那么下面我将贴上源代码,有兴趣的朋友可以帮忙瞅瞅,有什么不妥的,欢迎指出。
;(function(window, ht) { var distance = function(p1, p2) { var dx = p2.x - p1.x, dy = p2.y - p1.y; return Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)); }; var bezier2 = function(t, p0, p1, p2) { var t1 = 1 - t; return t1*t1*p0 + 2*t*t1*p1 + t*t*p2; }; var bezier3 = function(t, p0, p1, p2, p3 ) { var t1 = 1 - t; return t1*t1*t1*p0 + 3*t1*t1*t*p1 + 3*t1*t*t*p2 + t*t*t*p3; }; var getPointWithLength = function(length, p1, p2) { var dis = distance(p1, p2), temp = length / dis, dx = p2.x - p1.x, dy = p2.y - p1.y; return { x: p1.x + dx * temp, y: p1.y + dy * temp }; }; var ShapeLayout = ht.ShapeLayout = function() {}; ht.Default.def('ht.ShapeLayout', Object, { ms_fire: 1, ms_ac: ['padding', 'offset', 'shape', 'closePath', 'position', 'width', 'height'], calculationSize: function() { if (!this._points) return; var min = { x: Infinity, y: Infinity}, max = { x: -Infinity, y: -Infinity}, p, len = this._points.length; for (var i = 0; i < len; i++) { p = this._points[i]; min.x = Math.min(min.x, p.x); min.y = Math.min(min.y, p.y); max.x = Math.max(max.x, p.x); max.y = Math.max(max.y, p.y); } this._width = max.x - min.x; this._height = max.y - min.y; this._position = { x: min.x + this._width / 2, y: min.y + this._height / 2 }; }, _points: null, getPoints: function() { return this._points; }, setPoints: function(value) { if (value instanceof Array) this._points = value.slice(0); else if (value instanceof ht.List) this._points = value._as.slice(0); else this._points = null; this.__calcuPoints = !!this._points; this.calculationSize(); }, _segments: null, getSegments: function() { return this._segments; }, setSegments: function(value) { if (value instanceof Array) this._segments = value.slice(0); else if (value instanceof ht.List) this._segments = value._as.slice(0); else this._segments = null; this.__calcuPoints = !!this._segments; }, _style: {}, s: function() { return this.setStyle.apply(this, arguments); }, setStyle: function() { var name = arguments[0], value = arguments[1]; if (arguments.length === 1) { if (typeof name === 'object'){ for (var n in name) this._style[n] = name[n]; } else return this._style[name]; } else this._style[name] = value; }, _nodes: null, getNodes: function() { return this._nodes; }, setNodes: function(value) { if (value instanceof Array) this._nodes = value.slice(0); else if (value instanceof ht.List) this._nodes = value._as.slice(0); else this._nodes = null; }, addNode: function(node) { if (!this._nodes) this._nodes = []; this._nodes.push(node); }, _calculationPoints: [], splitPoints: function() { if (!this._points || this._points.length === 0) { alert('Please set points with setPoints method!'); return; } var points = this._points.slice(0), segments; if (!this._segments || this._segments.length === 0) { segments = points.map(function(p, index) { return 2; }); segments[0] = 1; } else { segments = this._segments.slice(0); } this._calculationPoints.length = 0; var beginPoint = points[0], preP = { point: { x: beginPoint.x, y: beginPoint.y }, distance: 0, totalLength: 0 }; this._calculationPoints.push(preP); var length = segments.length, pointIndex = 1, seg, p, tP, dis, p0, p1, p2, p3, j, curveResolution = this.s('curve.resolution') || 50; var calcuPoints = function(currP) { dis = distance(preP.point, currP); p = { point: { x: currP.x, y: currP.y }, distance: dis, totalLength: preP.totalLength + dis }; this._calculationPoints.push(p); preP = p; }.bind(this); for (var i = 1; i < length; i++) { seg = segments[i]; if (seg === 1) { tP = points[pointIndex++]; p = { point: { x: tP.x, y: tP.y }, distance: 0, totalLength: preP.totalLength }; this._calculationPoints.push(p); preP = p; } else if (seg === 2) { calcuPoints(points[pointIndex++]); } else if (seg === 3) { p1 = points[pointIndex++]; p2 = points[pointIndex++]; p0 = preP.point; for (j = 1; j <= curveResolution; j++) { tP = { x: bezier2(j / curveResolution, p0.x, p1.x, p2.x), y: bezier2(j / curveResolution, p0.y, p1.y, p2.y) }; calcuPoints(tP); } } else if (seg === 4) { p1 = points[pointIndex++]; p2 = points[pointIndex++]; p3 = points[pointIndex++]; p0 = preP.point; for (j = 1; j <= curveResolution; j++) { tP = { x: bezier3(j / curveResolution, p0.x, p1.x, p2.x, p3.x), y: bezier3(j / curveResolution, p0.y, p1.y, p2.y, p3.y) }; calcuPoints(tP); } } else if (seg === 5) { tP = this._calculationPoints[0].point; calcuPoints(tP); } } this._totalLength = preP.totalLength; }, layout: function(anim) { if (!this._nodes || this._nodes.length === 0) { alert('Please set nodes width setNode method!'); return; } var nodeCount = this._nodes.length, shape = this._shape, shapeList = ['circle', 'oval'], offset = this._offset || { x: 0, y: 0 }, position = this._position || { x: 0, y: 0 }, width = this._width || 0, height = this._height || 0; if (shape && shapeList.indexOf(shape) >= 0) { var radians = Math.PI * 2 / nodeCount, w = width / 2, h = height / 2, a = Math.max(w, h), b = Math.min(w, h), x, y, rad, node; if (shape === 'circle') a = b = Math.min(a, b); for (var i = 0; i < nodeCount; i++) { rad = radians * i; x = a * Math.cos(rad) + position.x + offset.x; y = b * Math.sin(rad) + position.y + offset.y; node = this._nodes[i]; if (!node) continue; if (!anim) node.setPosition({ x: x, y: y }); else { anim.action = function(pBegin, pEnd, v) { this.setPosition({ x: pBegin.x + (pEnd.x - pBegin.x) * v, y: pBegin.y + (pEnd.y - pBegin.y) * v }); }.bind(node, node.getPosition(), { x: x, y: y }); ht.Default.startAnim(anim); } } return; } if (!this._calculationPoints || this.__calcuPoints) this.splitPoints(); var padding = this._padding || 0, length = this._totalLength - 2 * padding, resolution = length / (nodeCount - (this._closePath ? 0 : 1)), i = 1, p, preP, beginP, dis, pointsCount = this._calculationPoints.length; for (; i < pointsCount; i++) { p = this._calculationPoints[i]; if (p.totalLength < padding) continue; preP = this._calculationPoints[i - 1]; dis = padding - preP.totalLength; beginP = { point: getPointWithLength(dis, preP.point, p.point), distance: p.distance - dis, totalLength: padding }; break; } preP = beginP; var nodeIndex = 0, indexLength, node; for (; i < pointsCount;) { p = this._calculationPoints[i]; indexLength = padding + resolution * nodeIndex; if (p.totalLength < indexLength) { preP = p; i++; continue; } node = this._nodes[nodeIndex++]; if (!node) break; dis = indexLength - preP.totalLength; tP = getPointWithLength(dis, preP.point, p.point); p = { x: tP.x + position.x + offset.x - width / 2, y: tP.y + position.y + offset.y - height / 2 }; if (!anim) node.setPosition(p); else { anim.action = function(pBegin, pEnd, v) { this.setPosition({ x: pBegin.x + (pEnd.x - pBegin.x) * v, y: pBegin.y + (pEnd.y - pBegin.y) * v }); }.bind(node, node.getPosition(), p); ht.Default.startAnim(anim); } preP = { point: tP, distance: dis, totalLength: indexLength }; } } }); }(window, ht));
相关推荐
XX大酒店的网络拓扑图展示了该酒店的网络基础设施布局,主要涵盖了客房区、办公区、餐饮区、物业及仓库区等多个区域的网络连接方案。在这个网络设计中,使用了多种网络设备来确保酒店各功能区域的高效通信和数据传输...
本文档提供的物联网拓扑图揭示了物联网架构的不同层次及其组成部分,帮助我们理解物联网系统如何运作。 首先,物联网的最顶层是应用层,这是用户直接接触的部分,包括物联网家居、物联网物流、物联网超市和物联网...
在本场景中,我们关注的是"C#拓扑图",这是一个基于C#编程语言实现的拓扑图示例,可能被应用于电信网管系统中,以便监控和管理复杂的网络基础设施。 首先,C#是一种由微软开发的面向对象的编程语言,广泛应用于...
SP相关的组成结构,服务器组织+客服端,成功案例的组成实现。
基于 HTML5 快速构建拓扑图是现代 Web 应用中的一个重要技术,尤其在电信、电力、工业控制等领域,用于可视化复杂的网络结构和工作流程。HT 是一种基于 HTML5 的图形化解决方案,它简化了拓扑图的开发过程,让开发者...
这个Repository提供的网络拓扑, 是一套完整可用的面向电信级/企业级用户的HTML5网络拓扑图方案. 图形是基于SVG绘制的, 同时配备了一系列UI套件, 使您更直观的了解Topology是如何工作的 指南 因为文件较多, 所以建议...
首先,网络拓扑图中的各种设备图标包括服务器、ATM交换机、信令网关(如SGW)、缓存服务器、多业务交换机(如MSR)、IAD(Integrated Access Device,综合接入设备)、VOIP网关(如MA5100、MA5200、G8000系列)、...
网络拓扑图是表示计算机网络中设备连接方式和布局的图形表示,它是设计和理解网络结构的关键工具。在这个PPT学习教案中,我们看到不同颜色的抽象图标代表了各种网络原件,这些图标对于制作网络拓扑图非常有用,无论...
在电力和电信行业中,拓扑图是一种常见的方式,用于表示设备、网络和系统的连接关系,有助于理解和分析复杂系统的工作原理。 基于 HTML5 的 Canvas 技术,我们可以开发出专门用于绘制拓扑图的工具。这些工具通常...
在这个压缩包中,包含了多种网络拓扑图,包括短信网关的拓扑图,这对于了解和学习网络通信尤其是电信增值业务的运行机制极具价值。 "2008229946368324.doc"可能是一个文档,详细阐述了特定网络拓扑的设计或案例分析...
好强大的HTML5拓扑图编辑器,不敢私有,0资源分分享给大家,我是做电力SCADA要编辑电力配网图的,但申请看描述该图形组件也能应用到电信、工控等其他需要图形可视化的领域,以前一直用C++做客户端,今天玩了下...
5. **动态更新**:控件支持动态更新,当网络中的设备状态发生变化或新设备加入时,拓扑图会自动更新,保持实时性。 6. **自定义功能**:C#控件允许用户根据实际需求定制图形元素、交互方式以及数据绑定,实现高度的...
标题“综合布线拓扑图(大学)”表明这是一个专门针对大学环境设计的网络布线结构示意图。 综合布线拓扑图通常包括以下几个关键组成部分: 1. 工作区(Work Area):这是用户直接与网络交互的地方,通常包括计算机...
拓扑节点连线及自动布局功能,电力和电信等行业预定义对象,具有动画渲染等特效,因此其应用面很广泛,可作为监控领域的绘图工具和人机界面,可作为一般性的图形化编辑工具,可扩展成工作流和组织图等企业应用。...
《电信设备-UI布局自适应方法、移动设备以及布局生成设备》 在现代通信技术飞速发展的背景下,UI(用户界面)设计对于电信设备的用户体验至关重要。本资料主要探讨了UI布局自适应方法、移动设备的特性以及如何生成...
通信概论