blob: ef704c75ed0ae5a8502393e46ea5917e376a1229 [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
Thomas Vachuska0af26912016-03-21 21:37:30 -070026 var $log, fs, sus, is, ts, ps, ttbs;
Simon Hunta4242de2015-02-24 17:11:55 -080027
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 },
Simon Hunt5674db92015-10-22 16:12:48 -070058 badgeConfig = {
59 radius: 12,
Simon Hunt004fc2c2015-10-23 11:55:58 -070060 yoff: 5,
61 gdelta: 10
Simon Hunt5674db92015-10-22 16:12:48 -070062 },
Simon Hunta4242de2015-02-24 17:11:55 -080063 icfg;
64
Simon Hunt5674db92015-10-22 16:12:48 -070065 var status = {
66 i: 'badgeInfo',
67 w: 'badgeWarn',
68 e: 'badgeError'
69 };
70
Simon Hunt1eee51d2016-02-26 19:12:13 -080071 // NOTE: this type of hack should go away once we have implemented
72 // the server-side UiModel code.
73 // {virtual -> cord} is for the E-CORD demo at ONS 2016
74 var remappedDeviceTypes = {
75 virtual: 'cord'
76 };
77
78 function mapDeviceTypeToGlyph(type) {
79 return remappedDeviceTypes[type] || type || 'unknown';
80 }
81
Simon Hunt5674db92015-10-22 16:12:48 -070082 function badgeStatus(badge) {
83 return status[badge.status] || status.i;
84 }
85
Simon Hunta4242de2015-02-24 17:11:55 -080086 // internal state
87 var deviceLabelIndex = 0,
88 hostLabelIndex = 0;
89
90
91 var dCol = {
92 black: '#000',
93 paleblue: '#acf',
94 offwhite: '#ddd',
95 darkgrey: '#444',
96 midgrey: '#888',
97 lightgrey: '#bbb',
98 orange: '#f90'
99 };
100
101 // note: these are the device icon colors without affinity
102 var dColTheme = {
103 light: {
104 rfill: dCol.offwhite,
105 online: {
106 glyph: dCol.darkgrey,
107 rect: dCol.paleblue
108 },
109 offline: {
110 glyph: dCol.midgrey,
111 rect: dCol.lightgrey
112 }
113 },
114 dark: {
115 rfill: dCol.midgrey,
116 online: {
117 glyph: dCol.darkgrey,
118 rect: dCol.paleblue
119 },
120 offline: {
121 glyph: dCol.midgrey,
122 rect: dCol.darkgrey
123 }
124 }
125 };
126
127 function devBaseColor(d) {
128 var o = d.online ? 'online' : 'offline';
129 return dColTheme[ts.theme()][o];
130 }
131
132 function setDeviceColor(d) {
133 var o = d.online,
134 s = d.el.classed('selected'),
135 c = devBaseColor(d),
136 a = instColor(d.master, o),
137 icon = d.el.select('g.deviceIcon'),
138 g, r;
139
140 if (s) {
141 g = c.glyph;
142 r = dCol.orange;
143 } else if (api.instVisible()) {
144 g = o ? a : c.glyph;
145 r = o ? c.rfill : a;
146 } else {
147 g = c.glyph;
148 r = c.rect;
149 }
150
151 icon.select('use').style('fill', g);
152 icon.select('rect').style('fill', r);
153 }
154
155 function instColor(id, online) {
156 return sus.cat7().getColor(id, !online, ts.theme());
157 }
158
159 // ====
160
161 function incDevLabIndex() {
Thomas Vachuska0af26912016-03-21 21:37:30 -0700162 setDevLabIndex(deviceLabelIndex+1);
Bri Prebilic Cole9cf1a8d2015-04-21 13:15:29 -0700163 switch(deviceLabelIndex) {
164 case 0: return 'Hide device labels';
165 case 1: return 'Show friendly device labels';
166 case 2: return 'Show device ID labels';
167 }
Simon Hunta4242de2015-02-24 17:11:55 -0800168 }
169
Thomas Vachuska0af26912016-03-21 21:37:30 -0700170 function setDevLabIndex(mode) {
171 deviceLabelIndex = mode % 3;
172 var p = ps.getPrefs('topo_prefs', ttbs.defaultPrefs);
173 p.dlbls = deviceLabelIndex;
174 ps.setPrefs('topo_prefs', p);
175 }
176
Simon Hunta4242de2015-02-24 17:11:55 -0800177 // Returns the newly computed bounding box of the rectangle
178 function adjustRectToFitText(n) {
179 var text = n.select('text'),
180 box = text.node().getBBox(),
181 lab = labelConfig;
182
183 text.attr('text-anchor', 'middle')
184 .attr('y', '-0.8em')
185 .attr('x', lab.imgPad/2);
186
187 // translate the bbox so that it is centered on [x,y]
188 box.x = -box.width / 2;
189 box.y = -box.height / 2;
190
191 // add padding
192 box.x -= (lab.padLR + lab.imgPad/2);
193 box.width += lab.padLR * 2 + lab.imgPad;
194 box.y -= lab.padTB;
195 box.height += lab.padTB * 2;
196
197 return box;
198 }
199
200 function hostLabel(d) {
201 var idx = (hostLabelIndex < d.labels.length) ? hostLabelIndex : 0;
202 return d.labels[idx];
203 }
204 function deviceLabel(d) {
205 var idx = (deviceLabelIndex < d.labels.length) ? deviceLabelIndex : 0;
206 return d.labels[idx];
207 }
208 function trimLabel(label) {
209 return (label && label.trim()) || '';
210 }
211
212 function emptyBox() {
213 return {
214 x: -2,
215 y: -2,
216 width: 4,
217 height: 4
218 };
219 }
220
Simon Hunt5674db92015-10-22 16:12:48 -0700221 function updateDeviceRendering(d) {
Simon Hunta4242de2015-02-24 17:11:55 -0800222 var label = trimLabel(deviceLabel(d)),
223 noLabel = !label,
224 node = d.el,
225 dim = icfg.device.dim,
Simon Huntd0f063a2016-01-14 17:29:25 -0800226 box, dx, dy,
227 bdg = d.badge;
Simon Hunta4242de2015-02-24 17:11:55 -0800228
229 node.select('text')
Simon Huntd0f063a2016-01-14 17:29:25 -0800230 .text(label);
Simon Hunta4242de2015-02-24 17:11:55 -0800231
232 if (noLabel) {
233 box = emptyBox();
234 dx = -dim/2;
235 dy = -dim/2;
236 } else {
237 box = adjustRectToFitText(node);
238 dx = box.x + devCfg.xoff;
239 dy = box.y + devCfg.yoff;
240 }
241
242 node.select('rect')
243 .transition()
244 .attr(box);
245
246 node.select('g.deviceIcon')
247 .transition()
248 .attr('transform', sus.translate(dx, dy));
Simon Hunta4242de2015-02-24 17:11:55 -0800249
Simon Hunt5674db92015-10-22 16:12:48 -0700250 // handle badge, if defined
251 if (bdg) {
Simon Huntc2bfe332015-12-04 11:01:24 -0800252 renderBadge(node, bdg, { dx: dx + dim, dy: dy });
Simon Hunte9343f32015-10-21 18:07:46 -0700253 }
254 }
255
Andrea Campanella52125412015-12-03 14:50:40 -0800256 function updateHostRendering(d) {
257 var node = d.el,
Simon Huntc2bfe332015-12-04 11:01:24 -0800258 bdg = d.badge;
Andrea Campanella52125412015-12-03 14:50:40 -0800259
260 updateHostLabel(d);
Andrea Campanella52125412015-12-03 14:50:40 -0800261
262 // handle badge, if defined
263 if (bdg) {
Simon Huntc2bfe332015-12-04 11:01:24 -0800264 renderBadge(node, bdg, icfg.host.badge);
265 }
266 }
Andrea Campanella52125412015-12-03 14:50:40 -0800267
Simon Huntc2bfe332015-12-04 11:01:24 -0800268 function renderBadge(node, bdg, boff) {
269 var bsel,
270 bcr = badgeConfig.radius,
271 bcgd = badgeConfig.gdelta;
Andrea Campanella52125412015-12-03 14:50:40 -0800272
Simon Huntc2bfe332015-12-04 11:01:24 -0800273 node.select('g.badge').remove();
Andrea Campanella52125412015-12-03 14:50:40 -0800274
Simon Huntc2bfe332015-12-04 11:01:24 -0800275 bsel = node.append('g')
276 .classed('badge', true)
277 .classed(badgeStatus(bdg), true)
278 .attr('transform', sus.translate(boff.dx, boff.dy));
Andrea Campanella52125412015-12-03 14:50:40 -0800279
Simon Huntc2bfe332015-12-04 11:01:24 -0800280 bsel.append('circle')
281 .attr('r', bcr);
282
283 if (bdg.txt) {
284 bsel.append('text')
285 .attr('dy', badgeConfig.yoff)
286 .attr('text-anchor', 'middle')
287 .text(bdg.txt);
288 } else if (bdg.gid) {
289 bsel.append('use')
290 .attr({
291 width: bcgd * 2,
292 height: bcgd * 2,
293 transform: sus.translate(-bcgd, -bcgd),
294 'xlink:href': '#' + bdg.gid
295 });
Andrea Campanella52125412015-12-03 14:50:40 -0800296 }
297 }
298
Simon Hunta4242de2015-02-24 17:11:55 -0800299 function updateHostLabel(d) {
300 var label = trimLabel(hostLabel(d));
301 d.el.select('text').text(label);
302 }
303
304 function updateDeviceColors(d) {
305 if (d) {
306 setDeviceColor(d);
307 } else {
308 api.node().filter('.device').each(function (d) {
309 setDeviceColor(d);
310 });
311 }
312 }
313
314
315 // ==========================
316 // updateNodes - subfunctions
317
318 function deviceExisting(d) {
319 var node = d.el;
320 node.classed('online', d.online);
Simon Hunt5674db92015-10-22 16:12:48 -0700321 updateDeviceRendering(d);
Simon Hunta4242de2015-02-24 17:11:55 -0800322 api.posNode(d, true);
323 }
324
325 function hostExisting(d) {
Andrea Campanella52125412015-12-03 14:50:40 -0800326 updateHostRendering(d);
Simon Hunta4242de2015-02-24 17:11:55 -0800327 api.posNode(d, true);
328 }
329
330 function deviceEnter(d) {
331 var node = d3.select(this),
Simon Hunt1eee51d2016-02-26 19:12:13 -0800332 glyphId = mapDeviceTypeToGlyph(d.type),
Simon Hunta4242de2015-02-24 17:11:55 -0800333 label = trimLabel(deviceLabel(d)),
Simon Hunta4242de2015-02-24 17:11:55 -0800334 noLabel = !label,
335 box, dx, dy, icon;
336
337 d.el = node;
338
339 node.append('rect').attr({ rx: 5, ry: 5 });
340 node.append('text').text(label).attr('dy', '1.1em');
341 box = adjustRectToFitText(node);
342 node.select('rect').attr(box);
343
344 icon = is.addDeviceIcon(node, glyphId);
345
346 if (noLabel) {
347 dx = -icon.dim/2;
348 dy = -icon.dim/2;
349 } else {
350 box = adjustRectToFitText(node);
351 dx = box.x + devCfg.xoff;
352 dy = box.y + devCfg.yoff;
353 }
354
355 icon.attr('transform', sus.translate(dx, dy));
356 }
357
358 function hostEnter(d) {
359 var node = d3.select(this),
360 gid = d.type || 'unknown',
361 rad = icfg.host.radius,
362 r = d.type ? rad.withGlyph : rad.noGlyph,
363 textDy = r + 10;
364
365 d.el = node;
366 sus.visible(node, api.showHosts());
367
368 is.addHostIcon(node, r, gid);
369
370 node.append('text')
371 .text(hostLabel)
372 .attr('dy', textDy)
373 .attr('text-anchor', 'middle');
374 }
375
376 function hostExit(d) {
377 var node = d.el;
378 node.select('use')
379 .style('opacity', 0.5)
380 .transition()
381 .duration(800)
382 .style('opacity', 0);
383
384 node.select('text')
385 .style('opacity', 0.5)
386 .transition()
387 .duration(800)
388 .style('opacity', 0);
389
390 node.select('circle')
391 .style('stroke-fill', '#555')
392 .style('fill', '#888')
393 .style('opacity', 0.5)
394 .transition()
395 .duration(1500)
396 .attr('r', 0);
397 }
398
399 function deviceExit(d) {
400 var node = d.el;
401 node.select('use')
402 .style('opacity', 0.5)
403 .transition()
404 .duration(800)
405 .style('opacity', 0);
406
407 node.selectAll('rect')
408 .style('stroke-fill', '#555')
409 .style('fill', '#888')
410 .style('opacity', 0.5);
411 }
412
413
414 // ==========================
415 // updateLinks - subfunctions
416
Simon Hunta4242de2015-02-24 17:11:55 -0800417 function linkEntering(d) {
418 var link = d3.select(this);
419 d.el = link;
420 api.restyleLinkElement(d);
421 if (d.type() === 'hostLink') {
422 sus.visible(link, api.showHosts());
423 }
424 }
425
426 var linkLabelOffset = '0.3em';
427
428 function applyLinkLabels() {
429 var entering;
430
431 api.updateLinkLabelModel();
432
433 // for elements already existing, we need to update the text
434 // and adjust the rectangle size to fit
435 api.linkLabel().each(function (d) {
436 var el = d3.select(this),
437 rect = el.select('rect'),
438 text = el.select('text');
439 text.text(d.label);
440 rect.attr(rectAroundText(el));
441 });
442
443 entering = api.linkLabel().enter().append('g')
444 .classed('linkLabel', true)
445 .attr('id', function (d) { return d.id; });
446
447 entering.each(function (d) {
448 var el = d3.select(this),
449 rect,
Bri Prebilic Cole038aedd2015-07-13 15:25:16 -0700450 text;
Simon Hunta4242de2015-02-24 17:11:55 -0800451
452 if (d.ldata.type() === 'hostLink') {
453 el.classed('hostLinkLabel', true);
454 sus.visible(el, api.showHosts());
455 }
456
457 d.el = el;
458 rect = el.append('rect');
459 text = el.append('text').text(d.label);
460 rect.attr(rectAroundText(el));
461 text.attr('dy', linkLabelOffset);
462
Bri Prebilic Cole038aedd2015-07-13 15:25:16 -0700463 el.attr('transform', transformLabel(d.ldata.position));
Simon Hunta4242de2015-02-24 17:11:55 -0800464 });
465
466 // Remove any labels that are no longer required.
467 api.linkLabel().exit().remove();
468 }
469
470 function rectAroundText(el) {
471 var text = el.select('text'),
472 box = text.node().getBBox();
473
474 // translate the bbox so that it is centered on [x,y]
475 box.x = -box.width / 2;
476 box.y = -box.height / 2;
477
478 // add padding
479 box.x -= 1;
480 box.width += 2;
481 return box;
482 }
483
484 function transformLabel(p) {
485 var dx = p.x2 - p.x1,
486 dy = p.y2 - p.y1,
487 xMid = dx/2 + p.x1,
488 yMid = dy/2 + p.y1;
489 return sus.translate(xMid, yMid);
490 }
491
Simon Hunt1a5301e2015-02-25 15:31:25 -0800492 function applyPortLabels(data, portLabelG) {
493 var entering = portLabelG.selectAll('.portLabel')
494 .data(data).enter().append('g')
495 .classed('portLabel', true)
496 .attr('id', function (d) { return d.id; });
497
498 entering.each(function (d) {
499 var el = d3.select(this),
500 rect = el.append('rect'),
501 text = el.append('text').text(d.num);
502
503 rect.attr(rectAroundText(el));
504 text.attr('dy', linkLabelOffset);
Simon Hunt969b3c92015-02-25 18:11:31 -0800505 el.attr('transform', sus.translate(d.x, d.y));
Simon Hunt1a5301e2015-02-25 15:31:25 -0800506 });
507 }
508
Bri Prebilic Cole80401762015-07-16 11:36:18 -0700509 function labelPoint(linkPos) {
510 var lengthUpLine = 1 / 3,
511 dx = linkPos.x2 - linkPos.x1,
512 dy = linkPos.y2 - linkPos.y1,
513 movedX = dx * lengthUpLine,
514 movedY = dy * lengthUpLine;
515
516 return {
517 x: movedX,
518 y: movedY
519 };
520 }
521
522 function calcGroupPos(linkPos) {
523 var moved = labelPoint(linkPos);
524 return sus.translate(linkPos.x1 + moved.x, linkPos.y1 + moved.y);
525 }
526
527 // calculates where on the link that the hash line for 5+ label appears
528 function hashAttrs(linkPos) {
529 var hashLength = 25,
530 halfLength = hashLength / 2,
531 dx = linkPos.x2 - linkPos.x1,
532 dy = linkPos.y2 - linkPos.y1,
533 length = Math.sqrt((dx * dx) + (dy * dy)),
534 moveAmtX = (dx / length) * halfLength,
535 moveAmtY = (dy / length) * halfLength,
536 mid = labelPoint(linkPos),
537 angle = Math.atan(dy / dx) + 45;
538
539 return {
540 x1: mid.x - moveAmtX,
541 y1: mid.y - moveAmtY,
542 x2: mid.x + moveAmtX,
543 y2: mid.y + moveAmtY,
544 stroke: api.linkConfig()[ts.theme()].baseColor,
545 transform: 'rotate(' + angle + ',' + mid.x + ',' + mid.y + ')'
546 };
547 }
548
549 function textLabelPos(linkPos) {
550 var point = labelPoint(linkPos),
551 dist = 20;
552 return {
553 x: point.x + dist,
554 y: point.y + dist
555 };
556 }
557
558 function applyNumLinkLabels(data, lblsG) {
559 var labels = lblsG.selectAll('g.numLinkLabel')
560 .data(data, function (d) { return 'pair-' + d.id; }),
561 entering;
562
563 // update existing labels
564 labels.each(function (d) {
565 var el = d3.select(this);
566
567 el.attr({
568 transform: function (d) { return calcGroupPos(d.linkCoords); }
569 });
570 el.select('line')
571 .attr(hashAttrs(d.linkCoords));
572 el.select('text')
573 .attr(textLabelPos(d.linkCoords))
574 .text(d.num);
575 });
576
577 // add new labels
578 entering = labels
579 .enter()
580 .append('g')
581 .attr({
582 transform: function (d) { return calcGroupPos(d.linkCoords); },
583 id: function (d) { return 'pair-' + d.id; }
584 })
585 .classed('numLinkLabel', true);
586
587 entering.each(function (d) {
588 var el = d3.select(this);
589
590 el.append('line')
591 .classed('numLinkHash', true)
592 .attr(hashAttrs(d.linkCoords));
593 el.append('text')
594 .classed('numLinkText', true)
595 .attr(textLabelPos(d.linkCoords))
596 .text(d.num);
597 });
598
599 // remove old labels
600 labels.exit().remove();
601 }
602
Simon Hunta4242de2015-02-24 17:11:55 -0800603 // ==========================
604 // Module definition
605
606 angular.module('ovTopo')
607 .factory('TopoD3Service',
608 ['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService',
Thomas Vachuska0af26912016-03-21 21:37:30 -0700609 'PrefsService', 'TopoToolbarService',
Simon Hunta4242de2015-02-24 17:11:55 -0800610
Thomas Vachuska0af26912016-03-21 21:37:30 -0700611 function (_$log_, _fs_, _sus_, _is_, _ts_, _ps_, _ttbs_) {
Simon Hunta4242de2015-02-24 17:11:55 -0800612 $log = _$log_;
613 fs = _fs_;
614 sus = _sus_;
615 is = _is_;
616 ts = _ts_;
Thomas Vachuska0af26912016-03-21 21:37:30 -0700617 ps = _ps_;
618 ttbs = _ttbs_;
Simon Hunta4242de2015-02-24 17:11:55 -0800619
620 icfg = is.iconConfig();
621
622 function initD3(_api_) {
623 api = _api_;
624 }
625
626 function destroyD3() { }
627
628 return {
629 initD3: initD3,
630 destroyD3: destroyD3,
631
632 incDevLabIndex: incDevLabIndex,
Thomas Vachuska0af26912016-03-21 21:37:30 -0700633 setDevLabIndex: setDevLabIndex,
Simon Hunta4242de2015-02-24 17:11:55 -0800634 adjustRectToFitText: adjustRectToFitText,
635 hostLabel: hostLabel,
636 deviceLabel: deviceLabel,
637 trimLabel: trimLabel,
638
Simon Hunt5674db92015-10-22 16:12:48 -0700639 updateDeviceLabel: updateDeviceRendering,
Simon Hunta4242de2015-02-24 17:11:55 -0800640 updateHostLabel: updateHostLabel,
641 updateDeviceColors: updateDeviceColors,
642
643 deviceExisting: deviceExisting,
644 hostExisting: hostExisting,
645 deviceEnter: deviceEnter,
646 hostEnter: hostEnter,
647 hostExit: hostExit,
648 deviceExit: deviceExit,
649
Simon Hunta4242de2015-02-24 17:11:55 -0800650 linkEntering: linkEntering,
651 applyLinkLabels: applyLinkLabels,
Simon Hunt1a5301e2015-02-25 15:31:25 -0800652 transformLabel: transformLabel,
Bri Prebilic Cole80401762015-07-16 11:36:18 -0700653 applyPortLabels: applyPortLabels,
654 applyNumLinkLabels: applyNumLinkLabels
Simon Hunta4242de2015-02-24 17:11:55 -0800655 };
656 }]);
657}());