图论中边是重要元素,它连接各个顶点构成拓扑图,有向图中,边具有方向性,在画布中表现为箭头,在实际应用中,边可以代表链路,链路上不只是有方向,还有流量,信号种类等信息,光用箭头表现力就不够了,可增加线条线型,以及流动效果来体现,这里介绍 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的类,详细代码如下
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