Steven Burrows
Committed by Gerrit Code Review

Topo2Link - Fixed width of rectangle and centered text

Topo2Layout/Link - Added port number on link hover
Topo2Layout/Select - Added Drag functionality
Topo2SubRegion - Added onClick event to node
Topo2Device - Added Color Theme
TopoForce - Removed console.log

Change-Id: Icd85d92c8f3c5f96cb896068fe9375c250717f5f
...@@ -103,7 +103,6 @@ ...@@ -103,7 +103,6 @@
103 // === EVENT HANDLERS 103 // === EVENT HANDLERS
104 104
105 function addDevice(data) { 105 function addDevice(data) {
106 - console.log(data);
107 var id = data.id, 106 var id = data.id,
108 d; 107 d;
109 108
......
...@@ -69,7 +69,7 @@ ...@@ -69,7 +69,7 @@
69 ps.setPrefs('topo_zoom', {tx:tr[0], ty:tr[1], sc:sc}); 69 ps.setPrefs('topo_zoom', {tx:tr[0], ty:tr[1], sc:sc});
70 70
71 // keep the map lines constant width while zooming 71 // keep the map lines constant width while zooming
72 -// mapG.style('stroke-width', (2.0 / sc) + 'px'); 72 + mapG.style('stroke-width', (2.0 / sc) + 'px');
73 } 73 }
74 74
75 function setUpZoom() { 75 function setUpZoom() {
...@@ -108,8 +108,8 @@ ...@@ -108,8 +108,8 @@
108 // provides function calls back into this space 108 // provides function calls back into this space
109 // showNoDevs: showNoDevs, 109 // showNoDevs: showNoDevs,
110 // projection: function () { return projection; }, 110 // projection: function () { return projection; },
111 - // zoomLayer: function () { return zoomLayer; }, 111 + zoomLayer: function () { return zoomLayer; },
112 - // zoomer: function () { return zoomer; }, 112 + zoomer: function () { return zoomer; },
113 // opacifyMap: opacifyMap, 113 // opacifyMap: opacifyMap,
114 // topoStartDone: topoStartDone 114 // topoStartDone: topoStartDone
115 }; 115 };
......
...@@ -30,6 +30,7 @@ ...@@ -30,6 +30,7 @@
30 30
31 // configuration 31 // configuration
32 var devIconDim = 36, 32 var devIconDim = 36,
33 + labelPad = 10,
33 hostRadius = 14, 34 hostRadius = 14,
34 badgeConfig = { 35 badgeConfig = {
35 radius: 12, 36 radius: 12,
...@@ -43,7 +44,8 @@ ...@@ -43,7 +44,8 @@
43 i: 'badgeInfo', 44 i: 'badgeInfo',
44 w: 'badgeWarn', 45 w: 'badgeWarn',
45 e: 'badgeError' 46 e: 'badgeError'
46 - }; 47 + },
48 + deviceLabelIndex = 0;
47 49
48 function createDeviceCollection(data, region) { 50 function createDeviceCollection(data, region) {
49 51
...@@ -81,13 +83,25 @@ ...@@ -81,13 +83,25 @@
81 } 83 }
82 } 84 }
83 85
84 - function deviceGlyphColor(d) { 86 + // note: these are the device icon colors without affinity (no master)
87 + var dColTheme = {
88 + light: {
89 + online: '#444444',
90 + offline: '#cccccc'
91 + },
92 + dark: {
93 + // TODO: theme
94 + online: '#444444',
95 + offline: '#cccccc'
96 + }
97 + };
85 98
99 + function deviceGlyphColor(d) {
86 var o = this.node.online, 100 var o = this.node.online,
87 id = this.node.master, // TODO: This should be from node.master 101 id = this.node.master, // TODO: This should be from node.master
88 otag = o ? 'online' : 'offline'; 102 otag = o ? 'online' : 'offline';
89 return o ? sus.cat7().getColor(id, 0, ts.theme()) 103 return o ? sus.cat7().getColor(id, 0, ts.theme())
90 - : '#ff0000'; 104 + : dColTheme[ts.theme()][otag];
91 } 105 }
92 106
93 function setDeviceColor() { 107 function setDeviceColor() {
......
...@@ -22,12 +22,12 @@ ...@@ -22,12 +22,12 @@
22 (function () { 22 (function () {
23 'use strict'; 23 'use strict';
24 24
25 - var $log, sus, t2rs, t2d3, t2vs; 25 + var $log, sus, t2rs, t2d3, t2vs, t2ss;
26 26
27 - var linkG, linkLabelG, numLinkLabelsG, nodeG, portLabelG; 27 + var uplink, linkG, linkLabelG, numLinkLabelsG, nodeG, portLabelG;
28 var link, linkLabel, node; 28 var link, linkLabel, node;
29 29
30 - var nodes, links; 30 + var nodes, links, highlightedLink;
31 31
32 var force; 32 var force;
33 33
...@@ -124,7 +124,7 @@ ...@@ -124,7 +124,7 @@
124 function init(_svg_, forceG, _uplink_, _dim_, opts) { 124 function init(_svg_, forceG, _uplink_, _dim_, opts) {
125 125
126 $log.debug("Initialising Topology Layout"); 126 $log.debug("Initialising Topology Layout");
127 - 127 + uplink = _uplink_;
128 settings = angular.extend({}, defaultSettings, opts); 128 settings = angular.extend({}, defaultSettings, opts);
129 129
130 linkG = forceG.append('g').attr('id', 'topo-links'); 130 linkG = forceG.append('g').attr('id', 'topo-links');
...@@ -147,6 +147,35 @@ ...@@ -147,6 +147,35 @@
147 .linkDistance(settings.linkDistance._def_) 147 .linkDistance(settings.linkDistance._def_)
148 .linkStrength(settings.linkStrength._def_) 148 .linkStrength(settings.linkStrength._def_)
149 .on('tick', tick); 149 .on('tick', tick);
150 +
151 + drag = sus.createDragBehavior(force,
152 + t2ss.selectObject, atDragEnd, dragEnabled, clickEnabled);
153 +
154 + _svg_.on('mousemove', mouseMoveHandler)
155 + }
156 +
157 + function zoomingOrPanning(ev) {
158 + return ev.metaKey || ev.altKey;
159 + }
160 +
161 + function atDragEnd(d) {
162 + // once we've finished moving, pin the node in position
163 + d.fixed = true;
164 + d3.select(this).classed('fixed', true);
165 + // TODO: sendUpdateMeta(d);
166 + t2ss.clickConsumed(true);
167 + }
168 +
169 + // predicate that indicates when dragging is active
170 + function dragEnabled() {
171 + var ev = d3.event.sourceEvent;
172 + // nodeLock means we aren't allowing nodes to be dragged...
173 + return !nodeLock && !zoomingOrPanning(ev);
174 + }
175 +
176 + // predicate that indicates when clicking is active
177 + function clickEnabled() {
178 + return true;
150 } 179 }
151 180
152 function tick() { 181 function tick() {
...@@ -188,6 +217,7 @@ ...@@ -188,6 +217,7 @@
188 }, 217 },
189 opacity: 0 218 opacity: 0
190 }) 219 })
220 + .call(drag)
191 // .on('mouseover', tss.nodeMouseOver) 221 // .on('mouseover', tss.nodeMouseOver)
192 // .on('mouseout', tss.nodeMouseOut) 222 // .on('mouseout', tss.nodeMouseOut)
193 .transition() 223 .transition()
...@@ -308,18 +338,140 @@ ...@@ -308,18 +338,140 @@
308 force.start(); 338 force.start();
309 } 339 }
310 340
341 + // Mouse Events
342 + function mouseMoveHandler() {
343 + var mp = getLogicalMousePosition(this),
344 + link = computeNearestLink(mp);
345 +
346 +
347 + if (highlightedLink) {
348 + highlightedLink.unenhance();
349 + highlightedLink = null;
350 + }
351 +
352 + if (link) {
353 + link.enhance();
354 + highlightedLink = link;
355 + }
356 + }
357 +
358 + // ======== ALGORITHM TO FIND LINK CLOSEST TO MOUSE ========
359 +
360 + function getLogicalMousePosition(container) {
361 + var m = d3.mouse(container),
362 + sc = uplink.zoomer().scale(),
363 + tr = uplink.zoomer().translate(),
364 + mx = (m[0] - tr[0]) / sc,
365 + my = (m[1] - tr[1]) / sc;
366 + return {x: mx, y: my};
367 + }
368 +
369 +
370 + function sq(x) { return x * x; }
371 +
372 + function mdist(p, m) {
373 + return Math.sqrt(sq(p.x - m.x) + sq(p.y - m.y));
374 + }
375 +
376 + function prox(dist) {
377 + return dist / uplink.zoomer().scale();
378 + }
379 +
380 + function computeNearestNode(mouse) {
381 + var proximity = prox(30),
382 + nearest = null,
383 + minDist,
384 + regionNodes = t2rs.regionNodes();
385 +
386 + if (regionNodes.length) {
387 + minDist = proximity * 2;
388 +
389 + regionNodes.forEach(function (d) {
390 + var dist;
391 +
392 + if (!api.showHosts() && d.class === 'host') {
393 + return; // skip hidden hosts
394 + }
395 +
396 + dist = mdist({x: d.x, y: d.y}, mouse);
397 + if (dist < minDist && dist < proximity) {
398 + minDist = dist;
399 + nearest = d;
400 + }
401 + });
402 + }
403 + return nearest;
404 + }
405 +
406 +
407 + function computeNearestLink(mouse) {
408 + var proximity = prox(30),
409 + nearest = null,
410 + minDist,
411 + regionLinks = t2rs.regionLinks();
412 +
413 + function pdrop(line, mouse) {
414 +
415 + var x1 = line.x1,
416 + y1 = line.y1,
417 + x2 = line.x2,
418 + y2 = line.y2,
419 + x3 = mouse.x,
420 + y3 = mouse.y,
421 + k = ((y2-y1) * (x3-x1) - (x2-x1) * (y3-y1)) /
422 + (sq(y2-y1) + sq(x2-x1)),
423 + x4 = x3 - k * (y2-y1),
424 + y4 = y3 + k * (x2-x1);
425 + return {x:x4, y:y4};
426 + }
427 +
428 + function lineHit(line, p, m) {
429 + if (p.x < line.x1 && p.x < line.x2) return false;
430 + if (p.x > line.x1 && p.x > line.x2) return false;
431 + if (p.y < line.y1 && p.y < line.y2) return false;
432 + if (p.y > line.y1 && p.y > line.y2) return false;
433 + // line intersects, but are we close enough?
434 + return mdist(p, m) <= proximity;
435 + }
436 +
437 + if (regionLinks.length) {
438 + minDist = proximity * 2;
439 +
440 + regionLinks.forEach(function (d) {
441 + // if (!api.showHosts() && d.type() === 'hostLink') {
442 + // return; // skip hidden host links
443 + // }
444 +
445 + var line = d.get('position'),
446 + point = pdrop(line, mouse),
447 + hit = lineHit(line, point, mouse),
448 + dist;
449 +
450 + if (hit) {
451 + dist = mdist(point, mouse);
452 + if (dist < minDist) {
453 + minDist = dist;
454 + nearest = d;
455 + }
456 + }
457 + });
458 + }
459 + return nearest;
460 + }
461 +
311 angular.module('ovTopo2') 462 angular.module('ovTopo2')
312 .factory('Topo2LayoutService', 463 .factory('Topo2LayoutService',
313 [ 464 [
314 '$log', 'SvgUtilService', 'Topo2RegionService', 465 '$log', 'SvgUtilService', 'Topo2RegionService',
315 - 'Topo2D3Service', 'Topo2ViewService', 466 + 'Topo2D3Service', 'Topo2ViewService', 'Topo2SelectService',
316 467
317 - function (_$log_, _sus_, _t2rs_, _t2d3_, _t2vs_) { 468 + function (_$log_, _sus_, _t2rs_, _t2d3_, _t2vs_, _t2ss_) {
318 469
319 $log = _$log_; 470 $log = _$log_;
320 t2rs = _t2rs_; 471 t2rs = _t2rs_;
321 t2d3 = _t2d3_; 472 t2d3 = _t2d3_;
322 t2vs = _t2vs_; 473 t2vs = _t2vs_;
474 + t2ss = _t2ss_;
323 sus = _sus_; 475 sus = _sus_;
324 476
325 return { 477 return {
......
...@@ -23,7 +23,9 @@ ...@@ -23,7 +23,9 @@
23 'use strict'; 23 'use strict';
24 24
25 var $log; 25 var $log;
26 - var Collection, Model, region, ts; 26 + var Collection, Model, region, ts, sus;
27 +
28 + var linkLabelOffset = '0.35em';
27 29
28 var widthRatio = 1.4, 30 var widthRatio = 1.4,
29 linkScale = d3.scale.linear() 31 linkScale = d3.scale.linear()
...@@ -70,12 +72,26 @@ ...@@ -70,12 +72,26 @@
70 y2: 0 72 y2: 0
71 } 73 }
72 // functions to aggregate dual link state 74 // functions to aggregate dual link state
73 -// extra: link.extra 75 + // extra: link.extra
74 }); 76 });
75 77
76 this.set(attrs); 78 this.set(attrs);
77 } 79 }
78 80
81 + function rectAroundText(el) {
82 + var text = el.select('text'),
83 + box = text.node().getBBox();
84 +
85 + // translate the bbox so that it is centered on [x,y]
86 + box.x = -box.width / 2;
87 + box.y = -box.height / 2;
88 +
89 + // add padding
90 + box.x -= 4;
91 + box.width += 8;
92 + return box;
93 + }
94 +
79 function linkEndPoints(srcId, dstId) { 95 function linkEndPoints(srcId, dstId) {
80 96
81 var sourceNode = this.region.findNodeById(srcId) 97 var sourceNode = this.region.findNodeById(srcId)
...@@ -106,13 +122,71 @@ ...@@ -106,13 +122,71 @@
106 return this.get('type'); 122 return this.get('type');
107 }, 123 },
108 expected: function () { 124 expected: function () {
109 - //TODO: original code is: (s && s.expected) && (t && t.expected); 125 + // TODO: original code is: (s && s.expected) && (t && t.expected);
110 return true; 126 return true;
111 }, 127 },
112 online: function () { 128 online: function () {
129 + // TODO: remove next line
113 return true; 130 return true;
131 +
114 return both && (s && s.online) && (t && t.online); 132 return both && (s && s.online) && (t && t.online);
115 }, 133 },
134 + enhance: function () {
135 + var data = [],
136 + point;
137 +
138 + angular.forEach(this.collection.models, function (link) {
139 + link.unenhance();
140 + });
141 +
142 + this.el.classed('enhanced', true);
143 + point = this.locatePortLabel();
144 + angular.extend(point, {
145 + id: 'topo-port-tgt',
146 + num: this.get('portB')
147 + });
148 + data.push(point);
149 +
150 + var entering = d3.select('#topo-portLabels').selectAll('.portLabel')
151 + .data(data).enter().append('g')
152 + .classed('portLabel', true)
153 + .attr('id', function (d) { return d.id; });
154 +
155 + entering.each(function (d) {
156 + var el = d3.select(this),
157 + rect = el.append('rect'),
158 + text = el.append('text').text(d.num);
159 +
160 + rect.attr(rectAroundText(el))
161 + .attr('rx', 2)
162 + .attr('ry', 2);
163 +
164 + text.attr('dy', linkLabelOffset)
165 + .attr('text-anchor', 'middle');
166 +
167 + el.attr('transform', sus.translate(d.x, d.y));
168 + });
169 + },
170 + unenhance: function () {
171 + this.el.classed('enhanced', false);
172 + d3.select('#topo-portLabels').selectAll('.portLabel').remove();
173 + },
174 + locatePortLabel: function (link, src) {
175 + var offset = 32,
176 + pos = this.get('position'),
177 + nearX = src ? pos.x1 : pos.x2,
178 + nearY = src ? pos.y1 : pos.y2,
179 + farX = src ? pos.x2 : pos.x1,
180 + farY = src ? pos.y2 : pos.y1;
181 +
182 + function dist(x, y) { return Math.sqrt(x*x + y*y); }
183 +
184 + var dx = farX - nearX,
185 + dy = farY - nearY,
186 + k = offset / dist(dx, dy);
187 +
188 + return {x: k * dx + nearX, y: k * dy + nearY};
189 + },
116 restyleLinkElement: function (immediate) { 190 restyleLinkElement: function (immediate) {
117 // this fn's job is to look at raw links and decide what svg classes 191 // this fn's job is to look at raw links and decide what svg classes
118 // need to be applied to the line element in the DOM 192 // need to be applied to the line element in the DOM
...@@ -144,9 +218,10 @@ ...@@ -144,9 +218,10 @@
144 .attr('stroke', linkConfig[th].baseColor); 218 .attr('stroke', linkConfig[th].baseColor);
145 } 219 }
146 }, 220 },
147 -
148 onEnter: function (el) { 221 onEnter: function (el) {
149 - var link = d3.select(el); 222 + var _this = this,
223 + link = d3.select(el);
224 +
150 this.el = link; 225 this.el = link;
151 226
152 this.restyleLinkElement(); 227 this.restyleLinkElement();
...@@ -166,12 +241,13 @@ ...@@ -166,12 +241,13 @@
166 241
167 angular.module('ovTopo2') 242 angular.module('ovTopo2')
168 .factory('Topo2LinkService', 243 .factory('Topo2LinkService',
169 - ['$log', 'Topo2Collection', 'Topo2Model', 'ThemeService', 244 + ['$log', 'Topo2Collection', 'Topo2Model', 'ThemeService', 'SvgUtilService',
170 245
171 - function (_$log_, _Collection_, _Model_, _ts_) { 246 + function (_$log_, _Collection_, _Model_, _ts_, _sus_) {
172 247
173 $log = _$log_; 248 $log = _$log_;
174 ts = _ts_; 249 ts = _ts_;
250 + sus = _sus_;
175 Collection = _Collection_; 251 Collection = _Collection_;
176 Model = _Model_; 252 Model = _Model_;
177 253
......
1 +/*
2 + * Copyright 2016-present 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 Select Module.
19 + */
20 +
21 +(function () {
22 + 'use strict';
23 +
24 + // internal state
25 + var hovered, selections, selectOrder, consumeClick;
26 +
27 + function selectObject(obj) {
28 + var el = this,
29 + nodeEv = el && el.tagName === 'g',
30 + ev = d3.event.sourceEvent || {},
31 + n;
32 +
33 + console.log(el, nodeEv, ev, n);
34 + }
35 +
36 + function clickConsumed(x) {
37 + var cc = consumeClick;
38 + consumeClick = !!x;
39 + return cc;
40 + }
41 +
42 + angular.module('ovTopo2')
43 + .factory('Topo2SelectService',
44 + [
45 + function () {
46 +
47 + return {
48 + selectObject: selectObject,
49 + clickConsumed: clickConsumed
50 + };
51 + }
52 + ]);
53 +
54 +})();
......
...@@ -22,7 +22,8 @@ ...@@ -22,7 +22,8 @@
22 (function () { 22 (function () {
23 'use strict'; 23 'use strict';
24 24
25 - var Collection, Model, is, sus, ts, t2vs; 25 + var wss, is, sus, ts, t2vs;
26 + var Collection, Model;
26 27
27 var remappedDeviceTypes = { 28 var remappedDeviceTypes = {
28 virtual: 'cord' 29 virtual: 'cord'
...@@ -71,11 +72,12 @@ ...@@ -71,11 +72,12 @@
71 72
72 angular.module('ovTopo2') 73 angular.module('ovTopo2')
73 .factory('Topo2SubRegionService', 74 .factory('Topo2SubRegionService',
74 - ['Topo2Collection', 'Topo2NodeModel', 'IconService', 'SvgUtilService', 75 + ['WebSocketService', 'Topo2Collection', 'Topo2NodeModel', 'IconService', 'SvgUtilService',
75 'ThemeService', 'Topo2ViewService', 76 'ThemeService', 'Topo2ViewService',
76 77
77 - function (_Collection_, _NodeModel_, _is_, _sus_, _ts_, classnames, _t2vs_) { 78 + function (_wss_, _Collection_, _NodeModel_, _is_, _sus_, _ts_, classnames, _t2vs_) {
78 79
80 + wss = _wss_;
79 t2vs = _t2vs_; 81 t2vs = _t2vs_;
80 is = _is_; 82 is = _is_;
81 sus = _sus_; 83 sus = _sus_;
...@@ -89,6 +91,12 @@ ...@@ -89,6 +91,12 @@
89 }, 91 },
90 nodeType: 'sub-region', 92 nodeType: 'sub-region',
91 mapDeviceTypeToGlyph: mapDeviceTypeToGlyph, 93 mapDeviceTypeToGlyph: mapDeviceTypeToGlyph,
94 + onClick: function () {
95 + wss.sendEvent('topo2navRegion', {
96 + dir: 'down',
97 + rid: this.get('id')
98 + });
99 + },
92 onEnter: function (el) { 100 onEnter: function (el) {
93 101
94 var node = d3.select(el), 102 var node = d3.select(el),
...@@ -97,6 +105,7 @@ ...@@ -97,6 +105,7 @@
97 glyph, labelWidth; 105 glyph, labelWidth;
98 106
99 this.el = node; 107 this.el = node;
108 + this.el.on('click', this.onClick.bind(this));
100 109
101 // Label 110 // Label
102 var labelElements = this.addLabelElements(label); 111 var labelElements = this.addLabelElements(label);
......