blob: 278fc2534a22ec81daa31f10bdf13374d2a26c7d [file] [log] [blame]
Simon Hunta4242de2015-02-24 17:11:55 -08001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2015-present Open Networking Laboratory
Simon Hunta4242de2015-02-24 17:11:55 -08003 *
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
Simon Huntf44d7262016-06-14 14:46:56 -070042 var devIconDim = 36;
43 var labelPad = 4;
44
45 var badgeConfig = {
Simon Hunt5674db92015-10-22 16:12:48 -070046 radius: 12,
Simon Hunt004fc2c2015-10-23 11:55:58 -070047 yoff: 5,
48 gdelta: 10
Simon Huntf44d7262016-06-14 14:46:56 -070049 };
50
51 // TODO: remove dependence on this
52 var icfg;
Simon Hunta4242de2015-02-24 17:11:55 -080053
Simon Hunt5674db92015-10-22 16:12:48 -070054 var status = {
55 i: 'badgeInfo',
56 w: 'badgeWarn',
57 e: 'badgeError'
58 };
59
Simon Hunt1eee51d2016-02-26 19:12:13 -080060 // NOTE: this type of hack should go away once we have implemented
61 // the server-side UiModel code.
62 // {virtual -> cord} is for the E-CORD demo at ONS 2016
63 var remappedDeviceTypes = {
64 virtual: 'cord'
65 };
66
67 function mapDeviceTypeToGlyph(type) {
68 return remappedDeviceTypes[type] || type || 'unknown';
69 }
70
Simon Hunt5674db92015-10-22 16:12:48 -070071 function badgeStatus(badge) {
72 return status[badge.status] || status.i;
73 }
74
Simon Hunta4242de2015-02-24 17:11:55 -080075 // internal state
76 var deviceLabelIndex = 0,
77 hostLabelIndex = 0;
78
Simon Hunta4242de2015-02-24 17:11:55 -080079 // note: these are the device icon colors without affinity
80 var dColTheme = {
81 light: {
Simon Huntf44d7262016-06-14 14:46:56 -070082 online: '#444444',
83 offline: '#cccccc'
Simon Hunta4242de2015-02-24 17:11:55 -080084 },
85 dark: {
Simon Huntf44d7262016-06-14 14:46:56 -070086 // TODO: theme
87 online: '#444444',
88 offline: '#cccccc'
Simon Hunta4242de2015-02-24 17:11:55 -080089 }
90 };
91
Simon Huntf44d7262016-06-14 14:46:56 -070092 function devGlyphColor(d) {
93 var o = d.online,
94 id = d.master,
95 otag = o ? 'online' : 'offline';
96 return o ? sus.cat7().getColor(id, 0, ts.theme())
97 : dColTheme[ts.theme()][otag];
Simon Hunta4242de2015-02-24 17:11:55 -080098 }
99
100 function setDeviceColor(d) {
Simon Huntf44d7262016-06-14 14:46:56 -0700101 d.el.select('use')
102 .style('fill', devGlyphColor(d));
Simon Hunta4242de2015-02-24 17:11:55 -0800103 }
104
Simon Hunta4242de2015-02-24 17:11:55 -0800105 function incDevLabIndex() {
Thomas Vachuska0af26912016-03-21 21:37:30 -0700106 setDevLabIndex(deviceLabelIndex+1);
Bri Prebilic Cole9cf1a8d2015-04-21 13:15:29 -0700107 switch(deviceLabelIndex) {
108 case 0: return 'Hide device labels';
109 case 1: return 'Show friendly device labels';
110 case 2: return 'Show device ID labels';
111 }
Simon Hunta4242de2015-02-24 17:11:55 -0800112 }
113
Thomas Vachuska0af26912016-03-21 21:37:30 -0700114 function setDevLabIndex(mode) {
115 deviceLabelIndex = mode % 3;
116 var p = ps.getPrefs('topo_prefs', ttbs.defaultPrefs);
117 p.dlbls = deviceLabelIndex;
118 ps.setPrefs('topo_prefs', p);
119 }
120
Simon Hunta4242de2015-02-24 17:11:55 -0800121 function hostLabel(d) {
122 var idx = (hostLabelIndex < d.labels.length) ? hostLabelIndex : 0;
123 return d.labels[idx];
124 }
Simon Huntf44d7262016-06-14 14:46:56 -0700125
Simon Hunta4242de2015-02-24 17:11:55 -0800126 function deviceLabel(d) {
127 var idx = (deviceLabelIndex < d.labels.length) ? deviceLabelIndex : 0;
128 return d.labels[idx];
129 }
Simon Huntf44d7262016-06-14 14:46:56 -0700130
Simon Hunta4242de2015-02-24 17:11:55 -0800131 function trimLabel(label) {
132 return (label && label.trim()) || '';
133 }
134
Simon Huntf44d7262016-06-14 14:46:56 -0700135 function computeLabelWidth(n) {
136 var text = n.select('text'),
137 box = text.node().getBBox();
138 return box.width + labelPad * 2;
139 }
140
141 function iconBox(dim, labelWidth) {
Simon Hunta4242de2015-02-24 17:11:55 -0800142 return {
Simon Huntf44d7262016-06-14 14:46:56 -0700143 x: -dim/2,
144 y: -dim/2,
145 width: dim + labelWidth,
146 height: dim
147 }
Simon Hunta4242de2015-02-24 17:11:55 -0800148 }
149
Simon Hunt5674db92015-10-22 16:12:48 -0700150 function updateDeviceRendering(d) {
Simon Huntf44d7262016-06-14 14:46:56 -0700151 var node = d.el,
152 bdg = d.badge,
153 label = trimLabel(deviceLabel(d)),
154 labelWidth;
Simon Hunta4242de2015-02-24 17:11:55 -0800155
Simon Huntf44d7262016-06-14 14:46:56 -0700156 node.select('text').text(label);
157 labelWidth = label ? computeLabelWidth(node) : 0;
Simon Hunta4242de2015-02-24 17:11:55 -0800158
159 node.select('rect')
160 .transition()
Simon Huntf44d7262016-06-14 14:46:56 -0700161 .attr(iconBox(devIconDim, labelWidth));
Simon Hunta4242de2015-02-24 17:11:55 -0800162
Simon Huntf44d7262016-06-14 14:46:56 -0700163 // TODO: verify badge placement
Simon Hunt5674db92015-10-22 16:12:48 -0700164 if (bdg) {
Simon Huntf44d7262016-06-14 14:46:56 -0700165 renderBadge(node, bdg, { dx: devIconDim, dy: 0 });
Simon Hunte9343f32015-10-21 18:07:46 -0700166 }
167 }
168
Andrea Campanella52125412015-12-03 14:50:40 -0800169 function updateHostRendering(d) {
170 var node = d.el,
Simon Huntc2bfe332015-12-04 11:01:24 -0800171 bdg = d.badge;
Andrea Campanella52125412015-12-03 14:50:40 -0800172
173 updateHostLabel(d);
Andrea Campanella52125412015-12-03 14:50:40 -0800174
Andrea Campanella52125412015-12-03 14:50:40 -0800175 if (bdg) {
Simon Huntc2bfe332015-12-04 11:01:24 -0800176 renderBadge(node, bdg, icfg.host.badge);
177 }
178 }
Andrea Campanella52125412015-12-03 14:50:40 -0800179
Simon Huntc2bfe332015-12-04 11:01:24 -0800180 function renderBadge(node, bdg, boff) {
181 var bsel,
182 bcr = badgeConfig.radius,
183 bcgd = badgeConfig.gdelta;
Andrea Campanella52125412015-12-03 14:50:40 -0800184
Simon Huntc2bfe332015-12-04 11:01:24 -0800185 node.select('g.badge').remove();
Andrea Campanella52125412015-12-03 14:50:40 -0800186
Simon Huntc2bfe332015-12-04 11:01:24 -0800187 bsel = node.append('g')
188 .classed('badge', true)
189 .classed(badgeStatus(bdg), true)
190 .attr('transform', sus.translate(boff.dx, boff.dy));
Andrea Campanella52125412015-12-03 14:50:40 -0800191
Simon Huntc2bfe332015-12-04 11:01:24 -0800192 bsel.append('circle')
193 .attr('r', bcr);
194
195 if (bdg.txt) {
196 bsel.append('text')
197 .attr('dy', badgeConfig.yoff)
198 .attr('text-anchor', 'middle')
199 .text(bdg.txt);
200 } else if (bdg.gid) {
201 bsel.append('use')
202 .attr({
203 width: bcgd * 2,
204 height: bcgd * 2,
205 transform: sus.translate(-bcgd, -bcgd),
206 'xlink:href': '#' + bdg.gid
207 });
Andrea Campanella52125412015-12-03 14:50:40 -0800208 }
209 }
210
Simon Hunta4242de2015-02-24 17:11:55 -0800211 function updateHostLabel(d) {
212 var label = trimLabel(hostLabel(d));
213 d.el.select('text').text(label);
214 }
215
216 function updateDeviceColors(d) {
217 if (d) {
218 setDeviceColor(d);
219 } else {
220 api.node().filter('.device').each(function (d) {
221 setDeviceColor(d);
222 });
223 }
224 }
225
226
227 // ==========================
228 // updateNodes - subfunctions
229
230 function deviceExisting(d) {
231 var node = d.el;
232 node.classed('online', d.online);
Simon Hunt5674db92015-10-22 16:12:48 -0700233 updateDeviceRendering(d);
Simon Hunta4242de2015-02-24 17:11:55 -0800234 api.posNode(d, true);
235 }
236
237 function hostExisting(d) {
Andrea Campanella52125412015-12-03 14:50:40 -0800238 updateHostRendering(d);
Simon Hunta4242de2015-02-24 17:11:55 -0800239 api.posNode(d, true);
240 }
241
242 function deviceEnter(d) {
243 var node = d3.select(this),
Simon Hunt1eee51d2016-02-26 19:12:13 -0800244 glyphId = mapDeviceTypeToGlyph(d.type),
Simon Hunta4242de2015-02-24 17:11:55 -0800245 label = trimLabel(deviceLabel(d)),
Simon Huntf44d7262016-06-14 14:46:56 -0700246 xlate = -devIconDim/2,
247 rect, text, glyph, labelWidth;
Simon Hunta4242de2015-02-24 17:11:55 -0800248
249 d.el = node;
250
Simon Huntf44d7262016-06-14 14:46:56 -0700251 rect = node.append('rect');
Simon Hunta4242de2015-02-24 17:11:55 -0800252
Simon Huntf44d7262016-06-14 14:46:56 -0700253 text = node.append('text').text(label)
254 .attr('text-anchor', 'left')
255 .attr('y', '0.3em')
256 .attr('x', devIconDim / 2 + labelPad);
Simon Hunta4242de2015-02-24 17:11:55 -0800257
Simon Huntf44d7262016-06-14 14:46:56 -0700258 glyph = is.addDeviceIcon(node, glyphId, devIconDim);
Simon Hunta4242de2015-02-24 17:11:55 -0800259
Simon Huntf44d7262016-06-14 14:46:56 -0700260 labelWidth = label ? computeLabelWidth(node) : 0;
261
262 rect.attr(iconBox(devIconDim, labelWidth));
263 glyph.attr(iconBox(devIconDim, 0));
264
265 node.attr('transform', sus.translate(xlate, xlate));
Simon Hunta4242de2015-02-24 17:11:55 -0800266 }
267
268 function hostEnter(d) {
269 var node = d3.select(this),
270 gid = d.type || 'unknown',
271 rad = icfg.host.radius,
272 r = d.type ? rad.withGlyph : rad.noGlyph,
273 textDy = r + 10;
274
275 d.el = node;
276 sus.visible(node, api.showHosts());
277
278 is.addHostIcon(node, r, gid);
279
280 node.append('text')
281 .text(hostLabel)
282 .attr('dy', textDy)
283 .attr('text-anchor', 'middle');
284 }
285
286 function hostExit(d) {
287 var node = d.el;
288 node.select('use')
289 .style('opacity', 0.5)
290 .transition()
291 .duration(800)
292 .style('opacity', 0);
293
294 node.select('text')
295 .style('opacity', 0.5)
296 .transition()
297 .duration(800)
298 .style('opacity', 0);
299
300 node.select('circle')
301 .style('stroke-fill', '#555')
302 .style('fill', '#888')
303 .style('opacity', 0.5)
304 .transition()
305 .duration(1500)
306 .attr('r', 0);
307 }
308
309 function deviceExit(d) {
310 var node = d.el;
311 node.select('use')
312 .style('opacity', 0.5)
313 .transition()
314 .duration(800)
315 .style('opacity', 0);
316
317 node.selectAll('rect')
318 .style('stroke-fill', '#555')
319 .style('fill', '#888')
320 .style('opacity', 0.5);
321 }
322
323
324 // ==========================
325 // updateLinks - subfunctions
326
Simon Hunta4242de2015-02-24 17:11:55 -0800327 function linkEntering(d) {
328 var link = d3.select(this);
329 d.el = link;
330 api.restyleLinkElement(d);
331 if (d.type() === 'hostLink') {
332 sus.visible(link, api.showHosts());
333 }
334 }
335
336 var linkLabelOffset = '0.3em';
337
338 function applyLinkLabels() {
339 var entering;
340
341 api.updateLinkLabelModel();
342
343 // for elements already existing, we need to update the text
344 // and adjust the rectangle size to fit
345 api.linkLabel().each(function (d) {
346 var el = d3.select(this),
347 rect = el.select('rect'),
348 text = el.select('text');
349 text.text(d.label);
350 rect.attr(rectAroundText(el));
351 });
352
353 entering = api.linkLabel().enter().append('g')
354 .classed('linkLabel', true)
355 .attr('id', function (d) { return d.id; });
356
357 entering.each(function (d) {
358 var el = d3.select(this),
359 rect,
Bri Prebilic Cole038aedd2015-07-13 15:25:16 -0700360 text;
Simon Hunta4242de2015-02-24 17:11:55 -0800361
362 if (d.ldata.type() === 'hostLink') {
363 el.classed('hostLinkLabel', true);
364 sus.visible(el, api.showHosts());
365 }
366
367 d.el = el;
368 rect = el.append('rect');
369 text = el.append('text').text(d.label);
370 rect.attr(rectAroundText(el));
371 text.attr('dy', linkLabelOffset);
372
Bri Prebilic Cole038aedd2015-07-13 15:25:16 -0700373 el.attr('transform', transformLabel(d.ldata.position));
Simon Hunta4242de2015-02-24 17:11:55 -0800374 });
375
376 // Remove any labels that are no longer required.
377 api.linkLabel().exit().remove();
378 }
379
380 function rectAroundText(el) {
381 var text = el.select('text'),
382 box = text.node().getBBox();
383
384 // translate the bbox so that it is centered on [x,y]
385 box.x = -box.width / 2;
386 box.y = -box.height / 2;
387
388 // add padding
389 box.x -= 1;
390 box.width += 2;
391 return box;
392 }
393
394 function transformLabel(p) {
395 var dx = p.x2 - p.x1,
396 dy = p.y2 - p.y1,
397 xMid = dx/2 + p.x1,
398 yMid = dy/2 + p.y1;
399 return sus.translate(xMid, yMid);
400 }
401
Simon Hunt1a5301e2015-02-25 15:31:25 -0800402 function applyPortLabels(data, portLabelG) {
403 var entering = portLabelG.selectAll('.portLabel')
404 .data(data).enter().append('g')
405 .classed('portLabel', true)
406 .attr('id', function (d) { return d.id; });
407
408 entering.each(function (d) {
409 var el = d3.select(this),
410 rect = el.append('rect'),
411 text = el.append('text').text(d.num);
412
413 rect.attr(rectAroundText(el));
414 text.attr('dy', linkLabelOffset);
Simon Hunt969b3c92015-02-25 18:11:31 -0800415 el.attr('transform', sus.translate(d.x, d.y));
Simon Hunt1a5301e2015-02-25 15:31:25 -0800416 });
417 }
418
Bri Prebilic Cole80401762015-07-16 11:36:18 -0700419 function labelPoint(linkPos) {
420 var lengthUpLine = 1 / 3,
421 dx = linkPos.x2 - linkPos.x1,
422 dy = linkPos.y2 - linkPos.y1,
423 movedX = dx * lengthUpLine,
424 movedY = dy * lengthUpLine;
425
426 return {
427 x: movedX,
428 y: movedY
429 };
430 }
431
432 function calcGroupPos(linkPos) {
433 var moved = labelPoint(linkPos);
434 return sus.translate(linkPos.x1 + moved.x, linkPos.y1 + moved.y);
435 }
436
437 // calculates where on the link that the hash line for 5+ label appears
438 function hashAttrs(linkPos) {
439 var hashLength = 25,
440 halfLength = hashLength / 2,
441 dx = linkPos.x2 - linkPos.x1,
442 dy = linkPos.y2 - linkPos.y1,
443 length = Math.sqrt((dx * dx) + (dy * dy)),
444 moveAmtX = (dx / length) * halfLength,
445 moveAmtY = (dy / length) * halfLength,
446 mid = labelPoint(linkPos),
447 angle = Math.atan(dy / dx) + 45;
448
449 return {
450 x1: mid.x - moveAmtX,
451 y1: mid.y - moveAmtY,
452 x2: mid.x + moveAmtX,
453 y2: mid.y + moveAmtY,
454 stroke: api.linkConfig()[ts.theme()].baseColor,
455 transform: 'rotate(' + angle + ',' + mid.x + ',' + mid.y + ')'
456 };
457 }
458
459 function textLabelPos(linkPos) {
460 var point = labelPoint(linkPos),
461 dist = 20;
462 return {
463 x: point.x + dist,
464 y: point.y + dist
465 };
466 }
467
468 function applyNumLinkLabels(data, lblsG) {
469 var labels = lblsG.selectAll('g.numLinkLabel')
470 .data(data, function (d) { return 'pair-' + d.id; }),
471 entering;
472
473 // update existing labels
474 labels.each(function (d) {
475 var el = d3.select(this);
476
477 el.attr({
478 transform: function (d) { return calcGroupPos(d.linkCoords); }
479 });
480 el.select('line')
481 .attr(hashAttrs(d.linkCoords));
482 el.select('text')
483 .attr(textLabelPos(d.linkCoords))
484 .text(d.num);
485 });
486
487 // add new labels
488 entering = labels
489 .enter()
490 .append('g')
491 .attr({
492 transform: function (d) { return calcGroupPos(d.linkCoords); },
493 id: function (d) { return 'pair-' + d.id; }
494 })
495 .classed('numLinkLabel', true);
496
497 entering.each(function (d) {
498 var el = d3.select(this);
499
500 el.append('line')
501 .classed('numLinkHash', true)
502 .attr(hashAttrs(d.linkCoords));
503 el.append('text')
504 .classed('numLinkText', true)
505 .attr(textLabelPos(d.linkCoords))
506 .text(d.num);
507 });
508
509 // remove old labels
510 labels.exit().remove();
511 }
512
Simon Hunta4242de2015-02-24 17:11:55 -0800513 // ==========================
514 // Module definition
515
516 angular.module('ovTopo')
517 .factory('TopoD3Service',
518 ['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService',
Thomas Vachuska0af26912016-03-21 21:37:30 -0700519 'PrefsService', 'TopoToolbarService',
Simon Hunta4242de2015-02-24 17:11:55 -0800520
Thomas Vachuska0af26912016-03-21 21:37:30 -0700521 function (_$log_, _fs_, _sus_, _is_, _ts_, _ps_, _ttbs_) {
Simon Hunta4242de2015-02-24 17:11:55 -0800522 $log = _$log_;
523 fs = _fs_;
524 sus = _sus_;
525 is = _is_;
526 ts = _ts_;
Thomas Vachuska0af26912016-03-21 21:37:30 -0700527 ps = _ps_;
528 ttbs = _ttbs_;
Simon Hunta4242de2015-02-24 17:11:55 -0800529
530 icfg = is.iconConfig();
531
532 function initD3(_api_) {
533 api = _api_;
534 }
535
536 function destroyD3() { }
537
538 return {
539 initD3: initD3,
540 destroyD3: destroyD3,
541
542 incDevLabIndex: incDevLabIndex,
Thomas Vachuska0af26912016-03-21 21:37:30 -0700543 setDevLabIndex: setDevLabIndex,
Simon Hunta4242de2015-02-24 17:11:55 -0800544 hostLabel: hostLabel,
545 deviceLabel: deviceLabel,
546 trimLabel: trimLabel,
547
Simon Hunt5674db92015-10-22 16:12:48 -0700548 updateDeviceLabel: updateDeviceRendering,
Simon Hunta4242de2015-02-24 17:11:55 -0800549 updateHostLabel: updateHostLabel,
550 updateDeviceColors: updateDeviceColors,
551
552 deviceExisting: deviceExisting,
553 hostExisting: hostExisting,
554 deviceEnter: deviceEnter,
555 hostEnter: hostEnter,
556 hostExit: hostExit,
557 deviceExit: deviceExit,
558
Simon Hunta4242de2015-02-24 17:11:55 -0800559 linkEntering: linkEntering,
560 applyLinkLabels: applyLinkLabels,
Simon Hunt1a5301e2015-02-25 15:31:25 -0800561 transformLabel: transformLabel,
Bri Prebilic Cole80401762015-07-16 11:36:18 -0700562 applyPortLabels: applyPortLabels,
563 applyNumLinkLabels: applyNumLinkLabels
Simon Hunta4242de2015-02-24 17:11:55 -0800564 };
565 }]);
566}());