TWaver - 专注UI技术

http://twaver.servasoft.com/
posts - 171, comments - 191, trackbacks - 0, articles - 2
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

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

Posted on 2012-07-17 11:20 TWaver 阅读(1551) 评论(0)  编辑  收藏
最近有客户提到自定义链路的需求,个人感觉非常有代表意义,现在共享出来给大家参考一下。先来看看需求:
  1. 链路要分成两半,用两种颜色填充。
  2. 填充百分比在不同值域时,用不同颜色。
  3. 显示刻度
  4. 有个开关,可以控制链路变短,变短后,链路只画开始和结束部分(相当于原始链路的缩影),中间不画
  5. 如果有多条链路,链路合并后两端分别显示这些链路中的最高填充百分比
  6. 合并前:

    合并后:


  7. 进入子网后,节点上显示和上层节点的连线信息
  8. 进入子网前,节点2和子网内节点4之间有链路:

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

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

前5个需求可以通过自定义Link和LinkUI实现,需要注意:
  1. 用Link#getBundleLinks获取所有的捆绑链路
  2. 用LinkUI#drawLinePoints画线
完整代码如下:

  1 // 自定义Link构造函数
  2 demo.ScaleLink = function(id, from, to) {
  3     // 调用基类构造函数
  4     demo.ScaleLink.superClass.constructor.call(this, id, from, to);
  5     // 设置链路宽度为10个像素
  6     this.setStyle('link.width', 10);
  7     //this.setStyle('link.color', 'rgba(0, 0, 0, 0)');
  8     // 设置Link类型为平行
  9     this.setStyle('link.type', 'parallel');
 10     // 设置链路捆绑的间距为40
 11     this.setStyle('link.bundle.offset', 40);
 12     // 设置刻度颜色
 13     this.setClient('scaleColor', 'black');
 14     // 设置刻度宽度
 15     this.setClient('scaleWidth', 1);
 16     // 设置刻度个数
 17     this.setClient('scaleNumbers', 4);
 18     // 设置是否变短
 19     this.setClient('shortened', false);
 20     // 设置变短后的长度
 21     this.setClient('shortenLength', 100);
 22     // 设置分割线颜色
 23     this.setClient('splitterColor', 'black');
 24     // 设置起始填充百分比
 25     this.setClient('fromFillPercent', 0);
 26     // 设置结束填充百分比
 27     this.setClient('toFillPercent', 0);
 28 };
 29 // 设置自定义Link继承twaver.Link
 30 twaver.Util.ext('demo.ScaleLink', twaver.Link, {
 31     // 重载获取UI类方法,返回自定义UI类
 32     getCanvasUIClass : function () {
 33         return demo.ScaleLinkUI;
 34     },
 35     // 根据百分比获取填充颜色
 36     getFillColor: function(percent) {
 37         if (percent < 0.25) {
 38             return 'green';
 39         }
 40         if (percent < 0.5) {
 41             return 'yellow';
 42         }
 43         if (percent < 0.75) {
 44             return 'magenta';
 45         }
 46         return 'red';
 47     },
 48     // 获取起始填充颜色
 49     getFromFillColor: function () {
 50         return this.getFillColor(this.getFromFillPercent());
 51     },
 52     // 获取结束填充颜色
 53     getToFillColor: function () {
 54         return this.getFillColor(this.getToFillPercent());
 55     },
 56     // 获取起始百分比
 57     getFromFillPercent: function () {
 58         // 如果是链路捆绑代理,返回所有捆绑链路中填充百分比最大的值
 59         if (this.isBundleAgent()) {
 60             var fromAgent = this.getFromAgent(),
 61                 percentKey, maxPercent = 0, percent;
 62             this.getBundleLinks().forEachSiblingLink(function (link) {
 63                 percentKey = fromAgent === link.getFromAgent() ? 'fromFillPercent' : 'toFillPercent';
 64                 percent = link.getClient(percentKey);
 65                 maxPercent = percent > maxPercent ? percent : maxPercent;
 66             });
 67             return maxPercent;
 68         } else {
 69             return this.getClient('fromFillPercent');
 70         }
 71     },
 72     // 获取结束百分比
 73     getToFillPercent: function () {
 74         // 如果是链路捆绑代理,返回所有捆绑链路中填充百分比最大的值
 75         if (this.isBundleAgent()) {
 76             var toAgent = this.getToAgent(),
 77                 percentKey, maxPercent = 0, percent;
 78             this.getBundleLinks().forEachSiblingLink(function (link) {
 79                 percentKey = toAgent === link.getToAgent() ? 'toFillPercent' : 'fromFillPercent';
 80                 percent = link.getClient(percentKey);
 81                 maxPercent = percent > maxPercent ? percent : maxPercent;
 82             });
 83             return maxPercent;
 84         } else {
 85             return this.getClient('toFillPercent');
 86         }
 87     },
 88     // 重载获取网元名称方法,判断如果是链路捆绑代理,就返回起始和结束代理节点的名称
 89     getName: function () {
 90         if (this.getClient('shortened')) {
 91             return null;
 92         } else if (this.isBundleAgent()) {
 93             return this.getFromAgent().getName() + '-' + this.getToAgent().getName();
 94         } else {
 95             return demo.ScaleLink.superClass.getName.call(this);
 96         }
 97     }
 98 });
 99 
100 // 自定义LinkUI构造函数
101 demo.ScaleLinkUI = function(network, element){
102     // 调用基类构造函数
103     demo.ScaleLinkUI.superClass.constructor.call(this, network, element);
104 };
105 // 设置自定义Link继承twaver.canvas.LinkUI
106 twaver.Util.ext('demo.ScaleLinkUI', twaver.canvas.LinkUI, {
107     // 获取Link角度
108     getAngle: function () {
109         return getAngle(this.getFromPoint(), this.getToPoint());
110     },
111     // 获取Link中间点
112     getMiddlePoint: function (from, to, percent) {
113         return {
114             x: from.x + (to.x - from.x) * percent,
115             y: from.y + (to.y - from.y) * percent
116         };
117     },
118     // 画刻度线
119     drawScaleLine: function (from, to, angle, length, ctx, percent, lineWidth, lineColor) {
120         var point = this.getMiddlePoint(from, to, percent);
121         var y = length/2 * Math.sin(angle),
122             x = length/2 * Math.cos(angle);
123         ctx.beginPath();
124         ctx.lineWidth = lineWidth;
125         ctx.strokeStyle = lineColor;
126         ctx.moveTo(point.x + x, point.y + y);
127         ctx.lineTo(point.x - x, point.y -y);
128         ctx.stroke();
129     },
130     // 获取是否将链路变短
131     isShorten: function () {
132         var link = this.getElement();
133         return link.getClient('shortened') && this.getLineLength() > link.getClient('shortenLength') * 2;
134     },
135     // 重载画链路函数,用自定义逻辑画链路
136     paintBody: function (ctx) {
137         var points = this.getLinkPoints(),
138             link = this.getElement();
139         if (!points || points.size() < 2) {
140             return;
141         }
142 
143         var lineLength = this.getLineLength(),
144             shortenLength = link.getClient('shortenLength'),
145             percent = shortenLength / lineLength,
146             from = points.get(0),
147             to = points.get(1),
148             angle = this.getAngle() + Math.PI/2;
149         if (this.isShorten()) {
150             fromPoints = new twaver.List([from, this.getMiddlePoint(from, to, percent)]);
151             toPoints = new twaver.List([this.getMiddlePoint(from, to, 1 - percent), to]);
152             this._paintBody(ctx, fromPoints, angle);
153             this._paintBody(ctx, toPoints, angle);
154 
155             // 画文字
156             ctx.textAlign = 'center';
157             ctx.textBaseline = 'middle';
158             ctx.fillStyle = 'black';
159             var textCenter = {x: (fromPoints.get(0).x + fromPoints.get(1).x)/2, y: (fromPoints.get(0).y + fromPoints.get(1).y)/2};
160             ctx.fillText(link.getName(), textCenter.x, textCenter.y);
161 
162             textCenter = {x: (toPoints.get(0).x + toPoints.get(1).x)/2, y: (toPoints.get(0).y + toPoints.get(1).y)/2};
163             ctx.fillText(link.getName(), textCenter.x, textCenter.y);
164 
165             ctx.fillText(link.getToNode().getName(), fromPoints.get(1).x, fromPoints.get(1).y);
166             ctx.fillText(link.getFromNode().getName(), toPoints.get(0).x, toPoints.get(0).y);
167         } else {
168             this._paintBody(ctx, points, angle);
169         }
170 
171         // 画起始箭头
172         if (link.getClient('arrow.from')) {
173             twaver.Util.drawArrow(ctx, 12, 9, points, true, 'arrow.standard', true, 'gray', 0, 0, 1, 'black');
174         }
175         // 画结束箭头
176         if (link.getClient('arrow.to')) {
177             twaver.Util.drawArrow(ctx, 12, 9, points, false, 'arrow.standard', true, 'gray', 0, 0, 1, 'black');
178         }
179     },
180     _paintBody: function (ctx, points, angle) {
181         var link = this.getElement(),
182             width = link.getStyle('link.width'),
183             grow = width,
184             outerColor = this.getOuterColor();
185         if (outerColor) {
186             var outerWidth = link.getStyle('outer.width');
187             grow += outerWidth * 2;
188         }
189         var selectBorder = !this.getEditAttachment() && link.getStyle('select.style') === 'border' && this.getNetwork().isSelected(link);
190         if (selectBorder) {
191             var selectWidth = link.getStyle('select.width');
192             grow += selectWidth * 2;
193         }
194         ctx.lineCap = link.getStyle('link.cap');
195         ctx.lineJoin = link.getStyle('link.join');
196         // 画选中边框
197         if (selectBorder) {
198             this.drawLinePoints(ctx, points, grow, link.getStyle('select.color'));
199         }
200         // 画边框
201         if (outerColor) {
202             this.drawLinePoints(ctx, points, width + outerWidth * 2, outerColor);
203         }
204         // 画Link
205         this.drawLinePoints(ctx, points, width, this.getInnerColor() || link.getStyle('link.color'));
206 
207         var fromFillPercent = link.getFromFillPercent(),
208             toFillPercent = link.getToFillPercent(),
209             fromFillColor = link.getFromFillColor(),
210             toFillColor = link.getToFillColor(),
211             from = points.get(0),
212             to = points.get(1);
213 
214         var x = from.x + (to.x - from.x) / 2 * fromFillPercent,
215             y = from.y + (to.y - from.y) / 2 * fromFillPercent;
216         var middle = {x: x, y: y};
217         var fromPoints = new twaver.List([from, middle]);
218         // 画起始填充色
219         this.drawLinePoints(ctx, fromPoints, width, fromFillColor);
220 
221         from = points.get(1);
222         to = points.get(0);
223         x = from.x + (to.x - from.x) / 2 * toFillPercent;
224         y = from.y + (to.y - from.y) / 2 * toFillPercent;
225         middle = {x: x, y: y};
226         var toPoints = new twaver.List([from, middle]);
227         // 画结束填充色
228         this.drawLinePoints(ctx, toPoints, width, toFillColor);
229 
230         from = points.get(0);
231         to = points.get(1);
232         var scaleWidth = link.getClient('scaleWidth'),
233             scaleColor = link.getClient('scaleColor');
234         // 画刻度
235         for (var i = 1, n = link.getClient('scaleNumbers') * 2; i < n; i++) {
236             this.drawScaleLine(from, to, angle, width/2, ctx, i/n, scaleWidth, scaleColor);
237         }
238         // 画分隔线
239         this.drawScaleLine(from, to, angle, width, ctx, 0.5, 3, link.getClient('splitterColor'));
240     }
241 });

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

  1 // 自定义Node构造函数
  2 demo.ScaleNode = function(id) {
  3     // 调用基类构造函数
  4     demo.ScaleNode.superClass.constructor.call(this, id);
  5 };
  6 // 设置自定义Node继承twaver.Node
  7 twaver.Util.ext('demo.ScaleNode', twaver.Node, {
  8     getCanvasUIClass: function () {
  9         return demo.ScaleNodeUI;
 10     }
 11 });
 12 
 13 // 自定义NodeUI构造函数
 14 demo.ScaleNodeUI = function(network, element){
 15     // 调用基类构造函数
 16     demo.ScaleNodeUI.superClass.constructor.call(this, network, element);
 17 };
 18 // 设置自定义NodeUI继承twaver.canvas.NodeUI
 19 twaver.Util.ext('demo.ScaleNodeUI', twaver.canvas.NodeUI, {
 20     // 重载画网元方法,画上层链路
 21     paintBody: function (ctx) {
 22         demo.ScaleNodeUI.superClass.paintBody.call(this, ctx);
 23         var result = this.getAttachedLinks();
 24         if (!result) {
 25             return;
 26         }
 27         for (var position in result) {
 28             this.paintLink(ctx, result[position], position);
 29         }
 30     },
 31     // 画链路
 32     paintLink: function (ctx, links, position) {
 33         var center = this.getElement().getCenterLocation(),
 34             count = links.length,
 35             half = count / 2,
 36             network = this.getNetwork(),
 37             gap = (count - 1) * -10,
 38             terminal, link, i, offset, shortenLength, angle, tempCenter, textWidth, textHeight = 20, textCenter;
 39         for (i=0; i<count; i++) {
 40             link = links[i];
 41             offset = link.getStyle('link.bundle.offset');
 42             shortenLength = link.getClient('shortenLength');
 43             textWidth = ctx.measureText(link.getName()).width;
 44             if (position === 'left') {
 45                 terminal = {x: center.x - offset - shortenLength, y: center.y + gap};
 46                 tempCenter = {x: center.x - offset, y: center.y + gap};
 47                 textCenter = {x: terminal.x - textWidth/2 - 10, y: terminal.y};
 48                 angle = Math.PI/2;
 49             } else if (position === 'right') {
 50                 terminal = {x: center.x + offset + shortenLength, y: center.y + gap};
 51                 tempCenter = {x: center.x + offset, y: center.y + gap};
 52                 textCenter = {x: terminal.x + textWidth/2 + 10, y: terminal.y};
 53                 angle = Math.PI/2;
 54             } else if (position === 'top') {
 55                 terminal = {x: center.x + gap, y: center.y - offset - shortenLength};
 56                 tempCenter = {x: center.x + gap, y: center.y - offset};
 57                 textCenter = {x: terminal.x, y: terminal.y - 10};
 58                 angle = 0;
 59             } else {
 60                 terminal = {x: center.x + gap, y: center.y + offset + shortenLength};
 61                 tempCenter = {x: center.x + gap, y: center.y + offset};
 62                 textCenter = {x: terminal.x, y: terminal.y + 10};
 63                 angle = 0;
 64             }
 65             gap += 20;
 66             var isFrom = link.getFromNode() === this.getElement(),
 67                 points;
 68             if (isFrom) {
 69                 points = new twaver.List([tempCenter, terminal]);
 70             } else {
 71                 points = new twaver.List([terminal, tempCenter]);
 72             }
 73             network.getElementUI(link)._paintBody(ctx, points, angle);
 74 
 75             ctx.textAlign = 'center';
 76             ctx.textBaseline = 'middle';
 77             ctx.fillStyle = 'black';
 78             // 另一端节点标签
 79             var name = isFrom ? link.getToNode().getName() : link.getFromNode().getName();
 80             ctx.fillText(name, textCenter.x, textCenter.y);
 81             textCenter = {x: (tempCenter.x + terminal.x)/2, y: (tempCenter.y + terminal.y)/2};
 82             // Link标签
 83             ctx.fillText(link.getName(), textCenter.x, textCenter.y);
 84 
 85             // 画起始箭头
 86             if (link.getClient('arrow.from')) {
 87                 twaver.Util.drawArrow(ctx, 12, 9, points, true, 'arrow.standard', true, 'gray', 0, 0, 1, 'black');
 88             }
 89             // 画结束箭头
 90             if (link.getClient('arrow.to')) {
 91                 twaver.Util.drawArrow(ctx, 12, 9, points, false, 'arrow.standard', true, 'gray', 0, 0, 1, 'black');
 92             }
 93         }
 94     },
 95     // 获取不同方位的上层链路集合
 96     getAttachedLinks: function () {
 97         var currentSubNetwork = this.getNetwork().getCurrentSubNetwork();
 98         if (!currentSubNetwork || !this.getElement().getLinks()) {
 99             return null;
100         }
101         var result;
102         this.getElement().getLinks().forEach(function (link) {
103             var fromSubNetwork = twaver.Util.getSubNetwork(link.getFromNode()),
104                 toSubNetwork = twaver.Util.getSubNetwork(link.getToNode());
105             if (fromSubNetwork !== toSubNetwork) {
106                 if (!result) {
107                     result = {};
108                 }
109                 var fromCenter = link.getFromNode().getCenterLocation(),
110                     toCenter = link.getToNode().getCenterLocation(),
111                     angle = getAngle(fromCenter, toCenter),
112                     isOut = currentSubNetwork === fromSubNetwork,
113                     position;
114                 if (isOut) {
115                     if (fromCenter.x <= toCenter.x) {
116                         if (angle >= -Math.PI/4 && angle <= Math.PI/4) {
117                             position = 'right';
118                         } else if (angle > Math.PI/4) {
119                             position = 'bottom';
120                         } else {
121                             position = 'top';
122                         }
123                     } else {
124                         if (angle >= -Math.PI/4 && angle <= Math.PI/4) {
125                             position = 'left';
126                         } else if (angle > Math.PI/4) {
127                             position = 'top';
128                         } else {
129                             position = 'bottom';
130                         }
131                     }
132                 } else {
133                     if (fromCenter.x <= toCenter.x) {
134                         if (angle >= -Math.PI/4 && angle <= Math.PI/4) {
135                             position = 'left';
136                         } else if (angle > Math.PI/4) {
137                             position = 'top';
138                         } else {
139                             position = 'bottom';
140                         }
141                     } else {
142                         if (angle >= -Math.PI/4 && angle <= Math.PI/4) {
143                             position = 'right';
144                         } else if (angle > Math.PI/4) {
145                             position = 'bottom';
146                         } else {
147                             position = 'top';
148                         }
149                     }
150                 }
151                 if (!result[position]) {
152                     result[position] = [];
153                 }
154                 result[position].push(link);
155             }
156         });
157         return result;
158     }
159 });

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

只有注册用户登录后才能发表评论。


网站导航: