用TWaver HTML5定制五彩斑斓的链路

来源:互联网 发布:unlocker强制删除软件 编辑:程序博客网 时间:2024/04/27 13:43
最近有客户提到自定义链路的需求,个人感觉非常有代表意义,现在共享出来给大家参考一下。先来看看需求:
  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.Linktwaver.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.LinkUItwaver.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.Nodetwaver.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.NodeUItwaver.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;}});

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

原创粉丝点击