图论中边是重要元素,它连接各个顶点构成拓扑图,有向图中,边具有方向性,在画布中表现为箭头,在实际应用中,边可以代表链路,链路上不只是有方向,还有流量,信号种类等信息,光用箭头表现力就不够了,可增加线条线型,以及流动效果来体现,这里介绍 Qunee 1.6 中线条流动效果的实现
虚线流动效果
虚线流动效果在 连线示例中有演示,使用虚线偏移量样式,不断增大,实现线条的流动
虚线流动代码
var offset = 0; var index = 0; var timer = setInterval (function (){ offset += -1; // edge2.setStyle (Q.Styles.EDGE_LINE_DASH_OFFSET, offset); edge1.setStyle (Q.Styles.EDGE_LINE_DASH_OFFSET, offset); index++; index = index%20; edge2.setStyle (Q.Styles.ARROW_TO_OFFSET, -0.3 -0.02 * (20 - index)); edge1.setStyle (Q.Styles.ARROW_FROM_OFFSET, {x: 0.3 + 0.02 * (20 - index), y: -10}); }, 150);
运行效果
定制流动效果
对于更高级的流动需求,需要定制来实现,原理是在线条上挂载一个小图标,让这个图标沿线移动,从而形成动画效果
实现代码
下面的代码实现在线条上移动小图标
var line = graph.createShapeNode ("Line"); line.moveTo (-100, 0); line.lineTo (200, 0); line.curveTo (300, 0, 300, 100, 200, 100); line.lineTo (0, 100); line.closePath (); line.setStyle (Q.Styles.SHAPE_STROKE_STYLE, "#2898E0"); line.setStyle (Q.Styles.SHAPE_LINE_DASH, [8, 5, 0.1, 6]); line.setStyle (Q.Styles.SHAPE_STROKE, 3); line.setStyle (Q.Styles.LINE_CAP, "round"); line.setStyle (Q.Styles.SHAPE_OUTLINE_STYLE, "#fcfb9b"); var ui = new Q.ImageUI ("images/flow.png"); ui.position = {x: 0, y: 0}; ui.size = {width: 20}; ui.renderColor = "#F00"; line.addUI (ui); setTimeout (function A (){ var x = ui.position.x + 20; ui.position = {x: x % (ui.parent.length || 1), y: 0}; line.invalidate (); setTimeout (A, 300); }, 100)
运行效果
进一步封装
上面的实现太随意,实际使用不太方便,可以进一步封装成专门用于流动支持的类,这样可以通过一个定时器实现所有的流动支持,我们创建一个 FlowingSupport 的类,详细代码如下
FlowingSupport 类代码
function FlowingSupport (graph) { this.flowMap = {}; this.graph = graph; } FlowingSupport.prototype = { flowMap: null, length: 0, gap: 40, graph: null, addFlowing: function (edgeOrLine, count, byPercent) { var flowList = this.flowMap[edgeOrLine.id]; if(!flowList){ flowList = this.flowMap[edgeOrLine.id] = []; this.length++; } count = count || 1; while(--count >= 0){ var ui = new Q.ImageUI ("network/images/flow.png"); ui.position = {x: 0, y: 0}; ui.size = {width: 20}; ui.renderColor = "#F00"; flowList.push (ui); flowList.byPercent = byPercent; edgeOrLine.addUI (ui); } }, removeFlowing: function (id){ var flowList = this.flowMap[id]; if(!flowList){ return; } var edgeOrLine = this.graph.getElement (id); if(edgeOrLine){ flowList.forEach (function (ui){ edgeOrLine.removeUI (ui); }) } this._doRemove (id); }, _doRemove: function (id){ delete this.flowMap[id]; this.length--; }, timer: null, perStep: 10, stop: function (){ clearTimeout (this.timer); }, start: function (){ if(this.timer){ clearTimeout (this.timer); } var offset = 0; var scope = this; scope.timer = setTimeout (function A () { if (!scope.length) { scope.timer = setTimeout (A, 2000); offset = 0; return; } offset += 1; for(var id in scope.flowMap){ var ui = scope.graph.getUI (id); if(!ui){ scope._doRemove (id); continue; } var lineLength = ui.length; if(!lineLength){ continue; } var flowList = scope.flowMap[id]; if(flowList.byPercent){ //按百分比,0 - 1 跑完整条线,线长度不同,速度也不同,跑完一圈的时间相同 var x = offset * 2; var gap = 15; scope.flowMap[id].forEach (function (ui){ ui.position = {x: (x % 100) / 100, y: 0}; x += gap; }); }else{ //按固定距离移动,速度相同,线条越长跑完一圈的时间越长 var x = offset * scope.perStep; scope.flowMap[id].forEach (function (ui){ ui.position = {x: x % lineLength, y: 0}; x += scope.gap; }); } scope.graph.invalidateUI (ui); //dashed line var data = ui.data; if(data instanceof Q.Edge){ if(data.getStyle (Q.Styles.EDGE_LINE_DASH)){ data.setStyle (Q.Styles.EDGE_LINE_DASH_OFFSET, -offset); } }else if(data instanceof Q.ShapeNode){ if(data.getStyle (Q.Styles.SHAPE_LINE_DASH)) { data.setStyle (Q.Styles.SHAPE_LINE_DASH_OFFSET, -offset); } } } scope.timer = setTimeout (A, 200); }, 200); } }
使用如下
这里的第二个参数为图标数量,第三个参数为是否按百分比流动,如果按百分比流动,每次移动的距离为线条长度的百分之二,这意味着不同长度的线条流动一圈,花费的时间相同
var flowingSupport = new FlowingSupport (graph); flowingSupport.addFlowing (edge, 3); flowingSupport.addFlowing (edge2, 1); flowingSupport.addFlowing (line, 1, true); flowingSupport.addFlowing (line2, 2, true); graph.callLater (function (){ flowingSupport.start (); })
运行效果
在线演示
http://demo.qunee.com/#Flowing%20Demo