基于 HTML5 的 PID-进料系统可视化界面
<h1 class="md-end-block md-heading md-focus"><span class="md-plain md-expand">前言</span></h1><p class="md-end-block md-p"><span class="md-plain"> 随着工业物联网和互联网技术的普及和发展,人工填料的方式已经逐渐被机械设备取代。工业厂商减小误操作、提升设备安全以及追求高效率等制造特点对设备的要求愈加高标准、严要求。同时机械生产以后还需遵从整个项目流程的规范管理,如何实行管理与交接也是一大严峻的挑战。因此,整个生产流程中还应该制定一套关于管理流程的可视化界面。</span></p>
<p class="md-end-block md-p md-focus"><span class="md-tab"> <span class="md-plain">在工业过程控制中,按被控对象的实时数据采集的信息与给定值比较产生的误差的比例、积分和微分进行控制的控制系统,简称 PID 控制系统。PID 控制生产环境具有适应性强,鲁棒性强,使用方便等特点。进料系统则涉及到超高压技术,在流水线系统中广泛应用,能够实现设备半自动化或自动化送料作业,解决传统进料方式计量不准、工作环境污染以及工人劳动强度高等问题,从而实现高效的流水线加工。结合 PID 和自动化部署,可以为电力、机械、冶金、化工、食品、纺织等工业或者民用行业供需。本篇文章通过搭建危险废物进料系统的 2D 场景以及数据界面展示,帮助我们了解如何使用 HT 实现一个可视化的 PID 控制进料系统。</span></span></p>
<p class="md-end-block md-p md-focus"><span class="md-tab"><span class="md-plain"> 项目地址预览: 基于 HTML5 的 PID-进料系统可视化界面 http://www.hightopo.com/demo/PID-feed-system/</span></span></p>
<h1 class="md-end-block md-p md-focus"> </h1>
<h1 class="md-end-block md-heading md-focus"><span class="md-plain md-expand">效果预览</span></h1>
<h2 class="md-end-block md-heading md-focus"><span class="md-plain md-expand"> 整体协作场景</span></h2>
<p><img src="https://img2018.cnblogs.com/blog/1496396/201909/1496396-20190921112003556-1024832611.gif"></p>
<h2 class="md-end-block md-heading md-focus"><span class="md-plain md-expand"> 抓斗操作场景</span></h2>
<p><img src="https://img2018.cnblogs.com/blog/1496396/201909/1496396-20190921112053531-1307183696.gif"></p>
<h2 class="md-end-block md-heading md-focus"><span class="md-plain md-expand"> 进料场景</span></h2>
<p><img src="https://img2018.cnblogs.com/blog/1496396/201909/1496396-20190921114345669-2048047365.gif"></p>
<h1 class="md-end-block md-heading md-focus"><span class="md-plain md-expand">代码构建</span></h1>
<h2 class="md-end-block md-heading md-focus"><span class="md-plain md-expand"> 搭建场景</span></h2>
<p><span class="md-plain md-expand"> 该文主要实现的是 2D 场景,我们需要用到拓扑组件的相关 api 搭建基础场景:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> dataModel = <span style="color: rgba(0, 0, 255, 1)">new</span> ht.DataModel(); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">数据容器,用来存取数据节点Node</span>
<span style="color: rgba(0, 128, 128, 1)">2</span> graphView = <span style="color: rgba(0, 0, 255, 1)">new</span> ht.graph.GraphView(dataModel); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">拓扑组件</span>
<span style="color: rgba(0, 128, 128, 1)">3</span> graphView.addToDOM(); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">将组件添加到body中</span></pre>
</div>
<p class="md-end-block md-p"><span class="md-plain"> 上述代码添加组件到body中所用的是 addToDom 方法,HT<span class="md-link"><span class="md-plain">组件一般会嵌入BorderPane、SplitView和TabView等容器中使用,而最外层的HT组件则需要用户手工将 getView() 返回的底层 div 元素添加到页面的 DOM 元素中,这里需要注意的是,当父容器大小变化时,如果父容器是 BorderPane 和 SplitView 等这些HT预定义的容器组件,则HT的容器会自动递归调用孩子组件 invalidate 函数通知更新。但如果父容器是原生的 html 元素, 则 HT 组件无法获知需要更新,因此最外层的 HT 组件一般需要监听 window 的窗口大小变化事件,调用最外层组件 invalidate 函数进行更新。</span></span></span></p>
<p class="md-end-block md-p md-focus"><span class="md-plain"> 为了最外层组件加载填充满窗口的方便性,HT 的所有组件都有 addToDOM 函数,其实现逻辑如下,其中 iv 是 invalidate 的简写:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> addToDom = <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)">(){
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span> <span style="color: rgba(0, 0, 255, 1)">var</span> self = <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)"> 3</span> view = self.getView(), <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">获取组件的底层div</span>
<span style="color: rgba(0, 128, 128, 1)"> 4</span> style =<span style="color: rgba(0, 0, 0, 1)"> view.style;
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span> document.body.appendChild(view); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">将组件底层div添加到body中</span>
<span style="color: rgba(0, 128, 128, 1)"> 6</span> style.left = '0'; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">默认所有组件的position都设置为absolute绝对定位</span>
<span style="color: rgba(0, 128, 128, 1)"> 7</span> style.right = '0'<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span> style.top = '0'<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span> style.bottom = '0'<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">10</span> window.addEventListener('resize',<span style="color: rgba(0, 0, 255, 1)">function</span>(){ self.iv(); },<span style="color: rgba(0, 0, 255, 1)">false</span>); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">窗口改变大小,调用刷新函数</span>
<span style="color: rgba(0, 128, 128, 1)">11</span> }</pre>
</div>
<p> 将视图默认方法重置:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> graphView.setPannable(<span style="color: rgba(0, 0, 255, 1)">false</span>); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">禁用通过鼠标拖拽进行平移操作</span>
<span style="color: rgba(0, 128, 128, 1)">2</span> graphView.setRectSelectable(<span style="color: rgba(0, 0, 255, 1)">false</span>); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">禁用拓扑上进行框选操作</span>
<span style="color: rgba(0, 128, 128, 1)">3</span> graphView.setMovableFunc(()=>{<span style="color: rgba(0, 0, 255, 1)">false</span>}); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">禁用移动过滤器函数</span></pre>
</div>
<p> 在 2D 编辑器上创建 2D 图形会生成 JSON 文件,引入生成场景需要进行反序列化:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> ht.Default.xhrLoad('displays/industry/PID-进料系统.json',<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)">(text){
</span><span style="color: rgba(0, 128, 128, 1)">2</span> <span style="color: rgba(0, 0, 255, 1)">var</span> json = ht.Default.parse(text); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">解析为JSON对象</span>
<span style="color: rgba(0, 128, 128, 1)">3</span> dataModel.deserialize(json); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">反序列化为场景</span>
<span style="color: rgba(0, 128, 128, 1)">4</span> })</pre>
</div>
<p> 在 HT 中,Data 类型对象构造时内部会自动被赋予一个 id 属性,可通过 data.getId() 和 data.setId( id ) 获取和设置,Data 对象添加到 DataModel 之后不允许修改 id 值,可通过 dataModel.getDataById (id ) 快速查找 Data 对象。但是一般建议 id 属性由 HT 自动分配,用户业务意义的唯一标示可存在 tag 属性上,通过 Data#setTag( tag ) 函数允许任意动态改变 tag 值,通过DataModel#getDataByTag(tag) 可查找到对应的 Data 对象,并支持通过 DataModel#removeDataByTag( tag ) 删除 Data 对象。我们这边通过在 JSON 中设置 Data 对象的 tag 属性,在代码中通过 dataModel.getDataByTag( tag ) 函数来获取该 Data 对象:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 0, 1)">{
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span> "c": "ht.Node"<span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)"> 3</span> "i": 407<span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span> "p"<span style="color: rgba(0, 0, 0, 1)">: {
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span> "displayName": "抓手的结"<span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span> "parent"<span style="color: rgba(0, 0, 0, 1)">: {
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> "__i": 403
<span style="color: rgba(0, 128, 128, 1)"> 8</span> <span style="color: rgba(0, 0, 0, 1)"> },
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span> "tag": "gripKnot"<span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)">10</span> "image": "symbols/symbol factory/垃圾处理/抓手的结.json"<span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)">11</span> "position"<span style="color: rgba(0, 0, 0, 1)">: {
</span><span style="color: rgba(0, 128, 128, 1)">12</span> "x": -569.62125<span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)">13</span> "y": -117.05025
<span style="color: rgba(0, 128, 128, 1)">14</span> <span style="color: rgba(0, 0, 0, 1)"> },
</span><span style="color: rgba(0, 128, 128, 1)">15</span> "width": 50<span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)">16</span> "height": 25
<span style="color: rgba(0, 128, 128, 1)">17</span> <span style="color: rgba(0, 0, 0, 1)"> },
</span><span style="color: rgba(0, 128, 128, 1)">18</span> "s"<span style="color: rgba(0, 0, 0, 1)">: {
</span><span style="color: rgba(0, 128, 128, 1)">19</span> "select.width": 0
<span style="color: rgba(0, 128, 128, 1)">20</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">21</span> },</pre>
</div>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> <span style="color: rgba(0, 0, 255, 1)">var</span> gripRightPaw = dataModel.getDataByTag('gripRightPaw'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">2</span> <span style="color: rgba(0, 0, 255, 1)">var</span> girpLeftPaw = dataModel.getDataByTag('grapLeftPaw'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">3</span> <span style="color: rgba(0, 0, 255, 1)">var</span> gripKnot = dataModel.getDataByTag('gripKnot');</pre>
</div>
<p><span class="md-image-before-src ty-focusable"><img src="https://img2018.cnblogs.com/blog/1496396/201909/1496396-20190907235634362-391764286.png"></span></p>
<h2 class="md-end-block md-heading md-focus"><span class="md-plain md-expand"> 展开动画</span></h2>
<p><span class="md-plain md-expand"> HT 对动画封装了 ht.Default.startAnim 函数,通过设置 duration 获取动画时长, action 函数里为执行的动画属性,以及 finishFunc 动画执行后的回调函数,该案例共置8个动画,包含自驱动以及异步动画。下面举第八个动画(循环水流动)为例来理解 ht 内置动画效果:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">循环水流动</span>
<span style="color: rgba(0, 128, 128, 1)"> 2</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> animation() {
</span><span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 0, 255, 1)">var</span> lineJson =<span style="color: rgba(0, 0, 0, 1)"> {};
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span> <span style="color: rgba(0, 0, 255, 1)">var</span> name = ''<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span> <span style="color: rgba(0, 0, 255, 1)">var</span> speed = 20<span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span> lastTime =<span style="color: rgba(0, 0, 0, 1)"> Date.now();
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">循环获取水流 tag,并设置初始化 shape.dash.offset 为0</span>
<span style="color: rgba(0, 128, 128, 1)"> 8</span> <span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> i = 1; i <= 9; i++<span style="color: rgba(0, 0, 0, 1)"> ) {
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (i != 8<span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 128, 1)">10</span> name = 'line'+<span style="color: rgba(0, 0, 0, 1)">i;
</span><span style="color: rgba(0, 128, 128, 1)">11</span> lineJson = 0<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">12</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">13</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">14</span> <span style="color: rgba(0, 0, 0, 1)"> ht.Default.startAnim({
</span><span style="color: rgba(0, 128, 128, 1)">15</span> duration: 5000<span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)">16</span> action: <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> () {
</span><span style="color: rgba(0, 128, 128, 1)">17</span> <span style="color: rgba(0, 0, 255, 1)">var</span> time =<span style="color: rgba(0, 0, 0, 1)"> Date.now(),
</span><span style="color: rgba(0, 128, 128, 1)">18</span> deltaTime = (time - lastTime) / 1000<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">19</span> <span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> tags <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> lineJson) {
</span><span style="color: rgba(0, 128, 128, 1)">20</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (tags.split('e') % 2<span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 128, 1)">21</span> lineJson += deltaTime *<span style="color: rgba(0, 0, 0, 1)"> speed;
</span><span style="color: rgba(0, 128, 128, 1)">22</span> } <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">23</span> lineJson -= deltaTime *<span style="color: rgba(0, 0, 0, 1)"> speed;
</span><span style="color: rgba(0, 128, 128, 1)">24</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">25</span> <span style="color: rgba(0, 0, 255, 1)">var</span> lines =<span style="color: rgba(0, 0, 0, 1)"> dataModel.getDataByTag(tags);
</span><span style="color: rgba(0, 128, 128, 1)">26</span> lines.setStyle('shape.dash.offset'<span style="color: rgba(0, 0, 0, 1)">,lineJson);
</span><span style="color: rgba(0, 128, 128, 1)">27</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">28</span> lastTime =<span style="color: rgba(0, 0, 0, 1)"> time
</span><span style="color: rgba(0, 128, 128, 1)">29</span> <span style="color: rgba(0, 0, 0, 1)"> },
</span><span style="color: rgba(0, 128, 128, 1)">30</span> finishFunc: <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> () {
</span><span style="color: rgba(0, 128, 128, 1)">31</span> <span style="color: rgba(0, 0, 0, 1)"> animation();
</span><span style="color: rgba(0, 128, 128, 1)">32</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">TODO... 也可以在这里异步调用下一个动画</span>
<span style="color: rgba(0, 128, 128, 1)">33</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">34</span> <span style="color: rgba(0, 0, 0, 1)"> })
</span><span style="color: rgba(0, 128, 128, 1)">35</span> }</pre>
</div>
<p> 该例首先根据已创建的循环水流(已绑定 tag 标签)通过 for 循环以及 dataModel. getDataByTag 动态获取 Data 节点,通过标签名携带的数字判断水流方向,最终使用 Data.setStyle(可以简写为 Data.s ) 设置虚线部分的偏移距离。</p>
<p><span class="md-image-before-src ty-focusable"><img src="https://img2018.cnblogs.com/blog/1496396/201909/1496396-20190908001028355-2085317210.gif"></span></p>
<p> 上面的循环水流为例,如果 lineJson += value (定值) ,当用户放大视图时图元数量减少,会多调用几次 anim 中的 action 函数,流动速度增快,缩小同理。因此采用 value = speed * deltaTime 的解决方式,解决视图在不同缩放 zoom 的情况下播放速度不一致的问题,具体原理如下:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">global</span>
<span style="color: rgba(0, 128, 128, 1)"> 2</span> <span style="color: rgba(0, 0, 255, 1)">var</span> lastTime =<span style="color: rgba(0, 0, 0, 1)"> Date.now();
</span><span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 0, 255, 1)">var</span> distance = 0; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">距离</span>
<span style="color: rgba(0, 128, 128, 1)"> 4</span> <span style="color: rgba(0, 0, 255, 1)">var</span> speed = 20; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">速度</span>
<span style="color: rgba(0, 128, 128, 1)"> 5</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">action</span>
<span style="color: rgba(0, 128, 128, 1)"> 6</span> <span style="color: rgba(0, 0, 0, 1)">ht.Default.startAnim({
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> duration:5000<span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span> action:<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)">(){
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span> <span style="color: rgba(0, 0, 255, 1)">var</span> time =<span style="color: rgba(0, 0, 0, 1)"> Date.now();
</span><span style="color: rgba(0, 128, 128, 1)">10</span> <span style="color: rgba(0, 0, 255, 1)">var</span> deltaTime = (time - lastTime) / 1000<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">11</span> distance += speed *<span style="color: rgba(0, 0, 0, 1)"> deltaTime;
</span><span style="color: rgba(0, 128, 128, 1)">12</span> lastTime =<span style="color: rgba(0, 0, 0, 1)"> time;
</span><span style="color: rgba(0, 128, 128, 1)">13</span> <span style="color: rgba(0, 0, 0, 1)"> },
</span><span style="color: rgba(0, 128, 128, 1)">14</span> finishFunc:<span style="color: rgba(0, 0, 255, 1)">function</span>(){<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">TODO}</span>
<span style="color: rgba(0, 128, 128, 1)">15</span> })</pre>
</div>
<p><span class="md-plain md-expand"> ht 实现动画不仅可以使用 startAnim 来驱动,也可以采用按调度 addScheduleTask 进行实现,代码如下:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> <span style="color: rgba(0, 0, 0, 1)">dataModel.addScheleTask({
</span><span style="color: rgba(0, 128, 128, 1)">2</span> interval, <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">调度间隔</span>
<span style="color: rgba(0, 128, 128, 1)">3</span> beforeAction(){}, <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">调度开始之前的动作</span>
<span style="color: rgba(0, 128, 128, 1)">4</span> action(){}, <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">调度任务</span>
<span style="color: rgba(0, 128, 128, 1)">5</span> afterAction(){} <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">调度结束之后的动作</span>
<span style="color: rgba(0, 128, 128, 1)">6</span> }) </pre>
</div>
<p> 也可以使用 callLater 进行实现,ht 内置函数封装了非常多关于动画有趣且实操性强的 api ,有兴趣可以进入官网 ( <span style="color: rgba(0, 0, 255, 1)"><span style="color: rgba(0, 0, 255, 1)">https://www.hightopo.com</span></span><span class="md-link"><span class="md-plain"> )</span></span>进行了解和学习,也可以线上申请 framework 的试用包。<span class="md-plain md-expand">如果想要了解更多HT封装的动画进行操作,可以参考 https://www.cnblogs.com/xhload3d/p/9222549.html 等其他文章。</span></p>
<h2><span class="md-plain md-expand"> 可操作</span></h2>
<p><span class="md-plain md-expand"> 当然,HT 也汲取了订阅-发布模式的天然优势,通过驱动数据更改视图,更加直观地感受到数据与视图的绑定过程。以下提供2种 HT 提供的可操作界面,第一种是通过创建面板组件, HT 内部提供了包含 formPane 、borderPane、TablePane 等一系列通用面板组件,此处我们以 formPane 为例,首先在 index.html 主页面中引入 ht-form.js ,该文件封装了 formPane 面板的 api ,相关伪代码如下:</span></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 255, 1)">var</span> fp = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ht.widget.FormPane(); //创建面板对象
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span> fp.setWidth(<span style="color: rgba(128, 0, 128, 1)">200</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)"> 3</span> fp.setHeight(<span style="color: rgba(128, 0, 128, 1)">100</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span> fp.setRowHeight(<span style="color: rgba(128, 0, 128, 1)">30</span><span style="color: rgba(0, 0, 0, 1)">); //面板行高
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span> fp.setPadding(<span style="color: rgba(128, 0, 128, 1)">16</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span> fp.getView().className = <span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">main</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">; //节点设置类名后可以直接在 style 中设置属性,说白了 fp.getView() 就是一个普通的 DOM 节点
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 0, 0, 1)">fp.addRow([{ //通过 addRow 方法添加文本以及进度条等内容
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span> id:<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">text</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span> element:<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Current Speed === 20</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)">10</span> align:<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">center'</span>
<span style="color: rgba(0, 128, 128, 1)">11</span> }],[<span style="color: rgba(128, 0, 128, 1)">0.1</span><span style="color: rgba(0, 0, 0, 1)">]);
</span><span style="color: rgba(0, 128, 128, 1)">12</span> <span style="color: rgba(0, 0, 0, 1)">fp.addRow([{
</span><span style="color: rgba(0, 128, 128, 1)">13</span> id:<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">speed</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)">14</span> <span style="color: rgba(0, 0, 0, 1)"> slider:{ //进度条
</span><span style="color: rgba(0, 128, 128, 1)">15</span> min:<span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)">16</span> max:<span style="color: rgba(128, 0, 128, 1)">100</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)">17</span> value:<span style="color: rgba(128, 0, 128, 1)">20</span><span style="color: rgba(0, 0, 0, 1)">, //当前进度值
</span><span style="color: rgba(0, 128, 128, 1)">18</span> step:<span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)">19</span> <span style="color: rgba(0, 0, 0, 1)"> onValueChanged(){ value改变时触发函数
</span><span style="color: rgba(0, 128, 128, 1)">20</span> <span style="color: rgba(0, 0, 255, 1)">var</span> speed = fp.v(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">speed</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">21</span> fp.v(<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">text</span><span style="color: rgba(128, 0, 0, 1)">'</span>,<span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">Current Speed === </span><span style="color: rgba(128, 0, 0, 1)">'</span> +<span style="color: rgba(0, 0, 0, 1)"> speed);
</span><span style="color: rgba(0, 128, 128, 1)">22</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">23</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">24</span> }],[<span style="color: rgba(128, 0, 128, 1)">0.1</span><span style="color: rgba(0, 0, 0, 1)">]);
</span><span style="color: rgba(0, 128, 128, 1)">25</span> document.body.appendChild(fp.getView());</pre>
</div>
<p><img src="https://img2018.cnblogs.com/blog/1496396/201909/1496396-20190910185646497-1210349173.gif"></p>
<p> 此时,我们只要把之前定义的 speed 指向 fp.v('speed') ,就可以简单地实现数据视图绑定:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> animation(fp){
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span> <span style="color: rgba(0, 0, 255, 1)">var</span> lineJson =<span style="color: rgba(0, 0, 0, 1)"> {};
</span><span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 0, 255, 1)">var</span> name = ''<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span> <span style="color: rgba(0, 0, 255, 1)">var</span> lastTime =<span style="color: rgba(0, 0, 0, 1)"> Date.now();
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span> <span style="color: rgba(0, 0, 255, 1)">var</span><span style="color: rgba(0, 0, 0, 1)"> speed;
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span> <span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> i = 1; i <= 9; i++<span style="color: rgba(0, 0, 0, 1)"> ) {
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (i != 8<span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span> name = 'line'+<span style="color: rgba(0, 0, 0, 1)">i;
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span> lineJson = 0<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">10</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">11</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">12</span> <span style="color: rgba(0, 0, 0, 1)"> ht.Default.startAnim({
</span><span style="color: rgba(0, 128, 128, 1)">13</span> duration: 5000<span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)">14</span> action: <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> () {
</span><span style="color: rgba(0, 128, 128, 1)">15</span> speed = fp.v('speed'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">16</span> <span style="color: rgba(0, 0, 255, 1)">var</span> time =<span style="color: rgba(0, 0, 0, 1)"> Date.now(),
</span><span style="color: rgba(0, 128, 128, 1)">17</span> deltaTime = (time - lastTime) / 1000<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">18</span> <span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> tags <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> lineJson) {
</span><span style="color: rgba(0, 128, 128, 1)">19</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (tags.split('e') % 2<span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 128, 1)">20</span> lineJson += deltaTime *<span style="color: rgba(0, 0, 0, 1)"> speed;
</span><span style="color: rgba(0, 128, 128, 1)">21</span> } <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">22</span> lineJson -= deltaTime *<span style="color: rgba(0, 0, 0, 1)"> speed;
</span><span style="color: rgba(0, 128, 128, 1)">23</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">24</span> <span style="color: rgba(0, 0, 255, 1)">var</span> lines =<span style="color: rgba(0, 0, 0, 1)"> dataModel.getDataByTag(tags);
</span><span style="color: rgba(0, 128, 128, 1)">25</span> lines.setStyle('shape.dash.offset'<span style="color: rgba(0, 0, 0, 1)">,lineJson);
</span><span style="color: rgba(0, 128, 128, 1)">26</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">27</span> lastTime =<span style="color: rgba(0, 0, 0, 1)"> time;
</span><span style="color: rgba(0, 128, 128, 1)">28</span> <span style="color: rgba(0, 0, 0, 1)"> },
</span><span style="color: rgba(0, 128, 128, 1)">29</span> finishFunc: <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> () {
</span><span style="color: rgba(0, 128, 128, 1)">30</span> <span style="color: rgba(0, 0, 0, 1)"> animation(fp);
</span><span style="color: rgba(0, 128, 128, 1)">31</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">32</span> <span style="color: rgba(0, 0, 0, 1)"> })
</span><span style="color: rgba(0, 128, 128, 1)">33</span> }</pre>
</div>
<p> </p>
<p> 另一种是通过 HT 的矢量图形库,矢量图形采用点、线或多边形的图形描述方式,解决了 png 、jpg 等格式图片在缩放过程中出现失真现象。创建矢量图形可以通过常规编辑器如 webstorm、webstorm 通过代码编写,也可以通过 HT-2D 编辑器直接创建图形,基本上不需要操作代码就可以简单地创建出图形,有学过 3dmax 或者 CAD 制图的同学对此应该都不陌生。在编辑器的不断完善下,内部已经有许多优秀的图标和组件案例,这边就可以直接引用一些小案例,首先需要创建一张图纸,然后直接拉取一个自制图标,类似 legend 的效果都是绘线画出来的,更改文字部分就可以直接看到效果了。</p>
<p> 关键的还是功能性组件,图标展示显示界面,功能性组件支持事件的触发,首先在控件里面拉取 slider 图标,然后到组件栏拉取 slider 组件,设置控件的最大值、最小值和默认值等一系列参数。</p>
<p><img src="https://img2018.cnblogs.com/blog/1496396/201909/1496396-20190921115942154-223690152.gif"></p>
<p> 可以得知我们即将改变的值有两个,一个是 slider ,一个是文本的值,默认20,我们给这两个 Data 对象绑定唯一标签,分别为 sliderValue 以及 textValue,先通过进度条的当前值改变文本的值:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)">1</span> <span style="color: rgba(0, 0, 255, 1)">var</span> sliderValue = dataModel.getDataByTag('sliderValue'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">2</span> <span style="color: rgba(0, 0, 255, 1)">var</span> textValue = dataModel.getDataByTag('textValue'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">3</span> sliderValue.a('ht.onChange',<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)">(){//value改变触发事件
</span><span style="color: rgba(0, 128, 128, 1)">4</span> textValue.a('textValue',sliderValue.a('ht.value'<span style="color: rgba(0, 0, 0, 1)">));
</span><span style="color: rgba(0, 128, 128, 1)">5</span> })</pre>
</div>
<p> 然后animation拿到进度条的当前值,指向speed:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> animation(data) {
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span> <span style="color: rgba(0, 0, 255, 1)">var</span> lineJson =<span style="color: rgba(0, 0, 0, 1)"> {};
</span><span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 0, 255, 1)">var</span> name = ''<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span> <span style="color: rgba(0, 0, 255, 1)">var</span> lastTime =<span style="color: rgba(0, 0, 0, 1)"> Date.now();
</span><span style="color: rgba(0, 128, 128, 1)"> 5</span> <span style="color: rgba(0, 0, 255, 1)">var</span><span style="color: rgba(0, 0, 0, 1)"> speed;
</span><span style="color: rgba(0, 128, 128, 1)"> 6</span> <span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> i = 1; i <= 9; i++<span style="color: rgba(0, 0, 0, 1)"> ) {
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (i != 8<span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 128, 1)"> 8</span> name = 'line'+<span style="color: rgba(0, 0, 0, 1)">i;
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span> lineJson = 0<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">10</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">11</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">12</span> <span style="color: rgba(0, 0, 0, 1)"> ht.Default.startAnim({
</span><span style="color: rgba(0, 128, 128, 1)">13</span> duration: 5000<span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 128, 1)">14</span> action: <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> () {
</span><span style="color: rgba(0, 128, 128, 1)">15</span> speed = data.a('ht.value'<span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 128, 1)">16</span> <span style="color: rgba(0, 0, 255, 1)">var</span> time =<span style="color: rgba(0, 0, 0, 1)"> Date.now(),
</span><span style="color: rgba(0, 128, 128, 1)">17</span> deltaTime = (time - lastTime) / 1000<span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 128, 1)">18</span> <span style="color: rgba(0, 0, 255, 1)">for</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> tags <span style="color: rgba(0, 0, 255, 1)">in</span><span style="color: rgba(0, 0, 0, 1)"> lineJson) {
</span><span style="color: rgba(0, 128, 128, 1)">19</span> <span style="color: rgba(0, 0, 255, 1)">if</span> (tags.split('e') % 2<span style="color: rgba(0, 0, 0, 1)">) {
</span><span style="color: rgba(0, 128, 128, 1)">20</span> lineJson += deltaTime *<span style="color: rgba(0, 0, 0, 1)"> speed;
</span><span style="color: rgba(0, 128, 128, 1)">21</span> } <span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 128, 1)">22</span> lineJson -= deltaTime *<span style="color: rgba(0, 0, 0, 1)"> speed;
</span><span style="color: rgba(0, 128, 128, 1)">23</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">24</span> <span style="color: rgba(0, 0, 255, 1)">var</span> lines =<span style="color: rgba(0, 0, 0, 1)"> dataModel.getDataByTag(tags);
</span><span style="color: rgba(0, 128, 128, 1)">25</span> lines.setStyle('shape.dash.offset'<span style="color: rgba(0, 0, 0, 1)">,lineJson);
</span><span style="color: rgba(0, 128, 128, 1)">26</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">27</span> lastTime =<span style="color: rgba(0, 0, 0, 1)"> time;
</span><span style="color: rgba(0, 128, 128, 1)">28</span> <span style="color: rgba(0, 0, 0, 1)"> },
</span><span style="color: rgba(0, 128, 128, 1)">29</span> finishFunc: <span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> () {
</span><span style="color: rgba(0, 128, 128, 1)">30</span> <span style="color: rgba(0, 0, 0, 1)"> animation(data);
</span><span style="color: rgba(0, 128, 128, 1)">31</span> <span style="color: rgba(0, 0, 0, 1)"> }
</span><span style="color: rgba(0, 128, 128, 1)">32</span> <span style="color: rgba(0, 0, 0, 1)"> })
</span><span style="color: rgba(0, 128, 128, 1)">33</span> }</pre>
</div>
<p> </p>
<p><img src="https://img2018.cnblogs.com/blog/1496396/201909/1496396-20190921120415369-515510329.gif"></p>
<p> 当然也可以自定义多个 slider 分别控制不同的动画,具体如何实现还是全凭需求而定。</p>
<p> </p>
<p> 不局限于 2D 可视化场景,与 3D 相关生产环境的可视化场景模拟也有许多案例,如下:</p>
<p><strong> 3D水泥工厂工艺流程:</strong><span style="color: rgba(0, 0, 255, 1)"><span style="color: rgba(0, 0, 255, 1)">http://www.hightopo.com/demo/CementFactory/</span></span></p>
<p> <img src="https://img2018.cnblogs.com/blog/1496396/201909/1496396-20190908012816618-1339509351.jpg"></p>
<p><strong> 3D高炉炼铁工业流程:</strong><span style="color: rgba(0, 0, 255, 1)"><span style="color: rgba(0, 0, 255, 1)">http://www.hightopo.com/demo/large-screen-puddling/</span></span></p>
<p><img src="https://img2018.cnblogs.com/blog/1496396/201909/1496396-20190908013221664-201828010.jpg"></p>
<p> </p>
<p> </p>
<p> </p>
<h1 class="md-end-block md-p md-focus"> </h1>
<p class="md-end-block md-p md-focus"> </p><br><br>
来源:https://www.cnblogs.com/htdaydayup/p/11525633.html
頁:
[1]