blob: 6e5df6c7414852610fbd1b93ccbf92ef31becf2b [file] [log] [blame]
Steven Burrowsec1f45c2016-08-08 16:14:41 +01001/*
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 Layout Module.
19 Module that contains the d3.force.layout logic
20 */
21
22(function () {
23 'use strict';
24
Steven Burrowse7cc3082016-09-27 11:24:58 -070025 var $log, wss, sus, t2rs, t2d3, t2vs, t2ss;
Steven Burrowsec1f45c2016-08-08 16:14:41 +010026
Steven Burrows583f4be2016-11-04 14:06:50 +010027 var linkG, linkLabelG, nodeG;
Steven Burrows1aa4f582016-12-13 15:05:41 -050028 var link, node, zoomer;
Steven Burrowsec1f45c2016-08-08 16:14:41 +010029
Steven Burrowsec1f45c2016-08-08 16:14:41 +010030 // default settings for force layout
31 var defaultSettings = {
32 gravity: 0.4,
33 friction: 0.7,
34 charge: {
35 // note: key is node.class
36 device: -8000,
Steven Burrows583f4be2016-11-04 14:06:50 +010037 host: -20000,
38 region: -5000,
Steven Burrowsec1f45c2016-08-08 16:14:41 +010039 _def_: -12000
40 },
41 linkDistance: {
42 // note: key is link.type
43 direct: 100,
44 optical: 120,
Steven Burrows583f4be2016-11-04 14:06:50 +010045 UiEdgeLink: 30,
Steven Burrowsec1f45c2016-08-08 16:14:41 +010046 _def_: 50
47 },
48 linkStrength: {
49 // note: key is link.type
50 // range: {0.0 ... 1.0}
Steven Burrowsdfa52b02016-09-02 13:50:43 +010051 direct: 1.0,
52 optical: 1.0,
Steven Burrows583f4be2016-11-04 14:06:50 +010053 UiEdgeLink: 15.0,
Steven Burrowsec1f45c2016-08-08 16:14:41 +010054 _def_: 1.0
55 }
56 };
57
58 // configuration
59 var linkConfig = {
60 light: {
61 baseColor: '#939598',
62 inColor: '#66f',
63 outColor: '#f00'
64 },
65 dark: {
66 // TODO : theme
67 baseColor: '#939598',
68 inColor: '#66f',
69 outColor: '#f00'
70 },
71 inWidth: 12,
72 outWidth: 10
73 };
74
75 // internal state
Steven Burrows1aa4f582016-12-13 15:05:41 -050076 var settings, // merged default settings and options
77 force, // force layout object
78 drag, // drag behavior handler
79 previousNearestLink, // previous link to mouse position
Steven Burrowsdfa52b02016-09-02 13:50:43 +010080 nodeLock = false; // whether nodes can be dragged or not (locked)
Steven Burrowsec1f45c2016-08-08 16:14:41 +010081
Steven Burrows1aa4f582016-12-13 15:05:41 -050082
83 function init(_svg_, forceG, _uplink_, _dim_, _zoomer_, opts) {
Steven Burrowsec1f45c2016-08-08 16:14:41 +010084
85 $log.debug("Initialising Topology Layout");
Steven Burrowsec1f45c2016-08-08 16:14:41 +010086 settings = angular.extend({}, defaultSettings, opts);
87
88 linkG = forceG.append('g').attr('id', 'topo-links');
89 linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels');
Steven Burrowsdfa52b02016-09-02 13:50:43 +010090 forceG.append('g').attr('id', 'topo-numLinkLabels');
Steven Burrowsec1f45c2016-08-08 16:14:41 +010091 nodeG = forceG.append('g').attr('id', 'topo-nodes');
Steven Burrowsdfa52b02016-09-02 13:50:43 +010092 forceG.append('g').attr('id', 'topo-portLabels');
Steven Burrowsec1f45c2016-08-08 16:14:41 +010093
94 link = linkG.selectAll('.link');
Steven Burrowsdfa52b02016-09-02 13:50:43 +010095 linkLabelG.selectAll('.linkLabel');
Steven Burrowsec1f45c2016-08-08 16:14:41 +010096 node = nodeG.selectAll('.node');
Steven Burrows1aa4f582016-12-13 15:05:41 -050097
98 zoomer = _zoomer_;
99 _svg_.on('mousemove', mouseMoveHandler);
100 _svg_.on('click', mouseClickHandler);
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100101 }
102
Steven Burrows583f4be2016-11-04 14:06:50 +0100103 function getDeviceChargeForType(node) {
104
105 var nodeType = node.get('nodeType');
106
107 return settings.charge[nodeType] ||
108 settings.charge._def_;
109 }
110
111 function getLinkDistanceForLinkType(node) {
112 var nodeType = node.get('type');
113
114 return settings.linkDistance[nodeType] ||
115 settings.linkDistance._def_;
116 }
117
118 function getLinkStrenghForLinkType(node) {
119 var nodeType = node.get('type');
120
121 return settings.linkStrength[nodeType] ||
122 settings.linkStrength._def_;
123 }
124
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100125 function createForceLayout() {
126
Steven Burrowsa3fca812016-10-14 15:11:04 -0500127 var regionLinks = t2rs.regionLinks(),
128 regionNodes = t2rs.regionNodes();
129
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100130 force = d3.layout.force()
131 .size(t2vs.getDimensions())
Steven Burrows583f4be2016-11-04 14:06:50 +0100132 .gravity(settings.gravity)
133 .friction(settings.friction)
134 .charge(getDeviceChargeForType)
135 .linkDistance(getLinkDistanceForLinkType)
136 .linkStrength(getLinkStrenghForLinkType)
Steven Burrowsa3fca812016-10-14 15:11:04 -0500137 .on("tick", tick);
138
139 force
140 .nodes(t2rs.regionNodes())
141 .links(regionLinks)
142 .start();
143
144 link = linkG.selectAll('.link')
145 .data(regionLinks, function (d) { return d.get('key'); });
146
147 node = nodeG.selectAll('.node')
148 .data(regionNodes, function (d) { return d.get('id'); });
Steven Burrows9edc7e02016-08-29 11:52:07 +0100149
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100150 drag = sus.createDragBehavior(force,
Steven Burrowsa3fca812016-10-14 15:11:04 -0500151 t2ss.selectObject, atDragEnd, dragEnabled, clickEnabled);
Steven Burrows9edc7e02016-08-29 11:52:07 +0100152
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100153 update();
Steven Burrows9edc7e02016-08-29 11:52:07 +0100154 }
155
Steven Burrowsa3fca812016-10-14 15:11:04 -0500156 // predicate that indicates when clicking is active
157 function clickEnabled() {
158 return true;
159 }
160
Steven Burrows9edc7e02016-08-29 11:52:07 +0100161 function zoomingOrPanning(ev) {
162 return ev.metaKey || ev.altKey;
163 }
164
165 function atDragEnd(d) {
166 // once we've finished moving, pin the node in position
167 d.fixed = true;
168 d3.select(this).classed('fixed', true);
Steven Burrowse7cc3082016-09-27 11:24:58 -0700169 sendUpdateMeta(d);
Steven Burrows583f4be2016-11-04 14:06:50 +0100170 $log.debug(d);
Steven Burrows9edc7e02016-08-29 11:52:07 +0100171 t2ss.clickConsumed(true);
172 }
173
174 // predicate that indicates when dragging is active
175 function dragEnabled() {
176 var ev = d3.event.sourceEvent;
177 // nodeLock means we aren't allowing nodes to be dragged...
178 return !nodeLock && !zoomingOrPanning(ev);
179 }
180
Steven Burrowse7cc3082016-09-27 11:24:58 -0700181 function sendUpdateMeta(d, clearPos) {
182 var metaUi = {},
183 ll;
184
185 // if we are not clearing the position data (unpinning),
186 // attach the x, y, (and equivalent longitude, latitude)...
187 if (!clearPos) {
188 ll = d.lngLatFromCoord([d.x, d.y]);
189 metaUi = {
190 x: d.x,
191 y: d.y,
192 equivLoc: {
193 lng: ll[0],
194 lat: ll[1]
195 }
196 };
197 }
198 d.metaUi = metaUi;
199 wss.sendEvent('updateMeta2', {
200 id: d.get('id'),
201 class: d.get('class'),
202 memento: metaUi
203 });
204 }
205
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100206 function tick() {
Steven Burrowsa3fca812016-10-14 15:11:04 -0500207 link
208 .attr("x1", function (d) { return d.source.x; })
209 .attr("y1", function (d) { return d.source.y; })
210 .attr("x2", function (d) { return d.target.x; })
211 .attr("y2", function (d) { return d.target.y; });
212
213 node
214 .attr({
215 transform: function (d) {
216 var dx = isNaN(d.x) ? 0 : d.x,
217 dy = isNaN(d.y) ? 0 : d.y;
218 return sus.translate(dx, dy);
219 }
220 });
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100221 }
222
223 function update() {
224 _updateNodes();
225 _updateLinks();
226 }
227
228 function _updateNodes() {
229
230 var regionNodes = t2rs.regionNodes();
231
232 // select all the nodes in the layout:
233 node = nodeG.selectAll('.node')
234 .data(regionNodes, function (d) { return d.get('id'); });
235
236 var entering = node.enter()
237 .append('g')
238 .attr({
239 id: function (d) { return sus.safeId(d.get('id')); },
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100240 class: function (d) { return d.svgClassName(); },
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100241 transform: function (d) {
242 // Need to guard against NaN here ??
243 return sus.translate(d.node.x, d.node.y);
244 },
245 opacity: 0
246 })
Steven Burrows9edc7e02016-08-29 11:52:07 +0100247 .call(drag)
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100248 .transition()
249 .attr('opacity', 1);
250
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100251 entering.filter('.device').each(t2d3.nodeEnter);
252 entering.filter('.sub-region').each(t2d3.nodeEnter);
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100253 entering.filter('.host').each(t2d3.hostEnter);
254
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100255 // operate on exiting nodes:
256 // Note that the node is removed after 2 seconds.
257 // Sub element animations should be shorter than 2 seconds.
258 var exiting = node.exit()
259 .transition()
Steven Burrows583f4be2016-11-04 14:06:50 +0100260 .duration(300)
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100261 .style('opacity', 0)
262 .remove();
263
264 // exiting node specifics:
265 // exiting.filter('.host').each(t2d3.hostExit);
266 exiting.filter('.device').each(t2d3.nodeExit);
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100267 }
268
269 function _updateLinks() {
270
271 // var th = ts.theme();
272 var regionLinks = t2rs.regionLinks();
273
274 link = linkG.selectAll('.link')
275 .data(regionLinks, function (d) { return d.get('key'); });
276
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100277 // operate on entering links:
278 var entering = link.enter()
279 .append('line')
280 .call(calcPosition)
281 .attr({
282 x1: function (d) { return d.get('position').x1; },
283 y1: function (d) { return d.get('position').y1; },
284 x2: function (d) { return d.get('position').x2; },
285 y2: function (d) { return d.get('position').y2; },
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100286 stroke: linkConfig.light.inColor,
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100287 'stroke-width': linkConfig.inWidth
288 });
289
290 entering.each(t2d3.linkEntering);
291
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100292 // operate on exiting links:
293 link.exit()
Steven Burrows583f4be2016-11-04 14:06:50 +0100294 .style('opacity', 1)
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100295 .transition()
Steven Burrows583f4be2016-11-04 14:06:50 +0100296 .duration(300)
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100297 .style('opacity', 0.0)
298 .remove();
299 }
300
301 function calcPosition() {
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100302 var lines = this;
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100303
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100304 lines.each(function (d) {
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100305 if (d.get('type') === 'hostLink') {
306 d.set('position', getDefaultPos(d));
307 }
308 });
309
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100310 lines.each(function (d) {
311 d.set('position', getDefaultPos(d));
312 });
313 }
314
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100315 function getDefaultPos(link) {
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100316 return {
317 x1: link.get('source').x,
318 y1: link.get('source').y,
319 x2: link.get('target').x,
320 y2: link.get('target').y
321 };
322 }
323
324 function setDimensions() {
325 if (force) {
326 force.size(t2vs.getDimensions());
327 }
328 }
329
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100330 function start() {
331 force.start();
332 }
333
Steven Burrows1aa4f582016-12-13 15:05:41 -0500334 function mouseClickHandler() {
335
336 if (!d3.event.shiftKey) {
337 t2rs.deselectLink();
338 }
339
340 if (!t2ss.clickConsumed()) {
341 if (previousNearestLink) {
342 previousNearestLink.select();
343 }
344 }
345
346 }
347
348 // Select Links
349 function mouseMoveHandler() {
350 var mp = getLogicalMousePosition(this),
351 link = computeNearestLink(mp);
352
353 // link.enhance();
354 if (link) {
355 if (previousNearestLink && previousNearestLink != link) {
356 previousNearestLink.unenhance();
357 }
358 link.enhance();
359 } else {
360 if (previousNearestLink) {
361 previousNearestLink.unenhance();
362 }
363 }
364
365 previousNearestLink = link;
366 }
367
368
369 function getLogicalMousePosition(container) {
370 var m = d3.mouse(container),
371 sc = zoomer.scale(),
372 tr = zoomer.translate(),
373 mx = (m[0] - tr[0]) / sc,
374 my = (m[1] - tr[1]) / sc;
375 return {x: mx, y: my};
376 }
377
378 function sq(x) { return x * x; }
379
380 function mdist(p, m) {
381 return Math.sqrt(sq(p.x - m.x) + sq(p.y - m.y));
382 }
383
384 function prox(dist) {
385 return dist / zoomer.scale();
386 }
387
388 function computeNearestLink(mouse) {
389 var proximity = prox(30),
390 nearest = null,
391 minDist;
392
393 function pdrop(line, mouse) {
394 var x1 = line.x1,
395 y1 = line.y1,
396 x2 = line.x2,
397 y2 = line.y2,
398 x3 = mouse.x,
399 y3 = mouse.y,
400 k = ((y2-y1) * (x3-x1) - (x2-x1) * (y3-y1)) /
401 (sq(y2-y1) + sq(x2-x1)),
402 x4 = x3 - k * (y2-y1),
403 y4 = y3 + k * (x2-x1);
404 return {x:x4, y:y4};
405 }
406
407 function lineHit(line, p, m) {
408 if (p.x < line.x1 && p.x < line.x2) return false;
409 if (p.x > line.x1 && p.x > line.x2) return false;
410 if (p.y < line.y1 && p.y < line.y2) return false;
411 if (p.y > line.y1 && p.y > line.y2) return false;
412 // line intersects, but are we close enough?
413 return mdist(p, m) <= proximity;
414 }
415
416 var links = t2rs.regionLinks();
417
418 if (links.length) {
419 minDist = proximity * 2;
420
421 links.forEach(function (d) {
422 var line = d.get('position'),
423 point,
424 hit,
425 dist;
426
427 // TODO: Reinstate when showHost() is implemented
428 // if (!api.showHosts() && d.type() === 'hostLink') {
429 // return; // skip hidden host links
430 // }
431
432 if (line) {
433 point = pdrop(line, mouse);
434 hit = lineHit(line, point, mouse);
435 if (hit) {
436 dist = mdist(point, mouse);
437 if (dist < minDist) {
438 minDist = dist;
439 nearest = d;
440 }
441 }
442 }
443 });
444 }
445
446 return nearest;
447 }
448
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100449 angular.module('ovTopo2')
450 .factory('Topo2LayoutService',
451 [
Steven Burrowse7cc3082016-09-27 11:24:58 -0700452 '$log', 'WebSocketService', 'SvgUtilService', 'Topo2RegionService',
Steven Burrows9edc7e02016-08-29 11:52:07 +0100453 'Topo2D3Service', 'Topo2ViewService', 'Topo2SelectService',
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100454
Steven Burrowse7cc3082016-09-27 11:24:58 -0700455 function (_$log_, _wss_, _sus_, _t2rs_, _t2d3_, _t2vs_, _t2ss_) {
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100456
457 $log = _$log_;
Steven Burrowse7cc3082016-09-27 11:24:58 -0700458 wss = _wss_;
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100459 t2rs = _t2rs_;
460 t2d3 = _t2d3_;
461 t2vs = _t2vs_;
Steven Burrows9edc7e02016-08-29 11:52:07 +0100462 t2ss = _t2ss_;
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100463 sus = _sus_;
464
465 return {
466 init: init,
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100467 createForceLayout: createForceLayout,
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100468 update: update,
Steven Burrows1c5c8612016-10-05 13:45:13 -0500469 tick: tick,
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100470 start: start,
471
472 setDimensions: setDimensions
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100473 };
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100474 }
475 ]
476 );
477})();