blob: 36a5cedf6db19948848abc714258e865e7805269 [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,
Steven Burrowsb11a8b82017-03-10 16:00:31 +000036 region: -8000,
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 Burrowsb11a8b82017-03-10 16:00:31 +000043 UiEdgeLink: 100,
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 Burrowsec1f45c2016-08-08 16:14:41 +010049 _def_: 1.0
50 }
51 };
52
53 // configuration
54 var linkConfig = {
55 light: {
56 baseColor: '#939598',
57 inColor: '#66f',
58 outColor: '#f00'
59 },
60 dark: {
61 // TODO : theme
62 baseColor: '#939598',
63 inColor: '#66f',
64 outColor: '#f00'
65 },
66 inWidth: 12,
67 outWidth: 10
68 };
69
70 // internal state
Steven Burrowsaf96a212016-12-28 12:57:02 +000071 var nodeLock = false; // whether nodes can be dragged or not (locked)
Steven Burrows9edc7e02016-08-29 11:52:07 +010072
Steven Burrowsa3fca812016-10-14 15:11:04 -050073 // predicate that indicates when clicking is active
74 function clickEnabled() {
75 return true;
76 }
77
Steven Burrowsaf96a212016-12-28 12:57:02 +000078 function getDefaultPosition(link) {
Steven Burrowsec1f45c2016-08-08 16:14:41 +010079 return {
80 x1: link.get('source').x,
81 y1: link.get('source').y,
82 x2: link.get('target').x,
83 y2: link.get('target').y
84 };
85 }
86
Steven Burrowsec1f45c2016-08-08 16:14:41 +010087 angular.module('ovTopo2')
88 .factory('Topo2LayoutService',
89 [
Steven Burrows42eb9e22017-02-06 14:20:24 +000090 '$log', '$timeout', 'WebSocketService', 'SvgUtilService', 'Topo2RegionService',
Steven Burrowsaf96a212016-12-28 12:57:02 +000091 'Topo2D3Service', 'Topo2ViewService', 'Topo2SelectService', 'Topo2ZoomService',
92 'Topo2ViewController',
Steven Burrows42eb9e22017-02-06 14:20:24 +000093 function ($log, $timeout, wss, sus, t2rs, t2d3, t2vs, t2ss, t2zs,
Steven Burrowsaf96a212016-12-28 12:57:02 +000094 ViewController) {
Steven Burrowsec1f45c2016-08-08 16:14:41 +010095
Steven Burrowsaf96a212016-12-28 12:57:02 +000096 var Layout = ViewController.extend({
Steven Burrowsbd402842017-03-08 21:30:38 +000097 init: function (svg, forceG, uplink, dim, zoomer, opts) {
Steven Burrowsaf96a212016-12-28 12:57:02 +000098 $log.debug('initialize Layout');
99 instance = this;
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100100
Steven Burrowsaf96a212016-12-28 12:57:02 +0000101 this.svg = svg;
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100102
Steven Burrowsaf96a212016-12-28 12:57:02 +0000103 // Append all the SVG Group elements to the forceG object
104 this.createForceElements();
105
106 this.uplink = uplink;
107 this.dim = dim;
108 this.zoomer = zoomer;
109
110 this.settings = angular.extend({}, defaultSettings, opts);
111
112 this.link = this.elements.linkG.selectAll('.link');
113 this.elements.linkLabelG.selectAll('.linkLabel');
114 this.node = this.elements.nodeG.selectAll('.node');
115 },
116 createForceElements: function () {
117
118 this.prevForce = this.forceG;
119
Simon Hunt95f4b422017-03-03 13:49:05 -0800120 this.forceG = d3.select('#topo2-zoomlayer')
121 .append('g').attr('class', 'topo2-force');
Steven Burrowsaf96a212016-12-28 12:57:02 +0000122
123 this.elements = {
Simon Hunt95f4b422017-03-03 13:49:05 -0800124 linkG: this.addElement(this.forceG, 'topo2-links'),
125 linkLabelG: this.addElement(this.forceG, 'topo2-linkLabels'),
126 numLinksLabels: this.addElement(this.forceG, 'topo2-numLinkLabels'),
127 nodeG: this.addElement(this.forceG, 'topo2-nodes'),
128 portLabels: this.addElement(this.forceG, 'topo2-portLabels')
Steven Burrowsaf96a212016-12-28 12:57:02 +0000129 };
130 },
131 addElement: function (parent, className) {
132 return parent.append('g').attr('class', className);
133 },
134 settingOrDefault: function (settingName, node) {
135 var nodeType = node.get('nodeType');
136 return this.settings[settingName][nodeType] || this.settings[settingName]._def_;
137 },
138 createForceLayout: function () {
139 var _this = this,
140 regionLinks = t2rs.regionLinks(),
141 regionNodes = t2rs.regionNodes();
142
143 this.force = d3.layout.force()
144 .size(t2vs.getDimensions())
145 .gravity(this.settings.gravity)
146 .friction(this.settings.friction)
147 .charge(this.settingOrDefault.bind(this, 'charge'))
148 .linkDistance(this.settingOrDefault.bind(this, 'linkDistance'))
149 .linkStrength(this.settingOrDefault.bind(this, 'linkStrength'))
150 .nodes(regionNodes)
151 .links(regionLinks)
152 .on("tick", this.tick.bind(this))
153 .on("start", function () {
154
155 // TODO: Find a better way to do this
Steven Burrows3cc0c372017-02-10 15:15:24 +0000156 // TODO: BROKEN - Click and dragging and element triggers this event
157// setTimeout(function () {
158// _this.centerLayout();
159// }, 500);
Steven Burrowsaf96a212016-12-28 12:57:02 +0000160 })
161 .start();
162
163 this.link = this.elements.linkG.selectAll('.link')
164 .data(regionLinks, function (d) { return d.get('key'); });
165
166 this.node = this.elements.nodeG.selectAll('.node')
167 .data(regionNodes, function (d) { return d.get('id'); });
168
169 this.drag = sus.createDragBehavior(this.force,
170 t2ss.selectObject,
171 this.atDragEnd,
172 this.dragEnabled.bind(this),
173 clickEnabled
174 );
175
176 this.update();
177 },
178 centerLayout: function () {
Simon Hunt95f4b422017-03-03 13:49:05 -0800179 d3.select('#topo2-zoomlayer').attr('data-layout', t2rs.model.get('id'));
Steven Burrowsaf96a212016-12-28 12:57:02 +0000180
Simon Hunt95f4b422017-03-03 13:49:05 -0800181 var zoomer = d3.select('#topo2-zoomlayer').node().getBBox(),
Steven Burrowsaf96a212016-12-28 12:57:02 +0000182 layoutBBox = this.forceG.node().getBBox(),
183 scale = (zoomer.height - 150) / layoutBBox.height,
184 x = (zoomer.width / 2) - ((layoutBBox.x + layoutBBox.width / 2) * scale),
185 y = (zoomer.height / 2) - ((layoutBBox.y + layoutBBox.height / 2) * scale);
186
187 t2zs.panAndZoom([x, y], scale, 1000);
188 },
189 tick: function () {
Steven Burrows42eb9e22017-02-06 14:20:24 +0000190
Steven Burrowsaf96a212016-12-28 12:57:02 +0000191 this.link
Steven Burrows42eb9e22017-02-06 14:20:24 +0000192 .attr("x1", function (d) { return d.get('source').x; })
193 .attr("y1", function (d) { return d.get('source').y; })
194 .attr("x2", function (d) { return d.get('target').x; })
195 .attr("y2", function (d) { return d.get('target').y; });
Steven Burrowsaf96a212016-12-28 12:57:02 +0000196
197 this.node
198 .attr({
199 transform: function (d) {
200 var dx = isNaN(d.x) ? 0 : d.x,
201 dy = isNaN(d.y) ? 0 : d.y;
202 return sus.translate(dx, dy);
203 }
204 });
205 },
206
207 start: function () {
208 this.force.start();
209 },
210 update: function () {
Steven Burrows42eb9e22017-02-06 14:20:24 +0000211
212 if (updateTimer) {
213 $timeout.cancel(updateTimer);
214 }
215 updateTimer = $timeout(this._update.bind(this), 150);
216 },
217 _update: function () {
Steven Burrowsaf96a212016-12-28 12:57:02 +0000218 this.updateNodes();
219 this.updateLinks();
220 },
221 updateNodes: function () {
222 var regionNodes = t2rs.regionNodes();
223
224 // select all the nodes in the layout:
225 this.node = this.elements.nodeG.selectAll('.node')
226 .data(regionNodes, function (d) { return d.get('id'); });
227
228 var entering = this.node.enter()
229 .append('g')
230 .attr({
231 id: function (d) { return sus.safeId(d.get('id')); },
232 class: function (d) { return d.svgClassName(); },
233 transform: function (d) {
234 // Need to guard against NaN here ??
235 return sus.translate(d.node.x, d.node.y);
236 },
237 opacity: 0
238 })
239 .call(this.drag)
240 .transition()
241 .attr('opacity', 1);
242
243 entering.filter('.device').each(t2d3.nodeEnter);
244 entering.filter('.sub-region').each(t2d3.nodeEnter);
Steven Burrowsb11a8b82017-03-10 16:00:31 +0000245 entering.filter('.host').each(t2d3.nodeEnter);
246 entering.filter('.peer-region').each(t2d3.nodeEnter);
Steven Burrowsaf96a212016-12-28 12:57:02 +0000247 },
248 updateLinks: function () {
249
250 var regionLinks = t2rs.regionLinks();
251
252 this.link = this.elements.linkG.selectAll('.link')
Steven Burrows42eb9e22017-02-06 14:20:24 +0000253 .data(regionLinks, function (d) { return d.get('key'); });
Steven Burrowsaf96a212016-12-28 12:57:02 +0000254
255 // operate on entering links:
256 var entering = this.link.enter()
257 .append('line')
258 .call(this.calcPosition)
259 .attr({
260 x1: function (d) { return d.get('position').x1; },
261 y1: function (d) { return d.get('position').y1; },
262 x2: function (d) { return d.get('position').x2; },
263 y2: function (d) { return d.get('position').y2; },
264 stroke: linkConfig.light.inColor,
265 'stroke-width': linkConfig.inWidth
266 });
267
Steven Burrowsb11a8b82017-03-10 16:00:31 +0000268 entering.each(t2d3.nodeEnter);
Steven Burrowsaf96a212016-12-28 12:57:02 +0000269
270 // operate on exiting links:
271 this.link.exit()
Steven Burrows42eb9e22017-02-06 14:20:24 +0000272 .attr('stroke-dasharray', '3 3')
273 .style('opacity', 0.5)
Steven Burrowsaf96a212016-12-28 12:57:02 +0000274 .transition()
Steven Burrows42eb9e22017-02-06 14:20:24 +0000275 .duration(1500)
276 .attr({
277 'stroke-dasharray': '3 12',
278 })
Steven Burrowsaf96a212016-12-28 12:57:02 +0000279 .style('opacity', 0.0)
280 .remove();
281 },
282 calcPosition: function () {
283 var lines = this;
284
285 lines.each(function (d) {
286 if (d.get('type') === 'hostLink') {
287 d.set('position', getDefaultPosition(d));
288 }
289 });
290
291 lines.each(function (d) {
292 d.set('position', getDefaultPosition(d));
293 });
294 },
295 sendUpdateMeta: function (d, clearPos) {
296 var metaUi = {},
297 ll;
298
299 // if we are not clearing the position data (unpinning),
300 // attach the x, y, (and equivalent longitude, latitude)...
301 if (!clearPos) {
302 ll = d.lngLatFromCoord([d.x, d.y]);
303 metaUi = {
304 x: d.x,
305 y: d.y,
306 equivLoc: {
307 lng: ll[0],
308 lat: ll[1]
309 }
310 };
311 }
312 d.metaUi = metaUi;
313 wss.sendEvent('updateMeta2', {
314 id: d.get('id'),
315 class: d.get('class'),
316 memento: metaUi
317 });
318 },
319 setDimensions: function () {
320 if (this.force) {
321 this.force.size(t2vs.getDimensions());
322 }
323 },
324 dragEnabled: function () {
325 var ev = d3.event.sourceEvent;
326 // nodeLock means we aren't allowing nodes to be dragged...
327 return !nodeLock && !this.zoomingOrPanning(ev);
328 },
329 zoomingOrPanning: function (ev) {
330 return ev.metaKey || ev.altKey;
331 },
332 atDragEnd: function (d) {
333 // once we've finished moving, pin the node in position
Steven Burrowsb11a8b82017-03-10 16:00:31 +0000334 d.fix(true);
Steven Burrowsaf96a212016-12-28 12:57:02 +0000335 instance.sendUpdateMeta(d);
Steven Burrowsaf96a212016-12-28 12:57:02 +0000336 t2ss.clickConsumed(true);
337 },
338 transitionDownRegion: function () {
339
340 this.prevForce.transition()
341 .duration(1500)
342 .style('opacity', 0)
343 .remove();
344
345 this.forceG
346 .style('opacity', 0)
347 .transition()
348 .delay(500)
349 .duration(500)
350 .style('opacity', 1);
351 },
352 transitionUpRegion: function () {
353 this.prevForce.transition()
354 .duration(1000)
355 .style('opacity', 0)
356 .remove();
357
358 this.forceG
359 .style('opacity', 0)
360 .transition()
361 .delay(500)
362 .duration(500)
363 .style('opacity', 1);
364 }
365 });
366
367 function getInstance(svg, forceG, uplink, dim, zoomer, opts) {
368 return instance || new Layout(svg, forceG, uplink, dim, zoomer, opts);
369 }
370
Steven Burrowsbd402842017-03-08 21:30:38 +0000371 return getInstance();
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100372 }
373 ]
374 );
375})();