blob: 93500207990038f2c85270e2f8463764276a8089 [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 Burrows42eb9e22017-02-06 14:20:24 +000025 var instance,
26 updateTimer;
Steven Burrowsec1f45c2016-08-08 16:14:41 +010027
Steven Burrowsec1f45c2016-08-08 16:14:41 +010028 // default settings for force layout
29 var defaultSettings = {
30 gravity: 0.4,
31 friction: 0.7,
32 charge: {
33 // note: key is node.class
34 device: -8000,
Steven Burrows583f4be2016-11-04 14:06:50 +010035 host: -20000,
36 region: -5000,
Steven Burrowsec1f45c2016-08-08 16:14:41 +010037 _def_: -12000
38 },
39 linkDistance: {
40 // note: key is link.type
41 direct: 100,
42 optical: 120,
Steven Burrows583f4be2016-11-04 14:06:50 +010043 UiEdgeLink: 30,
Steven Burrowsec1f45c2016-08-08 16:14:41 +010044 _def_: 50
45 },
46 linkStrength: {
47 // note: key is link.type
48 // range: {0.0 ... 1.0}
Steven Burrowsdfa52b02016-09-02 13:50:43 +010049 direct: 1.0,
50 optical: 1.0,
Steven Burrows583f4be2016-11-04 14:06:50 +010051 UiEdgeLink: 15.0,
Steven Burrowsec1f45c2016-08-08 16:14:41 +010052 _def_: 1.0
53 }
54 };
55
56 // configuration
57 var linkConfig = {
58 light: {
59 baseColor: '#939598',
60 inColor: '#66f',
61 outColor: '#f00'
62 },
63 dark: {
64 // TODO : theme
65 baseColor: '#939598',
66 inColor: '#66f',
67 outColor: '#f00'
68 },
69 inWidth: 12,
70 outWidth: 10
71 };
72
73 // internal state
Steven Burrowsaf96a212016-12-28 12:57:02 +000074 var nodeLock = false; // whether nodes can be dragged or not (locked)
Steven Burrows9edc7e02016-08-29 11:52:07 +010075
Steven Burrowsa3fca812016-10-14 15:11:04 -050076 // predicate that indicates when clicking is active
77 function clickEnabled() {
78 return true;
79 }
80
Steven Burrowsaf96a212016-12-28 12:57:02 +000081 function getDefaultPosition(link) {
Steven Burrowsec1f45c2016-08-08 16:14:41 +010082 return {
83 x1: link.get('source').x,
84 y1: link.get('source').y,
85 x2: link.get('target').x,
86 y2: link.get('target').y
87 };
88 }
89
Steven Burrowsec1f45c2016-08-08 16:14:41 +010090 angular.module('ovTopo2')
91 .factory('Topo2LayoutService',
92 [
Steven Burrows42eb9e22017-02-06 14:20:24 +000093 '$log', '$timeout', 'WebSocketService', 'SvgUtilService', 'Topo2RegionService',
Steven Burrowsaf96a212016-12-28 12:57:02 +000094 'Topo2D3Service', 'Topo2ViewService', 'Topo2SelectService', 'Topo2ZoomService',
95 'Topo2ViewController',
Steven Burrows42eb9e22017-02-06 14:20:24 +000096 function ($log, $timeout, wss, sus, t2rs, t2d3, t2vs, t2ss, t2zs,
Steven Burrowsaf96a212016-12-28 12:57:02 +000097 ViewController) {
Steven Burrowsec1f45c2016-08-08 16:14:41 +010098
Steven Burrowsaf96a212016-12-28 12:57:02 +000099 var Layout = ViewController.extend({
100 initialize: function (svg, forceG, uplink, dim, zoomer, opts) {
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100101
Steven Burrowsaf96a212016-12-28 12:57:02 +0000102 $log.debug('initialize Layout');
103 instance = this;
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100104
Steven Burrowsaf96a212016-12-28 12:57:02 +0000105 this.svg = svg;
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100106
Steven Burrowsaf96a212016-12-28 12:57:02 +0000107 // Append all the SVG Group elements to the forceG object
108 this.createForceElements();
109
110 this.uplink = uplink;
111 this.dim = dim;
112 this.zoomer = zoomer;
113
114 this.settings = angular.extend({}, defaultSettings, opts);
115
116 this.link = this.elements.linkG.selectAll('.link');
117 this.elements.linkLabelG.selectAll('.linkLabel');
118 this.node = this.elements.nodeG.selectAll('.node');
119 },
120 createForceElements: function () {
121
122 this.prevForce = this.forceG;
123
Simon Hunt95f4b422017-03-03 13:49:05 -0800124 this.forceG = d3.select('#topo2-zoomlayer')
125 .append('g').attr('class', 'topo2-force');
Steven Burrowsaf96a212016-12-28 12:57:02 +0000126
127 this.elements = {
Simon Hunt95f4b422017-03-03 13:49:05 -0800128 linkG: this.addElement(this.forceG, 'topo2-links'),
129 linkLabelG: this.addElement(this.forceG, 'topo2-linkLabels'),
130 numLinksLabels: this.addElement(this.forceG, 'topo2-numLinkLabels'),
131 nodeG: this.addElement(this.forceG, 'topo2-nodes'),
132 portLabels: this.addElement(this.forceG, 'topo2-portLabels')
Steven Burrowsaf96a212016-12-28 12:57:02 +0000133 };
134 },
135 addElement: function (parent, className) {
136 return parent.append('g').attr('class', className);
137 },
138 settingOrDefault: function (settingName, node) {
139 var nodeType = node.get('nodeType');
140 return this.settings[settingName][nodeType] || this.settings[settingName]._def_;
141 },
142 createForceLayout: function () {
143 var _this = this,
144 regionLinks = t2rs.regionLinks(),
145 regionNodes = t2rs.regionNodes();
146
147 this.force = d3.layout.force()
148 .size(t2vs.getDimensions())
149 .gravity(this.settings.gravity)
150 .friction(this.settings.friction)
151 .charge(this.settingOrDefault.bind(this, 'charge'))
152 .linkDistance(this.settingOrDefault.bind(this, 'linkDistance'))
153 .linkStrength(this.settingOrDefault.bind(this, 'linkStrength'))
154 .nodes(regionNodes)
155 .links(regionLinks)
156 .on("tick", this.tick.bind(this))
157 .on("start", function () {
158
159 // TODO: Find a better way to do this
Steven Burrows3cc0c372017-02-10 15:15:24 +0000160 // TODO: BROKEN - Click and dragging and element triggers this event
161// setTimeout(function () {
162// _this.centerLayout();
163// }, 500);
Steven Burrowsaf96a212016-12-28 12:57:02 +0000164 })
165 .start();
166
167 this.link = this.elements.linkG.selectAll('.link')
168 .data(regionLinks, function (d) { return d.get('key'); });
169
170 this.node = this.elements.nodeG.selectAll('.node')
171 .data(regionNodes, function (d) { return d.get('id'); });
172
173 this.drag = sus.createDragBehavior(this.force,
174 t2ss.selectObject,
175 this.atDragEnd,
176 this.dragEnabled.bind(this),
177 clickEnabled
178 );
179
180 this.update();
181 },
182 centerLayout: function () {
Simon Hunt95f4b422017-03-03 13:49:05 -0800183 d3.select('#topo2-zoomlayer').attr('data-layout', t2rs.model.get('id'));
Steven Burrowsaf96a212016-12-28 12:57:02 +0000184
Simon Hunt95f4b422017-03-03 13:49:05 -0800185 var zoomer = d3.select('#topo2-zoomlayer').node().getBBox(),
Steven Burrowsaf96a212016-12-28 12:57:02 +0000186 layoutBBox = this.forceG.node().getBBox(),
187 scale = (zoomer.height - 150) / layoutBBox.height,
188 x = (zoomer.width / 2) - ((layoutBBox.x + layoutBBox.width / 2) * scale),
189 y = (zoomer.height / 2) - ((layoutBBox.y + layoutBBox.height / 2) * scale);
190
191 t2zs.panAndZoom([x, y], scale, 1000);
192 },
193 tick: function () {
Steven Burrows42eb9e22017-02-06 14:20:24 +0000194
Steven Burrowsaf96a212016-12-28 12:57:02 +0000195 this.link
Steven Burrows42eb9e22017-02-06 14:20:24 +0000196 .attr("x1", function (d) { return d.get('source').x; })
197 .attr("y1", function (d) { return d.get('source').y; })
198 .attr("x2", function (d) { return d.get('target').x; })
199 .attr("y2", function (d) { return d.get('target').y; });
Steven Burrowsaf96a212016-12-28 12:57:02 +0000200
201 this.node
202 .attr({
203 transform: function (d) {
204 var dx = isNaN(d.x) ? 0 : d.x,
205 dy = isNaN(d.y) ? 0 : d.y;
206 return sus.translate(dx, dy);
207 }
208 });
209 },
210
211 start: function () {
212 this.force.start();
213 },
214 update: function () {
Steven Burrows42eb9e22017-02-06 14:20:24 +0000215
216 if (updateTimer) {
217 $timeout.cancel(updateTimer);
218 }
219 updateTimer = $timeout(this._update.bind(this), 150);
220 },
221 _update: function () {
Steven Burrowsaf96a212016-12-28 12:57:02 +0000222 this.updateNodes();
223 this.updateLinks();
224 },
225 updateNodes: function () {
226 var regionNodes = t2rs.regionNodes();
227
228 // select all the nodes in the layout:
229 this.node = this.elements.nodeG.selectAll('.node')
230 .data(regionNodes, function (d) { return d.get('id'); });
231
232 var entering = this.node.enter()
233 .append('g')
234 .attr({
235 id: function (d) { return sus.safeId(d.get('id')); },
236 class: function (d) { return d.svgClassName(); },
237 transform: function (d) {
238 // Need to guard against NaN here ??
239 return sus.translate(d.node.x, d.node.y);
240 },
241 opacity: 0
242 })
243 .call(this.drag)
244 .transition()
245 .attr('opacity', 1);
246
247 entering.filter('.device').each(t2d3.nodeEnter);
248 entering.filter('.sub-region').each(t2d3.nodeEnter);
249 entering.filter('.host').each(t2d3.hostEnter);
Steven Burrowsaf96a212016-12-28 12:57:02 +0000250 },
251 updateLinks: function () {
252
253 var regionLinks = t2rs.regionLinks();
254
255 this.link = this.elements.linkG.selectAll('.link')
Steven Burrows42eb9e22017-02-06 14:20:24 +0000256 .data(regionLinks, function (d) { return d.get('key'); });
Steven Burrowsaf96a212016-12-28 12:57:02 +0000257
258 // operate on entering links:
259 var entering = this.link.enter()
260 .append('line')
261 .call(this.calcPosition)
262 .attr({
263 x1: function (d) { return d.get('position').x1; },
264 y1: function (d) { return d.get('position').y1; },
265 x2: function (d) { return d.get('position').x2; },
266 y2: function (d) { return d.get('position').y2; },
267 stroke: linkConfig.light.inColor,
268 'stroke-width': linkConfig.inWidth
269 });
270
271 entering.each(t2d3.linkEntering);
272
273 // operate on exiting links:
274 this.link.exit()
Steven Burrows42eb9e22017-02-06 14:20:24 +0000275 .attr('stroke-dasharray', '3 3')
276 .style('opacity', 0.5)
Steven Burrowsaf96a212016-12-28 12:57:02 +0000277 .transition()
Steven Burrows42eb9e22017-02-06 14:20:24 +0000278 .duration(1500)
279 .attr({
280 'stroke-dasharray': '3 12',
281 })
Steven Burrowsaf96a212016-12-28 12:57:02 +0000282 .style('opacity', 0.0)
283 .remove();
284 },
285 calcPosition: function () {
286 var lines = this;
287
288 lines.each(function (d) {
289 if (d.get('type') === 'hostLink') {
290 d.set('position', getDefaultPosition(d));
291 }
292 });
293
294 lines.each(function (d) {
295 d.set('position', getDefaultPosition(d));
296 });
297 },
298 sendUpdateMeta: function (d, clearPos) {
299 var metaUi = {},
300 ll;
301
302 // if we are not clearing the position data (unpinning),
303 // attach the x, y, (and equivalent longitude, latitude)...
304 if (!clearPos) {
305 ll = d.lngLatFromCoord([d.x, d.y]);
306 metaUi = {
307 x: d.x,
308 y: d.y,
309 equivLoc: {
310 lng: ll[0],
311 lat: ll[1]
312 }
313 };
314 }
315 d.metaUi = metaUi;
316 wss.sendEvent('updateMeta2', {
317 id: d.get('id'),
318 class: d.get('class'),
319 memento: metaUi
320 });
321 },
322 setDimensions: function () {
323 if (this.force) {
324 this.force.size(t2vs.getDimensions());
325 }
326 },
327 dragEnabled: function () {
328 var ev = d3.event.sourceEvent;
329 // nodeLock means we aren't allowing nodes to be dragged...
330 return !nodeLock && !this.zoomingOrPanning(ev);
331 },
332 zoomingOrPanning: function (ev) {
333 return ev.metaKey || ev.altKey;
334 },
335 atDragEnd: function (d) {
336 // once we've finished moving, pin the node in position
337 d.fixed = true;
338 d3.select(this).classed('fixed', true);
339 instance.sendUpdateMeta(d);
Steven Burrowsaf96a212016-12-28 12:57:02 +0000340 t2ss.clickConsumed(true);
341 },
342 transitionDownRegion: function () {
343
344 this.prevForce.transition()
345 .duration(1500)
346 .style('opacity', 0)
347 .remove();
348
349 this.forceG
350 .style('opacity', 0)
351 .transition()
352 .delay(500)
353 .duration(500)
354 .style('opacity', 1);
355 },
356 transitionUpRegion: function () {
357 this.prevForce.transition()
358 .duration(1000)
359 .style('opacity', 0)
360 .remove();
361
362 this.forceG
363 .style('opacity', 0)
364 .transition()
365 .delay(500)
366 .duration(500)
367 .style('opacity', 1);
368 }
369 });
370
371 function getInstance(svg, forceG, uplink, dim, zoomer, opts) {
372 return instance || new Layout(svg, forceG, uplink, dim, zoomer, opts);
373 }
374
375 return getInstance;
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100376 }
377 ]
378 );
379})();