您现在的位置:中国智能制造网>SCADA频道 >行业资讯

如何设计一款Web SCADA 电力接线图工控组态编辑器

2018年10月09日 10:13:40来源:搜狐号作者:CSDN关键词:SCADA系统

​前语

SVG并非仅仅是一种图像格式, 由于它是一种根据XML的言语,也就意味着它承继了XML的跨渠道性和可扩展性,从而在图形可重用性上迈出了一大步。如SVG能够内嵌于其他的XML文档中,而SVG文档中也能够嵌入其他的XML内容,各个不同的SVG图形能够方便地组合, 构成新的SVG图形。这个 Demo 运用的技能根据 HTML5 的技能适应了只能电网调度、配电网运转监控与配电网运维管控,经过移动终端完成 Web SCADA 账上运维的年代需求。由于传统电力行业 CS 桌面监控体系一直到新一代 Web 和移动终端进化中,HT 是施行本钱最低,开发和运转功率最高的前端图形技能解决方案。SVG 矢量图形咱们都不会陌生了,尤其是在工控电信等范畴,可是这篇文章并不是要制造一个新的制作 SVG 图的修改器,而是一个可制作矢量图形而且对这个图形进行数据绑定的更高阶。

代码实现整体框架

整个界面被分为五个部分,别离为 palette 组件面板,toolbar 工具条,graphView 拓扑组件,propertyPane 特点面板以及 treeView 树组件,这五个部分中的组件需求先创立出来,然后才放到对应的方位上去:

palette = newht.widget.Palette(); //组件面板toolbar = newht.widget.Toolbar(toolbar_config); //工具条g2d = newht.graph.GraphView(dataModel); //拓扑组件 treeView = newht.widget.TreeView(dataModel); //树组件propertyPane = newht.widget.PropertyPane(dataModel); //特点面板propertyView = propertyPane.getPropertyView(); //特点组件rulerFrame = newht.widget.RulerFrame(g2d); //刻度尺

这些布局,只需求结合 splitView 和 borderPane 进行布局即可轻松完成~其间 splitView 为 HT 中的 切割组件,参数1为放置在前面的 view 组件(可为左面的,或许上面的);参数2为放置在后面的 view 组件(可为右边的,或许下面的);参数3为可选值,默以为 h,表明左右切割,若设置为 v 则为上下切割;参数4即为切割的份额。borderPane 跟 splitView 的作用有些类似,可是在这个 Demo 中布局,结合这两种组件,代码看起来会愈加清爽。

borderPane = new ht .widget.BorderPane() ;//边框面板leftSplit = new ht .widget.SplitView(palette, borderPane, 'h', 260) ;//切割组件,h表明左右切割,v表明上下切割rightSplit = new ht .widget.SplitView(propertyPane, treeView, 'v', 0.4) ;mainSplit = new ht .widget.SplitView(leftSplit, rightSplit, 'h', - 260) ; borderPane .setTopView(toolbar) ;//设置边框面板的顶部组件为 toolbarborderPane .setTopHeight( 30) ;borderPane .setCenterView(rulerFrame) ;//设置边框面板的中心组件为 rulerframemainSplit .addToDOM() ;//将 mainSplit 的底层 div 增加进 body 体中dataModel .deserialize(datamodel_config) ;//反序列化 datamodel_config 的内容,将json内容转为拓扑图场景内容g2d .fitContent(true) ;

布局完毕后,就要考虑每一个容器中应该放置哪些内容,我将这些内容别离封装到不同的函数中,经过调用这些函数来进行数据的显现。

Palette 组件面板

左侧的 Palette 组件面板需求向其内部增加 group 作为分组,然后再向组内增加节点。可是咱们运用这个组件的最重要的一个原因是它能够拖拽节点,可是由于咱们拖拽后需求在 graphView 拓扑组件中生成一个新的节点显现在拓扑图上,所以我将拖拽部分的逻辑写在了 graphView 拓扑组件的初始化函数中,这一末节就不做解释。

尽管说最重要的要素是拖拽,可是不行否认,这个组件在分类上也是十分直观

在 Palette 中做了三个分组:电力、食物加工厂以及污水处理。并在这些分组下面填充了许多归于该组类型的节点。我将这些分组的信息存储在 palette_config.js 文件中,由于三组中的信息量太大,这儿只将一小部分的信息展现出来,看看是怎么经过 json 方针来对分组进行数据显现的:

palette_config = { scene: { name: '电力', items: [ { name: '文字', image: '__text__', type: ht.Text }, { name: '箭头', image: 'symbols/arrow.json'}, { name: '地线', image: 'symbols/earthwire.json'} ] }, food: { name: '食物加工厂', items: [ { name: '间歇式流化床处理器', image: 'symbols/food/Batch fluid bed processor.json'}, { name: '啤酒瓶', image: 'symbols/food/Beer bottle.json'}, { name: '台式均质机', image: 'symbols/food/Batch fluid bed processor.json'} ] }, pumps: { name: '污水处理', items: [ { name: '3维泵', image: 'symbols/pumps/3-D Pump.json'}, { name: '18-惠勒货车', image: 'symbols/pumps/18-wheeler truck 1.json'} ] } };

经过遍历这个方针获取内部数据,显现不同的数据信息。当然,在获取方针的信息的时分,咱们需求创立 ht.Group 类的方针,以及分组内部的 ht.Node 类的元素(这些元素都为组的孩子),然后将这些获取来的数据赋值到这两品种型的节点上,而且将这些节点增加到 Palette 的数据容器中:

functioninitPalette(){//初始化组件面板中的内容for( varname inpalette_config){ //从 palette_config.js 文件中获取数据varinfo = palette_config[name]; vargroup = newht.Group(); //组件面板用ht.Group展现分组,ht.Node展现按钮元素group.setName(info.name); group.setExpanded( false); //设置group默许封闭palette.dm().add(group); //将节点增加到 palette 的数据容器中info.items.forEach( function(item){varnode = newht.Node(); //新建 ht.Node 类型节点node.setName(item.name); //设置称号 用于显现在 palette 面板中节点下方阐明文字node.setImage(item.image); //设置节点在 palette 面板中的显现图片//文本类型if(item.type === ht.Text) { //经过 json 方针中设置的 type 信息来获取当时信息为何品种型的节点,不同类型的节点有些特点设置不同node.s({ 'text': 'Text', //文本类型的节点需求设置这个特点显现文本的内容'text.align': 'center', //文本对齐方法'text.vAlign': 'middle', //文本垂直对齐方法'text.font': '32px Arial'//文本字体}); } node.item = item; node.s({ 'image.stretch': item.stretch || 'centerUniform', //设置节点显现图片为填充的方法,这样不同份额的图片也不会由于拉伸而导致变形'draggable': item.draggable === undefined? true: item.draggable, //设置节点是否可被拖拽}); group.addChild(node); //将节点设置为 group 组的孩子palette.dm().add(node); //节点相同也得增加到 palette 的数据容器中进行存储}); } }graphView 拓扑组件

前面说到了 Palette 组件中节点拖拽到 graphView 拓扑图形中,来看看这个部分是怎么完成的。graphView 拓扑组件是 HT 十分重要的一个组件,了解它十分有必要。假如 Palette 中的 Node 的 draggable 特点设置为 true ,那么 Palette 能够主动处理 dragstart ,可是 dragover 和 dragdrop 事情需求咱们处理,咱们知道 IOS 和 Android 设备上并不支持 dragover 和 dragdrop 这类事情,所以 Palette 插件还供给了模仿的拖拽事情 handleDragAndDrop,能够完美兼容 PC 和手持终端。

functioninitGraphView(){if(ht. Default.isTouchable){ //判别是否为触屏可Touch方法交互palette.handleDragAndDrop = function(e, state){//重写此方法能够禁用HTML5原生的Drag和Drop事情并启用模仿的拖拽事情if(ht. Default.containedInView(e, g2d)){ //判别交互事情所处方位是否在View组件之上if(state === 'between'){ e.preventDefault(); //撤销事情的默许动作。} elseif(state === 'end'){ //当state为end时,判别e是否在graphView的范围内,假如是,则创立NodehandleDrop(e); } } }; } else{ g2d.getView().addEventListener( "dragover", function(e){e.dataTransfer.dropEffect = "copy"; e.preventDefault(); }); g2d.getView().addEventListener( "drop", function(e){handleDrop(e); }); } } functionhandleDrop(e){//被拖拽的元素在方针元素上一起鼠标铺开触发的事情e.preventDefault(); varpaletteNode = palette.dm().sm().ld(); //获取 palette 面板上最终选中的节点 if(paletteNode) { varitem = paletteNode.item, image = item.image; data = g2d.getDataAt(e, null, 5); //获取事情下的节点varnode = new(item.type || ht.Node)(); node.setImage(image); //设置节点图片node.setName(item.name); //设置节点称号node.p(g2d.lp(e)); //设置节点的坐标为拓扑中的逻辑坐标 lp函数为将事情坐标转换为拓扑中的逻辑坐标node.s( 'label', ''); //设置节点在 graphView 中底部不显现 setName 中的阐明。由于 label 的优先级大于 name if(data instanceofht.Group){ //假如拖拽到“组类型”的节点上,那么直接设置父亲孩子联系node.setParent(data); //设置节点的父亲data.setExpanded( true); //打开分组} else{ node.setParent(g2d.getCurrentSubGraph()); } g2d.dm().add(node); g2d.sm().ss(node); } }

我在 graphView 拓扑图的场景中央增加了一个 json 场景,经过 dm.deserialize(datamodel_config) 反序列化 json 场景内容导出的一个电信行业的图纸。HT 共同的矢量引擎功用满意电力行业设备品种繁多、设备图元和线路网络需无极缩放、绑定量测数据实时改写等需求;三维出现技能使得电力厂站和变压器等设备 3D 可视化监控成为可能。

treeView 树组件

至于树组件,树组件和 graphView 拓扑组件共用同一个 dataModl 数据容器,正本只需求创立出一个树组件方针,然后将其增加进布局容器中即可显现当时拓扑图形中的一切的数据节点,一般 HT 会将树组件上的节点分为几品种型进行显现,ht.Edge、ht.Group、ht.Node、ht.SubGraph、ht.Shape 等类型进行显现,可是这样做有一个问题,假如创立的节点十分多的话,那么无法分辨出那个节点是哪一个,也就无法快速地定位和修正该节点,会给绘图人员带来很大的困扰,所以我在 treeView 的 label 和 icon 的显现上做了一些处理:

// 初始化树组件functioninitTreeView(){// 重载树组件上的文本显现treeView.getLabel = function(data){if(data instanceofht.Text) { returndata.s( 'text'); } elseif(data instanceofht.Shape) { returndata.getName() || '不规则图形'} returndata.getName() || '节点'}; // 重载树组件上的图标显现varoldGetIconFunc = treeView.getIcon; treeView.getIcon = function(data){if(data instanceofht.Text) { return'symbols/text.json'; } varimg = data.getImage(); returnimg ? img : oldGetIconFunc.apply( this, arguments); } }propertyPane 特点面板

特点面板,即为显现特点的一个容器,不同的类型的节点可能在特点的显现上有所不同,所以我在 properties_config.js 文件中将几个比较常见的类型的特点存储到数组中,首要有几种特点: text_properties 用于显现文本类型的节点的特点、data_properties 一切的 data 节点均显现的特点、node_properties 用于显现 ht.Node 类型的节点的特点、group_properties 用于显现 ht.Group 类型的节点的特点以及 edge_properties 用于显现 ht.Edge 类型的节点的特点。经过将这些特点分类,咱们能够对在 graphView 中选中的不同的节点类型来对特点进行过滤:

functioninitPropertyView(){//初始化特点组件 dataModel.sm().ms( function(e){//监听选中改变事情propertyView.setProperties( null); vardata = dataModel.sm().ld(); //针对不同类型的节点设置不同的特点内容if(data instanceofht.Text) { //文本类型propertyView.addProperties(text_properties); return; } if(data instanceofht.Data){ // data 类型,一切的节点都根据这个类型propertyView.addProperties(data_properties); } if(data instanceofht.Node){ // node 类型propertyView.addProperties(node_properties); } if(data instanceofht.Group){ //组类型propertyView.addProperties(group_properties); } if(data instanceofht.Edge){ //连线类型propertyView.addProperties(edge_properties); } }); }

数据绑定在特点栏中也有表现,拿 data_properties 中的“标签”和“可修改”作为演示:

{ name: 'name', //设置了 name 特点,假如没有设置 accessType 则默许经过 get/setName 来获取和设置 name 值displayName: '称号', //用于存取特点名的显现文本值,若为空则显现name特点值editable: true//设置该特点是否可修改 }, { name: '2d.editable', //结合 accessType,则经过 node.s('2d.editable') 获取和设置该特点accessType: 'style', //操作存取特点类型displayName: '可修改', //用于存取特点名的显现文本值,若为空则显现name特点值valueType: 'boolean', //布尔类型,显现为勾选框editable: true//设置该特点是否可修改 }

这两个特点比较有代表性,一个是直接经过 get/set 来设置 name 特点值,一个是经过结合特点的类型来操控 name 的特点值。只要在特点栏中操作“称号”和“可修改”两个特点,就能够直接在拓扑图中看到对应的节点的显现状况,这就是数据绑定。当然,还能够对矢量图形进行部分的数据绑定,可是不是本文的要点,有爱好的能够参阅我的这篇文章 WebGL 3D 电信机架实战之数据绑定。

toolbar 工具栏

差点忘掉说这个部分了,toolbar 上总共有 8 种功用,别离是选中修改、连线、直角连线、不规则图形、刻度尺显现、场景扩大、场景缩小以及场景内容导出 json。这 8 种功用都是存储在 toolbar_config.js 文件中的,经过制作 toolbar 中的元素给每一个元素都增加上了对应的点击触发的内容,首要讲讲 CreateEdgeInteractor.js 创立连线的内容。

咱们经过 ht.Default.def 自定义了 CreateEdgeInteractor 类,然后经过 graphView.setInteractors([ new CreateEdgeInteractor(graphView, ‘points’)]) 这种方法来增加 graphView 拓扑图中的交互器,能够完成创立连线的交互功用。

在 CreateEdgeInteractor 类中经过监听 touchend 甩手后事情向 graphView 拓扑图中增加一个 edge 连线,能够经过在 CreateEdgeInteractor 函数中传参来制作不同的连线类型,比方 “ortho” 则为折线类型:

varCreateEdgeInteractor = function(graphView, type){CreateEdgeInteractor.superClass.constructor.call( this, graphView); this._type = type; }; ht.Default.def(CreateEdgeInteractor, DNDInteractor, { //自定义类,承继 DNDInteractor,此交互器有一些根本的交互功用handleWindowTouchEnd: function(e){this.redraw(); varisPoints = false; if( this._target){ varedge = newht.Edge( this._source, this._target); //创立一条连线,传入起始点和结尾edge.s({ 'edge.type': this._type //设置连线类型 为传入的参数 type 类型 参阅 HT for Web 连线类型}); isPoints = this._type === 'points'; //假如没有设置则默以为 points 连线方法if(isPoints){ edge.s({ 'edge.points': [{ //设置连线的点x: ( this._source.p().x + this._target.p().x)/ 2, y: ( this._source.p().y + this._target.p().y)/ 2}] }); } edge.setParent( this._graphView.getCurrentSubGraph()); //设置连线的父亲节点为当时子网this._graphView.getDataModel().add(edge); //将连线增加到拓扑图的数据容器中this._graphView.getSelectionModel().setSelection(edge); //设置选中该节点 } this._graphView.removeTopPainter( this); //删去顶层Painterif(isPoints){ resetDefault(); //重置toolbar导航栏的状况} } });总结

一开始想说要做这个修改器还有点怕怕的,就是感觉任务重,可是不上不行,所以总是在拖,可是后来全体剖析下来,发现其实一步一步来就好,不要把过程想得太复杂,什么事情都是从小堆到大的,曾经咱们用 svg 制作的图形都能够在这上面制作,当然,假如有需求拓宽也彻底 ok,究竟他人写的修改器纷歧定能够彻底满意你的要求。这个修改器虽然在画图上面跟别家无异,可是最重要的是它能够制作出矢量图形,结合 HT 的数据绑定和动画,咱们就能够对这些矢量图形中的每一个部分进行操作,比方灯的闪烁啊,比方人眨眼睛等等操作。

  • 凡本网注明"来源:中国智能制造网的所有作品,版权均属于中国智能制造网,转载请必须注明中国智能制造网,https://www.gkzhan.com。违反者本网将追究相关法律责任。
  • 本网转载并注明自其它来源的作品,目的在于传递更多信息,并不代表本网赞同其观点或证实其内容的真实性,不承担此类作品侵权行为的直接责任及连带责任。其他媒体、网站或个人从本网转载时,必须保留本网注明的作品来源,并自负版权等法律责任。
  • 如涉及作品内容、版权等问题,请在作品发表之日起一周内与本网联系,否则视为放弃相关权利。

热门频道