zoukankan      html  css  js  c++  java
  • 用TWaver HTML5定制五彩斑斓的链路

    最近有客户提到自定义链路的需求,个人感觉非常有代表意义,现在共享出来给大家参考一下。先来看看需求:

    1. 链路要分成两半,用两种颜色填充。
    2. 填充百分比在不同值域时,用不同颜色。
    3. 显示刻度

    4. 有个开关,可以控制链路变短,变短后,链路只画开始和结束部分(相当于原始链路的缩影),中间不画

    5. 如果有多条链路,链路合并后两端分别显示这些链路中的最高填充百分比

      合并前:

      合并后:

    6. 进入子网后,节点上显示和上层节点的连线信息

    进入子网前,节点2和子网内节点4之间有链路:

    进入子网后,节点4上也显示此链路:

    先看看实现的效果,后面我们慢慢解释如何定制链路:


    前5个需求可以通过自定义Link和LinkUI实现,需要注意:

    1. 用Link#getBundleLinks获取所有的捆绑链路
    2. 用LinkUI#drawLinePoints画线

    完整代码如下:

    // 自定义Link构造函数
    demo.ScaleLink = function(id, from, to) {
        // 调用基类构造函数
        demo.ScaleLink.superClass.constructor.call(this, id, from, to);
        // 设置链路宽度为10个像素
        this.setStyle('link.width', 10);
        //this.setStyle('link.color', 'rgba(0, 0, 0, 0)');
        // 设置Link类型为平行
        this.setStyle('link.type', 'parallel');
        // 设置链路捆绑的间距为40
        this.setStyle('link.bundle.offset', 40);
        // 设置刻度颜色
        this.setClient('scaleColor', 'black');
        // 设置刻度宽度
        this.setClient('scaleWidth', 1);
        // 设置刻度个数
        this.setClient('scaleNumbers', 4);
        // 设置是否变短
        this.setClient('shortened', false);
        // 设置变短后的长度
        this.setClient('shortenLength', 100);
        // 设置分割线颜色
        this.setClient('splitterColor', 'black');
        // 设置起始填充百分比
        this.setClient('fromFillPercent', 0);
        // 设置结束填充百分比
        this.setClient('toFillPercent', 0);
    };
    // 设置自定义Link继承twaver.Link
    twaver.Util.ext('demo.ScaleLink', twaver.Link, {
        // 重载获取UI类方法,返回自定义UI类
    	getCanvasUIClass : function () {
    		return demo.ScaleLinkUI;
    	},
    	// 根据百分比获取填充颜色
    	getFillColor: function(percent) {
    		if (percent < 0.25) {
    			return 'green';
    		}
    		if (percent < 0.5) {
    			return 'yellow';
    		}
    		if (percent < 0.75) {
    			return 'magenta';
    		}
    		return 'red';
    	},
    	// 获取起始填充颜色
    	getFromFillColor: function () {
    		return this.getFillColor(this.getFromFillPercent());
    	},
    	// 获取结束填充颜色
    	getToFillColor: function () {
    		return this.getFillColor(this.getToFillPercent());
    	},
    	// 获取起始百分比
    	getFromFillPercent: function () {
    	    // 如果是链路捆绑代理,返回所有捆绑链路中填充百分比最大的值
    		if (this.isBundleAgent()) {
    			var fromAgent = this.getFromAgent(),
    				percentKey, maxPercent = 0, percent;
    			this.getBundleLinks().forEachSiblingLink(function (link) {
    				percentKey = fromAgent === link.getFromAgent() ? 'fromFillPercent' : 'toFillPercent';
    				percent = link.getClient(percentKey);
    				maxPercent = percent > maxPercent ? percent : maxPercent;
    			});
    			return maxPercent;
    		} else {
    			return this.getClient('fromFillPercent');
    		}
    	},
    	// 获取结束百分比
    	getToFillPercent: function () {
    	    // 如果是链路捆绑代理,返回所有捆绑链路中填充百分比最大的值
    		if (this.isBundleAgent()) {
    			var toAgent = this.getToAgent(),
    				percentKey, maxPercent = 0, percent;
    			this.getBundleLinks().forEachSiblingLink(function (link) {
    				percentKey = toAgent === link.getToAgent() ? 'toFillPercent' : 'fromFillPercent';
    				percent = link.getClient(percentKey);
    				maxPercent = percent > maxPercent ? percent : maxPercent;
    			});
    			return maxPercent;
    		} else {
    			return this.getClient('toFillPercent');
    		}
    	},
    	// 重载获取网元名称方法,判断如果是链路捆绑代理,就返回起始和结束代理节点的名称
    	getName: function () {
    	    if (this.getClient('shortened')) {
    	        return null;
    	    } else if (this.isBundleAgent()) {
    			return this.getFromAgent().getName() + '-' + this.getToAgent().getName();
    		} else {
    			return demo.ScaleLink.superClass.getName.call(this);
    		}
    	}
    });
    
    // 自定义LinkUI构造函数
    demo.ScaleLinkUI = function(network, element){
        // 调用基类构造函数
        demo.ScaleLinkUI.superClass.constructor.call(this, network, element);
    };
    // 设置自定义Link继承twaver.canvas.LinkUI
    twaver.Util.ext('demo.ScaleLinkUI', twaver.canvas.LinkUI, {
    	// 获取Link角度
    	getAngle: function () {
    		return getAngle(this.getFromPoint(), this.getToPoint());
    	},
    	// 获取Link中间点
    	getMiddlePoint: function (from, to, percent) {
    		return {
    	    	x: from.x + (to.x - from.x) * percent,
    	    	y: from.y + (to.y - from.y) * percent
    	    };
    	},
    	// 画刻度线
    	drawScaleLine: function (from, to, angle, length, ctx, percent, lineWidth, lineColor) {
    	    var point = this.getMiddlePoint(from, to, percent);
    	    var y = length/2 * Math.sin(angle),
    	    	x = length/2 * Math.cos(angle);
    	    ctx.beginPath();
    	    ctx.lineWidth = lineWidth;
    	    ctx.strokeStyle = lineColor;
    	    ctx.moveTo(point.x + x, point.y + y);
    	    ctx.lineTo(point.x - x, point.y -y);
    	    ctx.stroke();
    	},
    	// 获取是否将链路变短
    	isShorten: function () {
    		var link = this.getElement();
    		return link.getClient('shortened') && this.getLineLength() > link.getClient('shortenLength') * 2;
    	},
    	// 重载画链路函数,用自定义逻辑画链路
    	paintBody: function (ctx) {
            var points = this.getLinkPoints(),
            	link = this.getElement();
            if (!points || points.size() < 2) {
                return;
            }
    
            var lineLength = this.getLineLength(),
            	shortenLength = link.getClient('shortenLength'),
            	percent = shortenLength / lineLength,
    	    	from = points.get(0),
    	    	to = points.get(1),
    	    	angle = this.getAngle() + Math.PI/2;
    	    if (this.isShorten()) {
            	fromPoints = new twaver.List([from, this.getMiddlePoint(from, to, percent)]);
            	toPoints = new twaver.List([this.getMiddlePoint(from, to, 1 - percent), to]);
            	this._paintBody(ctx, fromPoints, angle);
            	this._paintBody(ctx, toPoints, angle);
    
            	// 画文字
    			ctx.textAlign = 'center';
    			ctx.textBaseline = 'middle';
    			ctx.fillStyle = 'black';
    			var textCenter = {x: (fromPoints.get(0).x + fromPoints.get(1).x)/2, y: (fromPoints.get(0).y + fromPoints.get(1).y)/2};
    			ctx.fillText(link.getName(), textCenter.x, textCenter.y);
    
    			textCenter = {x: (toPoints.get(0).x + toPoints.get(1).x)/2, y: (toPoints.get(0).y + toPoints.get(1).y)/2};
    			ctx.fillText(link.getName(), textCenter.x, textCenter.y);
    
    			ctx.fillText(link.getToNode().getName(), fromPoints.get(1).x, fromPoints.get(1).y);
    			ctx.fillText(link.getFromNode().getName(), toPoints.get(0).x, toPoints.get(0).y);
            } else {
            	this._paintBody(ctx, points, angle);
            }
    
    		// 画起始箭头
    		if (link.getClient('arrow.from')) {
    		    twaver.Util.drawArrow(ctx, 12, 9, points, true, 'arrow.standard', true, 'gray', 0, 0, 1, 'black');
            }
    	    // 画结束箭头
            if (link.getClient('arrow.to')) {
    		    twaver.Util.drawArrow(ctx, 12, 9, points, false, 'arrow.standard', true, 'gray', 0, 0, 1, 'black');
            }
    	},
    	_paintBody: function (ctx, points, angle) {
            var link = this.getElement(),
            	width = link.getStyle('link.width'),
            	grow = width,
            	outerColor = this.getOuterColor();
            if (outerColor) {
                var outerWidth = link.getStyle('outer.width');
                grow += outerWidth * 2;
            }
            var selectBorder = !this.getEditAttachment() && link.getStyle('select.style') === 'border' && this.getNetwork().isSelected(link);
            if (selectBorder) {
                var selectWidth = link.getStyle('select.width');
                grow += selectWidth * 2;
            }
            ctx.lineCap = link.getStyle('link.cap');
            ctx.lineJoin = link.getStyle('link.join');
            // 画选中边框
            if (selectBorder) {
                this.drawLinePoints(ctx, points, grow, link.getStyle('select.color'));
            }
            // 画边框
            if (outerColor) {
                this.drawLinePoints(ctx, points, width + outerWidth * 2, outerColor);
            }
            // 画Link
            this.drawLinePoints(ctx, points, width, this.getInnerColor() || link.getStyle('link.color'));
    
    		var fromFillPercent = link.getFromFillPercent(),
    	    	toFillPercent = link.getToFillPercent(),
    	    	fromFillColor = link.getFromFillColor(),
    	    	toFillColor = link.getToFillColor(),
    	    	from = points.get(0),
    	    	to = points.get(1);
    
    	    var x = from.x + (to.x - from.x) / 2 * fromFillPercent,
    	    	y = from.y + (to.y - from.y) / 2 * fromFillPercent;
    	    var middle = {x: x, y: y};
    	    var fromPoints = new twaver.List([from, middle]);
    		// 画起始填充色
    	    this.drawLinePoints(ctx, fromPoints, width, fromFillColor);
    
    	    from = points.get(1);
    	    to = points.get(0);
    	    x = from.x + (to.x - from.x) / 2 * toFillPercent;
    	    y = from.y + (to.y - from.y) / 2 * toFillPercent;
    	    middle = {x: x, y: y};
    	    var toPoints = new twaver.List([from, middle]);
    	    // 画结束填充色
    	    this.drawLinePoints(ctx, toPoints, width, toFillColor);
    
        	from = points.get(0);
        	to = points.get(1);
    	    var scaleWidth = link.getClient('scaleWidth'),
    	    	scaleColor = link.getClient('scaleColor');
    	    // 画刻度
    	    for (var i = 1, n = link.getClient('scaleNumbers') * 2; i < n; i++) {
    	    	this.drawScaleLine(from, to, angle, width/2, ctx, i/n, scaleWidth, scaleColor);
    	    }
    	    // 画分隔线
    	    this.drawScaleLine(from, to, angle, width, ctx, 0.5, 3, link.getClient('splitterColor'));
    	}
    });

    最后一个需求可以通过定制Node和NodeUI达到目的:

    // 自定义Node构造函数
    demo.ScaleNode = function(id) {
        // 调用基类构造函数
    	demo.ScaleNode.superClass.constructor.call(this, id);
    };
    // 设置自定义Node继承twaver.Node
    twaver.Util.ext('demo.ScaleNode', twaver.Node, {
    	getCanvasUIClass: function () {
    		return demo.ScaleNodeUI;
    	}
    });
    
    // 自定义NodeUI构造函数
    demo.ScaleNodeUI = function(network, element){
        // 调用基类构造函数
        demo.ScaleNodeUI.superClass.constructor.call(this, network, element);
    };
    // 设置自定义NodeUI继承twaver.canvas.NodeUI
    twaver.Util.ext('demo.ScaleNodeUI', twaver.canvas.NodeUI, {
        // 重载画网元方法,画上层链路
    	paintBody: function (ctx) {
    		demo.ScaleNodeUI.superClass.paintBody.call(this, ctx);
        	var result = this.getAttachedLinks();
        	if (!result) {
        		return;
        	}
        	for (var position in result) {
        		this.paintLink(ctx, result[position], position);
        	}
    	},
        // 画链路
        paintLink: function (ctx, links, position) {
        	var center = this.getElement().getCenterLocation(),
        		count = links.length,
        		half = count / 2,
        		network = this.getNetwork(),
        		gap = (count - 1) * -10,
        		terminal, link, i, offset, shortenLength, angle, tempCenter, textWidth, textHeight = 20, textCenter;
        	for (i=0; i<count; i++) {
        		link = links[i];
        		offset = link.getStyle('link.bundle.offset');
        		shortenLength = link.getClient('shortenLength');
        		textWidth = ctx.measureText(link.getName()).width;
    	    	if (position === 'left') {
    	    		terminal = {x: center.x - offset - shortenLength, y: center.y + gap};
    	    		tempCenter = {x: center.x - offset, y: center.y + gap};
    	    		textCenter = {x: terminal.x - textWidth/2 - 10, y: terminal.y};
    	    		angle = Math.PI/2;
    	    	} else if (position === 'right') {
    	    		terminal = {x: center.x + offset + shortenLength, y: center.y + gap};
    	    		tempCenter = {x: center.x + offset, y: center.y + gap};
    	    		textCenter = {x: terminal.x + textWidth/2 + 10, y: terminal.y};
    	    		angle = Math.PI/2;
    	    	} else if (position === 'top') {
    	    		terminal = {x: center.x + gap, y: center.y - offset - shortenLength};
    	    		tempCenter = {x: center.x + gap, y: center.y - offset};
    	    		textCenter = {x: terminal.x, y: terminal.y - 10};
    	    		angle = 0;
    	    	} else {
    	    		terminal = {x: center.x + gap, y: center.y + offset + shortenLength};
    	    		tempCenter = {x: center.x + gap, y: center.y + offset};
    	    		textCenter = {x: terminal.x, y: terminal.y + 10};
    	    		angle = 0;
    	    	}
    	    	gap += 20;
    	    	var isFrom = link.getFromNode() === this.getElement(),
    	    		points;
    	    	if (isFrom) {
    	    		points = new twaver.List([tempCenter, terminal]);
    	    	} else {
    	    		points = new twaver.List([terminal, tempCenter]);
    	    	}
    	    	network.getElementUI(link)._paintBody(ctx, points, angle);
    
    			ctx.textAlign = 'center';
    			ctx.textBaseline = 'middle';
    			ctx.fillStyle = 'black';
    			// 另一端节点标签
    			var name = isFrom ? link.getToNode().getName() : link.getFromNode().getName();
    			ctx.fillText(name, textCenter.x, textCenter.y);
    			textCenter = {x: (tempCenter.x + terminal.x)/2, y: (tempCenter.y + terminal.y)/2};
    			// Link标签
    			ctx.fillText(link.getName(), textCenter.x, textCenter.y);
    
    			// 画起始箭头
    			if (link.getClient('arrow.from')) {
    			    twaver.Util.drawArrow(ctx, 12, 9, points, true, 'arrow.standard', true, 'gray', 0, 0, 1, 'black');
                }
    		    // 画结束箭头
                if (link.getClient('arrow.to')) {
    			    twaver.Util.drawArrow(ctx, 12, 9, points, false, 'arrow.standard', true, 'gray', 0, 0, 1, 'black');
                }
    	    }
        },
        // 获取不同方位的上层链路集合
    	getAttachedLinks: function () {
    		var currentSubNetwork = this.getNetwork().getCurrentSubNetwork();
    		if (!currentSubNetwork || !this.getElement().getLinks()) {
    			return null;
    		}
    		var result;
    		this.getElement().getLinks().forEach(function (link) {
    			var fromSubNetwork = twaver.Util.getSubNetwork(link.getFromNode()),
    				toSubNetwork = twaver.Util.getSubNetwork(link.getToNode());
    			if (fromSubNetwork !== toSubNetwork) {
    				if (!result) {
    					result = {};
    				}
    				var fromCenter = link.getFromNode().getCenterLocation(),
    					toCenter = link.getToNode().getCenterLocation(),
    					angle = getAngle(fromCenter, toCenter),
    					isOut = currentSubNetwork === fromSubNetwork,
    					position;
    				if (isOut) {
    					if (fromCenter.x <= toCenter.x) {
    						if (angle >= -Math.PI/4 && angle <= Math.PI/4) {
    							position = 'right';
    						} else if (angle > Math.PI/4) {
    							position = 'bottom';
    						} else {
    							position = 'top';
    						}
    					} else {
    						if (angle >= -Math.PI/4 && angle <= Math.PI/4) {
    							position = 'left';
    						} else if (angle > Math.PI/4) {
    							position = 'top';
    						} else {
    							position = 'bottom';
    						}
    					}
    				} else {
    					if (fromCenter.x <= toCenter.x) {
    						if (angle >= -Math.PI/4 && angle <= Math.PI/4) {
    							position = 'left';
    						} else if (angle > Math.PI/4) {
    							position = 'top';
    						} else {
    							position = 'bottom';
    						}
    					} else {
    						if (angle >= -Math.PI/4 && angle <= Math.PI/4) {
    							position = 'right';
    						} else if (angle > Math.PI/4) {
    							position = 'bottom';
    						} else {
    							position = 'top';
    						}
    					}
    				}
    				if (!result[position]) {
    					result[position] = [];
    				}
    				result[position].push(link);
    			}
    		});
    		return result;
    	}
    });

    本文完整代码见附件:见原文左下方

  • 相关阅读:
    cors 详解
    网站性能优化实战——从12.67s到1.06s的故事
    浏览器渲染引擎
    window.location.reload(false);window.location.reload(true);history.Go(0)区别
    微信小程序image组件中aspectFill和widthfix模式应用详解
    git commit -m 与 git commit -am的区别
    git add详解
    Genymotion 解决虚拟镜像下载速度特别慢的问题
    使用Struts+Hibernate开发学生信息管理系统
    ZipInputStream的用法
  • 原文地址:https://www.cnblogs.com/twaver/p/2595471.html
Copyright © 2011-2022 走看看