blob: 4cd86cc4cd8572adb08ba3720c3ec7d56deca124 [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 Burrowsdfa52b02016-09-02 13:50:43 +010027 var uplink, linkG, linkLabelG, nodeG;
28 var link, node;
Steven Burrowsec1f45c2016-08-08 16:14:41 +010029
Steven Burrowsdfa52b02016-09-02 13:50:43 +010030 var highlightedLink;
Steven Burrowsec1f45c2016-08-08 16:14:41 +010031
32 // default settings for force layout
33 var defaultSettings = {
34 gravity: 0.4,
35 friction: 0.7,
36 charge: {
37 // note: key is node.class
38 device: -8000,
39 host: -5000,
40 _def_: -12000
41 },
42 linkDistance: {
43 // note: key is link.type
44 direct: 100,
45 optical: 120,
46 hostLink: 3,
47 _def_: 50
48 },
49 linkStrength: {
50 // note: key is link.type
51 // range: {0.0 ... 1.0}
Steven Burrowsdfa52b02016-09-02 13:50:43 +010052 direct: 1.0,
53 optical: 1.0,
54 hostLink: 1.0,
Steven Burrowsec1f45c2016-08-08 16:14:41 +010055 _def_: 1.0
56 }
57 };
58
59 // configuration
60 var linkConfig = {
61 light: {
62 baseColor: '#939598',
63 inColor: '#66f',
64 outColor: '#f00'
65 },
66 dark: {
67 // TODO : theme
68 baseColor: '#939598',
69 inColor: '#66f',
70 outColor: '#f00'
71 },
72 inWidth: 12,
73 outWidth: 10
74 };
75
76 // internal state
77 var settings, // merged default settings and options
78 force, // force layout object
79 drag, // drag behavior handler
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
82 var tickStuff = {
83 nodeAttr: {
84 transform: function (d) {
85 var dx = isNaN(d.x) ? 0 : d.x,
86 dy = isNaN(d.y) ? 0 : d.y;
87 return sus.translate(dx, dy);
88 }
89 },
90 linkAttr: {
91 x1: function (d) { return d.get('position').x1; },
92 y1: function (d) { return d.get('position').y1; },
93 x2: function (d) { return d.get('position').x2; },
94 y2: function (d) { return d.get('position').y2; }
Steven Burrowsec1f45c2016-08-08 16:14:41 +010095 }
96 };
97
98 function init(_svg_, forceG, _uplink_, _dim_, opts) {
99
100 $log.debug("Initialising Topology Layout");
Steven Burrows9edc7e02016-08-29 11:52:07 +0100101 uplink = _uplink_;
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100102 settings = angular.extend({}, defaultSettings, opts);
103
104 linkG = forceG.append('g').attr('id', 'topo-links');
105 linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels');
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100106 forceG.append('g').attr('id', 'topo-numLinkLabels');
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100107 nodeG = forceG.append('g').attr('id', 'topo-nodes');
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100108 forceG.append('g').attr('id', 'topo-portLabels');
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100109
110 link = linkG.selectAll('.link');
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100111 linkLabelG.selectAll('.linkLabel');
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100112 node = nodeG.selectAll('.node');
113
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100114 _svg_.on('mousemove', mouseMoveHandler);
115 }
116
117 function createForceLayout() {
118
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100119 force = d3.layout.force()
120 .size(t2vs.getDimensions())
121 .nodes(t2rs.regionNodes())
122 .links(t2rs.regionLinks())
123 .gravity(settings.gravity)
124 .friction(settings.friction)
125 .charge(settings.charge._def_)
126 .linkDistance(settings.linkDistance._def_)
127 .linkStrength(settings.linkStrength._def_)
128 .on('tick', tick);
Steven Burrows9edc7e02016-08-29 11:52:07 +0100129
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100130 drag = sus.createDragBehavior(force,
131 t2ss.selectObject, atDragEnd, dragEnabled, clickEnabled);
Steven Burrows9edc7e02016-08-29 11:52:07 +0100132
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100133 start();
134 update();
Steven Burrows9edc7e02016-08-29 11:52:07 +0100135 }
136
137 function zoomingOrPanning(ev) {
138 return ev.metaKey || ev.altKey;
139 }
140
141 function atDragEnd(d) {
142 // once we've finished moving, pin the node in position
143 d.fixed = true;
144 d3.select(this).classed('fixed', true);
Steven Burrowse7cc3082016-09-27 11:24:58 -0700145 sendUpdateMeta(d);
Steven Burrows9edc7e02016-08-29 11:52:07 +0100146 t2ss.clickConsumed(true);
147 }
148
149 // predicate that indicates when dragging is active
150 function dragEnabled() {
151 var ev = d3.event.sourceEvent;
152 // nodeLock means we aren't allowing nodes to be dragged...
153 return !nodeLock && !zoomingOrPanning(ev);
154 }
155
Steven Burrowse7cc3082016-09-27 11:24:58 -0700156 function sendUpdateMeta(d, clearPos) {
157 var metaUi = {},
158 ll;
159
160 // if we are not clearing the position data (unpinning),
161 // attach the x, y, (and equivalent longitude, latitude)...
162 if (!clearPos) {
163 ll = d.lngLatFromCoord([d.x, d.y]);
164 metaUi = {
165 x: d.x,
166 y: d.y,
167 equivLoc: {
168 lng: ll[0],
169 lat: ll[1]
170 }
171 };
172 }
173 d.metaUi = metaUi;
174 wss.sendEvent('updateMeta2', {
175 id: d.get('id'),
176 class: d.get('class'),
177 memento: metaUi
178 });
179 }
180
Steven Burrows9edc7e02016-08-29 11:52:07 +0100181 // predicate that indicates when clicking is active
182 function clickEnabled() {
183 return true;
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100184 }
185
186 function tick() {
187 // guard against null (which can happen when our view pages out)...
188 if (node && node.size()) {
189 node.attr(tickStuff.nodeAttr);
190 }
191 if (link && link.size()) {
192 link.call(calcPosition)
193 .attr(tickStuff.linkAttr);
194 // t2d3.applyNumLinkLabels(linkNums, numLinkLabelsG);
195 }
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100196 }
197
198 function update() {
199 _updateNodes();
200 _updateLinks();
201 }
202
203 function _updateNodes() {
204
205 var regionNodes = t2rs.regionNodes();
206
207 // select all the nodes in the layout:
208 node = nodeG.selectAll('.node')
209 .data(regionNodes, function (d) { return d.get('id'); });
210
211 var entering = node.enter()
212 .append('g')
213 .attr({
214 id: function (d) { return sus.safeId(d.get('id')); },
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100215 class: function (d) { return d.svgClassName(); },
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100216 transform: function (d) {
217 // Need to guard against NaN here ??
218 return sus.translate(d.node.x, d.node.y);
219 },
220 opacity: 0
221 })
Steven Burrows9edc7e02016-08-29 11:52:07 +0100222 .call(drag)
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100223 // .on('mouseover', tss.nodeMouseOver)
224 // .on('mouseout', tss.nodeMouseOut)
225 .transition()
226 .attr('opacity', 1);
227
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100228 entering.filter('.device').each(t2d3.nodeEnter);
229 entering.filter('.sub-region').each(t2d3.nodeEnter);
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100230 entering.filter('.host').each(t2d3.hostEnter);
231
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100232 // operate on exiting nodes:
233 // Note that the node is removed after 2 seconds.
234 // Sub element animations should be shorter than 2 seconds.
235 var exiting = node.exit()
236 .transition()
237 .duration(2000)
238 .style('opacity', 0)
239 .remove();
240
241 // exiting node specifics:
242 // exiting.filter('.host').each(t2d3.hostExit);
243 exiting.filter('.device').each(t2d3.nodeExit);
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100244 }
245
246 function _updateLinks() {
247
248 // var th = ts.theme();
249 var regionLinks = t2rs.regionLinks();
250
251 link = linkG.selectAll('.link')
252 .data(regionLinks, function (d) { return d.get('key'); });
253
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100254 // operate on entering links:
255 var entering = link.enter()
256 .append('line')
257 .call(calcPosition)
258 .attr({
259 x1: function (d) { return d.get('position').x1; },
260 y1: function (d) { return d.get('position').y1; },
261 x2: function (d) { return d.get('position').x2; },
262 y2: function (d) { return d.get('position').y2; },
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100263 stroke: linkConfig.light.inColor,
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100264 'stroke-width': linkConfig.inWidth
265 });
266
267 entering.each(t2d3.linkEntering);
268
269 // operate on both existing and new links:
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100270 // link.each(...)
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100271
272 // add labels for how many links are in a thick line
273 // t2d3.applyNumLinkLabels(linkNums, numLinkLabelsG);
274
275 // apply or remove labels
276 // t2d3.applyLinkLabels();
277
278 // operate on exiting links:
279 link.exit()
280 .attr('stroke-dasharray', '3 3')
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100281 .attr('stroke', linkConfig.light.outColor)
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100282 .style('opacity', 0.5)
283 .transition()
284 .duration(1500)
285 .attr({
286 'stroke-dasharray': '3 12',
287 'stroke-width': linkConfig.outWidth
288 })
289 .style('opacity', 0.0)
290 .remove();
291 }
292
293 function calcPosition() {
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100294 var lines = this;
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100295
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100296 lines.each(function (d) {
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100297 if (d.get('type') === 'hostLink') {
298 d.set('position', getDefaultPos(d));
299 }
300 });
301
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100302 lines.each(function (d) {
303 d.set('position', getDefaultPos(d));
304 });
305 }
306
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100307 function getDefaultPos(link) {
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100308
309 return {
310 x1: link.get('source').x,
311 y1: link.get('source').y,
312 x2: link.get('target').x,
313 y2: link.get('target').y
314 };
315 }
316
317 function setDimensions() {
318 if (force) {
319 force.size(t2vs.getDimensions());
320 }
321 }
322
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100323 function start() {
324 force.start();
325 }
326
Steven Burrows9edc7e02016-08-29 11:52:07 +0100327 // Mouse Events
328 function mouseMoveHandler() {
329 var mp = getLogicalMousePosition(this),
330 link = computeNearestLink(mp);
331
Steven Burrows9edc7e02016-08-29 11:52:07 +0100332 if (highlightedLink) {
333 highlightedLink.unenhance();
334 highlightedLink = null;
335 }
336
337 if (link) {
338 link.enhance();
339 highlightedLink = link;
340 }
341 }
342
343 // ======== ALGORITHM TO FIND LINK CLOSEST TO MOUSE ========
344
345 function getLogicalMousePosition(container) {
346 var m = d3.mouse(container),
347 sc = uplink.zoomer().scale(),
348 tr = uplink.zoomer().translate(),
349 mx = (m[0] - tr[0]) / sc,
350 my = (m[1] - tr[1]) / sc;
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100351 return { x: mx, y: my };
Steven Burrows9edc7e02016-08-29 11:52:07 +0100352 }
353
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100354 function sq(x) {
355 return x * x;
356 }
Steven Burrows9edc7e02016-08-29 11:52:07 +0100357
358 function mdist(p, m) {
359 return Math.sqrt(sq(p.x - m.x) + sq(p.y - m.y));
360 }
361
362 function prox(dist) {
363 return dist / uplink.zoomer().scale();
364 }
365
Steven Burrows9edc7e02016-08-29 11:52:07 +0100366 function computeNearestLink(mouse) {
367 var proximity = prox(30),
368 nearest = null,
369 minDist,
370 regionLinks = t2rs.regionLinks();
371
372 function pdrop(line, mouse) {
373
374 var x1 = line.x1,
375 y1 = line.y1,
376 x2 = line.x2,
377 y2 = line.y2,
378 x3 = mouse.x,
379 y3 = mouse.y,
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100380 k = ((y2 - y1) * (x3 - x1) - (x2 - x1) * (y3 - y1)) /
381 (sq(y2 - y1) + sq(x2 - x1)),
382 x4 = x3 - k * (y2 - y1),
383 y4 = y3 + k * (x2 - x1);
384 return { x: x4, y: y4 };
Steven Burrows9edc7e02016-08-29 11:52:07 +0100385 }
386
387 function lineHit(line, p, m) {
388 if (p.x < line.x1 && p.x < line.x2) return false;
389 if (p.x > line.x1 && p.x > line.x2) return false;
390 if (p.y < line.y1 && p.y < line.y2) return false;
391 if (p.y > line.y1 && p.y > line.y2) return false;
392 // line intersects, but are we close enough?
393 return mdist(p, m) <= proximity;
394 }
395
396 if (regionLinks.length) {
397 minDist = proximity * 2;
398
399 regionLinks.forEach(function (d) {
400 // if (!api.showHosts() && d.type() === 'hostLink') {
401 // return; // skip hidden host links
402 // }
403
404 var line = d.get('position'),
405 point = pdrop(line, mouse),
406 hit = lineHit(line, point, mouse),
407 dist;
408
409 if (hit) {
410 dist = mdist(point, mouse);
411 if (dist < minDist) {
412 minDist = dist;
413 nearest = d;
414 }
415 }
416 });
417 }
418 return nearest;
419 }
420
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100421 angular.module('ovTopo2')
422 .factory('Topo2LayoutService',
423 [
Steven Burrowse7cc3082016-09-27 11:24:58 -0700424 '$log', 'WebSocketService', 'SvgUtilService', 'Topo2RegionService',
Steven Burrows9edc7e02016-08-29 11:52:07 +0100425 'Topo2D3Service', 'Topo2ViewService', 'Topo2SelectService',
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100426
Steven Burrowse7cc3082016-09-27 11:24:58 -0700427 function (_$log_, _wss_, _sus_, _t2rs_, _t2d3_, _t2vs_, _t2ss_) {
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100428
429 $log = _$log_;
Steven Burrowse7cc3082016-09-27 11:24:58 -0700430 wss = _wss_;
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100431 t2rs = _t2rs_;
432 t2d3 = _t2d3_;
433 t2vs = _t2vs_;
Steven Burrows9edc7e02016-08-29 11:52:07 +0100434 t2ss = _t2ss_;
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100435 sus = _sus_;
436
437 return {
438 init: init,
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100439 createForceLayout: createForceLayout,
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100440 update: update,
Steven Burrows1c5c8612016-10-05 13:45:13 -0500441 tick: tick,
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100442 start: start,
443
444 setDimensions: setDimensions
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100445 };
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100446 }
447 ]
448 );
449})();