blob: 1f061dd6860dbbe166ff341b925a4f513135b45d [file] [log] [blame]
Simon Hunta4242de2015-02-24 17:11:55 -08001/*
2 * Copyright 2015 Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/*
18 ONOS GUI -- Topology D3 Module.
19 Functions for manipulating the D3 visualizations of the Topology
20 */
21
22(function () {
23 'use strict';
24
25 // injected refs
26 var $log, fs, sus, is, ts;
27
28 // api to topoForce
29 var api;
30 /*
31 node() // get ref to D3 selection of nodes
32 link() // get ref to D3 selection of links
33 linkLabel() // get ref to D3 selection of link labels
34 instVisible() // true if instances panel is visible
35 posNode() // position node
36 showHosts() // true if hosts are to be shown
37 restyleLinkElement() // update link styles based on backing data
38 updateLinkLabelModel() // update backing data for link labels
39 */
40
41 // configuration
42 var devCfg = {
43 xoff: -20,
44 yoff: -18
45 },
46 labelConfig = {
47 imgPad: 16,
48 padLR: 4,
49 padTB: 3,
50 marginLR: 3,
51 marginTB: 2,
52 port: {
53 gap: 3,
54 width: 18,
55 height: 14
56 }
57 },
58 icfg;
59
60 // internal state
61 var deviceLabelIndex = 0,
62 hostLabelIndex = 0;
63
64
65 var dCol = {
66 black: '#000',
67 paleblue: '#acf',
68 offwhite: '#ddd',
69 darkgrey: '#444',
70 midgrey: '#888',
71 lightgrey: '#bbb',
72 orange: '#f90'
73 };
74
75 // note: these are the device icon colors without affinity
76 var dColTheme = {
77 light: {
78 rfill: dCol.offwhite,
79 online: {
80 glyph: dCol.darkgrey,
81 rect: dCol.paleblue
82 },
83 offline: {
84 glyph: dCol.midgrey,
85 rect: dCol.lightgrey
86 }
87 },
88 dark: {
89 rfill: dCol.midgrey,
90 online: {
91 glyph: dCol.darkgrey,
92 rect: dCol.paleblue
93 },
94 offline: {
95 glyph: dCol.midgrey,
96 rect: dCol.darkgrey
97 }
98 }
99 };
100
101 function devBaseColor(d) {
102 var o = d.online ? 'online' : 'offline';
103 return dColTheme[ts.theme()][o];
104 }
105
106 function setDeviceColor(d) {
107 var o = d.online,
108 s = d.el.classed('selected'),
109 c = devBaseColor(d),
110 a = instColor(d.master, o),
111 icon = d.el.select('g.deviceIcon'),
112 g, r;
113
114 if (s) {
115 g = c.glyph;
116 r = dCol.orange;
117 } else if (api.instVisible()) {
118 g = o ? a : c.glyph;
119 r = o ? c.rfill : a;
120 } else {
121 g = c.glyph;
122 r = c.rect;
123 }
124
125 icon.select('use').style('fill', g);
126 icon.select('rect').style('fill', r);
127 }
128
129 function instColor(id, online) {
130 return sus.cat7().getColor(id, !online, ts.theme());
131 }
132
133 // ====
134
135 function incDevLabIndex() {
136 deviceLabelIndex = (deviceLabelIndex+1) % 3;
Bri Prebilic Cole9cf1a8d2015-04-21 13:15:29 -0700137 switch(deviceLabelIndex) {
138 case 0: return 'Hide device labels';
139 case 1: return 'Show friendly device labels';
140 case 2: return 'Show device ID labels';
141 }
Simon Hunta4242de2015-02-24 17:11:55 -0800142 }
143
144 // Returns the newly computed bounding box of the rectangle
145 function adjustRectToFitText(n) {
146 var text = n.select('text'),
147 box = text.node().getBBox(),
148 lab = labelConfig;
149
150 text.attr('text-anchor', 'middle')
151 .attr('y', '-0.8em')
152 .attr('x', lab.imgPad/2);
153
154 // translate the bbox so that it is centered on [x,y]
155 box.x = -box.width / 2;
156 box.y = -box.height / 2;
157
158 // add padding
159 box.x -= (lab.padLR + lab.imgPad/2);
160 box.width += lab.padLR * 2 + lab.imgPad;
161 box.y -= lab.padTB;
162 box.height += lab.padTB * 2;
163
164 return box;
165 }
166
167 function hostLabel(d) {
168 var idx = (hostLabelIndex < d.labels.length) ? hostLabelIndex : 0;
169 return d.labels[idx];
170 }
171 function deviceLabel(d) {
172 var idx = (deviceLabelIndex < d.labels.length) ? deviceLabelIndex : 0;
173 return d.labels[idx];
174 }
175 function trimLabel(label) {
176 return (label && label.trim()) || '';
177 }
178
179 function emptyBox() {
180 return {
181 x: -2,
182 y: -2,
183 width: 4,
184 height: 4
185 };
186 }
187
188
189 function updateDeviceLabel(d) {
190 var label = trimLabel(deviceLabel(d)),
191 noLabel = !label,
192 node = d.el,
193 dim = icfg.device.dim,
194 box, dx, dy;
195
196 node.select('text')
197 .text(label)
198 .style('opacity', 0)
199 .transition()
200 .style('opacity', 1);
201
202 if (noLabel) {
203 box = emptyBox();
204 dx = -dim/2;
205 dy = -dim/2;
206 } else {
207 box = adjustRectToFitText(node);
208 dx = box.x + devCfg.xoff;
209 dy = box.y + devCfg.yoff;
210 }
211
212 node.select('rect')
213 .transition()
214 .attr(box);
215
216 node.select('g.deviceIcon')
217 .transition()
218 .attr('transform', sus.translate(dx, dy));
219 }
220
Simon Hunte9343f32015-10-21 18:07:46 -0700221 function updateDeviceBadge(d) {
222 // TODO: Fix this WIP
223 var node = d.el,
224 bsel;
225
226 if (d.badge) {
227 bsel = node.append('g')
228 .classed('badge', true)
229 .attr('transform', sus.translate(-14, -14));
230
231 bsel.append('circle')
232 .attr('r', 14);
233 bsel.append('text')
234 .attr('transform', sus.translate(-5, 3))
235 .text('42');
236 }
237 }
238
Simon Hunta4242de2015-02-24 17:11:55 -0800239 function updateHostLabel(d) {
240 var label = trimLabel(hostLabel(d));
241 d.el.select('text').text(label);
242 }
243
244 function updateDeviceColors(d) {
245 if (d) {
246 setDeviceColor(d);
247 } else {
248 api.node().filter('.device').each(function (d) {
249 setDeviceColor(d);
250 });
251 }
252 }
253
254
255 // ==========================
256 // updateNodes - subfunctions
257
258 function deviceExisting(d) {
259 var node = d.el;
260 node.classed('online', d.online);
261 updateDeviceLabel(d);
Simon Hunte9343f32015-10-21 18:07:46 -0700262 updateDeviceBadge(d);
Simon Hunta4242de2015-02-24 17:11:55 -0800263 api.posNode(d, true);
264 }
265
266 function hostExisting(d) {
267 updateHostLabel(d);
268 api.posNode(d, true);
269 }
270
271 function deviceEnter(d) {
272 var node = d3.select(this),
273 glyphId = d.type || 'unknown',
274 label = trimLabel(deviceLabel(d)),
275 //devCfg = deviceIconConfig,
276 noLabel = !label,
277 box, dx, dy, icon;
278
279 d.el = node;
280
281 node.append('rect').attr({ rx: 5, ry: 5 });
282 node.append('text').text(label).attr('dy', '1.1em');
283 box = adjustRectToFitText(node);
284 node.select('rect').attr(box);
285
286 icon = is.addDeviceIcon(node, glyphId);
287
288 if (noLabel) {
289 dx = -icon.dim/2;
290 dy = -icon.dim/2;
291 } else {
292 box = adjustRectToFitText(node);
293 dx = box.x + devCfg.xoff;
294 dy = box.y + devCfg.yoff;
295 }
296
297 icon.attr('transform', sus.translate(dx, dy));
298 }
299
300 function hostEnter(d) {
301 var node = d3.select(this),
302 gid = d.type || 'unknown',
303 rad = icfg.host.radius,
304 r = d.type ? rad.withGlyph : rad.noGlyph,
305 textDy = r + 10;
306
307 d.el = node;
308 sus.visible(node, api.showHosts());
309
310 is.addHostIcon(node, r, gid);
311
312 node.append('text')
313 .text(hostLabel)
314 .attr('dy', textDy)
315 .attr('text-anchor', 'middle');
316 }
317
318 function hostExit(d) {
319 var node = d.el;
320 node.select('use')
321 .style('opacity', 0.5)
322 .transition()
323 .duration(800)
324 .style('opacity', 0);
325
326 node.select('text')
327 .style('opacity', 0.5)
328 .transition()
329 .duration(800)
330 .style('opacity', 0);
331
332 node.select('circle')
333 .style('stroke-fill', '#555')
334 .style('fill', '#888')
335 .style('opacity', 0.5)
336 .transition()
337 .duration(1500)
338 .attr('r', 0);
339 }
340
341 function deviceExit(d) {
342 var node = d.el;
343 node.select('use')
344 .style('opacity', 0.5)
345 .transition()
346 .duration(800)
347 .style('opacity', 0);
348
349 node.selectAll('rect')
350 .style('stroke-fill', '#555')
351 .style('fill', '#888')
352 .style('opacity', 0.5);
353 }
354
355
356 // ==========================
357 // updateLinks - subfunctions
358
Simon Hunta4242de2015-02-24 17:11:55 -0800359 function linkEntering(d) {
360 var link = d3.select(this);
361 d.el = link;
362 api.restyleLinkElement(d);
363 if (d.type() === 'hostLink') {
364 sus.visible(link, api.showHosts());
365 }
366 }
367
368 var linkLabelOffset = '0.3em';
369
370 function applyLinkLabels() {
371 var entering;
372
373 api.updateLinkLabelModel();
374
375 // for elements already existing, we need to update the text
376 // and adjust the rectangle size to fit
377 api.linkLabel().each(function (d) {
378 var el = d3.select(this),
379 rect = el.select('rect'),
380 text = el.select('text');
381 text.text(d.label);
382 rect.attr(rectAroundText(el));
383 });
384
385 entering = api.linkLabel().enter().append('g')
386 .classed('linkLabel', true)
387 .attr('id', function (d) { return d.id; });
388
389 entering.each(function (d) {
390 var el = d3.select(this),
391 rect,
Bri Prebilic Cole038aedd2015-07-13 15:25:16 -0700392 text;
Simon Hunta4242de2015-02-24 17:11:55 -0800393
394 if (d.ldata.type() === 'hostLink') {
395 el.classed('hostLinkLabel', true);
396 sus.visible(el, api.showHosts());
397 }
398
399 d.el = el;
400 rect = el.append('rect');
401 text = el.append('text').text(d.label);
402 rect.attr(rectAroundText(el));
403 text.attr('dy', linkLabelOffset);
404
Bri Prebilic Cole038aedd2015-07-13 15:25:16 -0700405 el.attr('transform', transformLabel(d.ldata.position));
Simon Hunta4242de2015-02-24 17:11:55 -0800406 });
407
408 // Remove any labels that are no longer required.
409 api.linkLabel().exit().remove();
410 }
411
412 function rectAroundText(el) {
413 var text = el.select('text'),
414 box = text.node().getBBox();
415
416 // translate the bbox so that it is centered on [x,y]
417 box.x = -box.width / 2;
418 box.y = -box.height / 2;
419
420 // add padding
421 box.x -= 1;
422 box.width += 2;
423 return box;
424 }
425
426 function transformLabel(p) {
427 var dx = p.x2 - p.x1,
428 dy = p.y2 - p.y1,
429 xMid = dx/2 + p.x1,
430 yMid = dy/2 + p.y1;
431 return sus.translate(xMid, yMid);
432 }
433
Simon Hunt1a5301e2015-02-25 15:31:25 -0800434 function applyPortLabels(data, portLabelG) {
435 var entering = portLabelG.selectAll('.portLabel')
436 .data(data).enter().append('g')
437 .classed('portLabel', true)
438 .attr('id', function (d) { return d.id; });
439
440 entering.each(function (d) {
441 var el = d3.select(this),
442 rect = el.append('rect'),
443 text = el.append('text').text(d.num);
444
445 rect.attr(rectAroundText(el));
446 text.attr('dy', linkLabelOffset);
Simon Hunt969b3c92015-02-25 18:11:31 -0800447 el.attr('transform', sus.translate(d.x, d.y));
Simon Hunt1a5301e2015-02-25 15:31:25 -0800448 });
449 }
450
Bri Prebilic Cole80401762015-07-16 11:36:18 -0700451 function labelPoint(linkPos) {
452 var lengthUpLine = 1 / 3,
453 dx = linkPos.x2 - linkPos.x1,
454 dy = linkPos.y2 - linkPos.y1,
455 movedX = dx * lengthUpLine,
456 movedY = dy * lengthUpLine;
457
458 return {
459 x: movedX,
460 y: movedY
461 };
462 }
463
464 function calcGroupPos(linkPos) {
465 var moved = labelPoint(linkPos);
466 return sus.translate(linkPos.x1 + moved.x, linkPos.y1 + moved.y);
467 }
468
469 // calculates where on the link that the hash line for 5+ label appears
470 function hashAttrs(linkPos) {
471 var hashLength = 25,
472 halfLength = hashLength / 2,
473 dx = linkPos.x2 - linkPos.x1,
474 dy = linkPos.y2 - linkPos.y1,
475 length = Math.sqrt((dx * dx) + (dy * dy)),
476 moveAmtX = (dx / length) * halfLength,
477 moveAmtY = (dy / length) * halfLength,
478 mid = labelPoint(linkPos),
479 angle = Math.atan(dy / dx) + 45;
480
481 return {
482 x1: mid.x - moveAmtX,
483 y1: mid.y - moveAmtY,
484 x2: mid.x + moveAmtX,
485 y2: mid.y + moveAmtY,
486 stroke: api.linkConfig()[ts.theme()].baseColor,
487 transform: 'rotate(' + angle + ',' + mid.x + ',' + mid.y + ')'
488 };
489 }
490
491 function textLabelPos(linkPos) {
492 var point = labelPoint(linkPos),
493 dist = 20;
494 return {
495 x: point.x + dist,
496 y: point.y + dist
497 };
498 }
499
500 function applyNumLinkLabels(data, lblsG) {
501 var labels = lblsG.selectAll('g.numLinkLabel')
502 .data(data, function (d) { return 'pair-' + d.id; }),
503 entering;
504
505 // update existing labels
506 labels.each(function (d) {
507 var el = d3.select(this);
508
509 el.attr({
510 transform: function (d) { return calcGroupPos(d.linkCoords); }
511 });
512 el.select('line')
513 .attr(hashAttrs(d.linkCoords));
514 el.select('text')
515 .attr(textLabelPos(d.linkCoords))
516 .text(d.num);
517 });
518
519 // add new labels
520 entering = labels
521 .enter()
522 .append('g')
523 .attr({
524 transform: function (d) { return calcGroupPos(d.linkCoords); },
525 id: function (d) { return 'pair-' + d.id; }
526 })
527 .classed('numLinkLabel', true);
528
529 entering.each(function (d) {
530 var el = d3.select(this);
531
532 el.append('line')
533 .classed('numLinkHash', true)
534 .attr(hashAttrs(d.linkCoords));
535 el.append('text')
536 .classed('numLinkText', true)
537 .attr(textLabelPos(d.linkCoords))
538 .text(d.num);
539 });
540
541 // remove old labels
542 labels.exit().remove();
543 }
544
Simon Hunta4242de2015-02-24 17:11:55 -0800545 // ==========================
546 // Module definition
547
548 angular.module('ovTopo')
549 .factory('TopoD3Service',
550 ['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService',
551
552 function (_$log_, _fs_, _sus_, _is_, _ts_) {
553 $log = _$log_;
554 fs = _fs_;
555 sus = _sus_;
556 is = _is_;
557 ts = _ts_;
558
559 icfg = is.iconConfig();
560
561 function initD3(_api_) {
562 api = _api_;
563 }
564
565 function destroyD3() { }
566
567 return {
568 initD3: initD3,
569 destroyD3: destroyD3,
570
571 incDevLabIndex: incDevLabIndex,
572 adjustRectToFitText: adjustRectToFitText,
573 hostLabel: hostLabel,
574 deviceLabel: deviceLabel,
575 trimLabel: trimLabel,
576
577 updateDeviceLabel: updateDeviceLabel,
578 updateHostLabel: updateHostLabel,
579 updateDeviceColors: updateDeviceColors,
580
581 deviceExisting: deviceExisting,
582 hostExisting: hostExisting,
583 deviceEnter: deviceEnter,
584 hostEnter: hostEnter,
585 hostExit: hostExit,
586 deviceExit: deviceExit,
587
Simon Hunta4242de2015-02-24 17:11:55 -0800588 linkEntering: linkEntering,
589 applyLinkLabels: applyLinkLabels,
Simon Hunt1a5301e2015-02-25 15:31:25 -0800590 transformLabel: transformLabel,
Bri Prebilic Cole80401762015-07-16 11:36:18 -0700591 applyPortLabels: applyPortLabels,
592 applyNumLinkLabels: applyNumLinkLabels
Simon Hunta4242de2015-02-24 17:11:55 -0800593 };
594 }]);
595}());