blob: 97fac88a8e11df817c1859f8354d12263002313b [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 Burrowsec1f45c2016-08-08 16:14:41 +010078 angular.module('ovTopo2')
79 .factory('Topo2LayoutService',
80 [
Steven Burrows42eb9e22017-02-06 14:20:24 +000081 '$log', '$timeout', 'WebSocketService', 'SvgUtilService', 'Topo2RegionService',
Steven Burrows448468c2017-04-13 16:09:30 -070082 'Topo2ViewService', 'Topo2SelectService', 'Topo2ZoomService',
Steven Burrows6a4febe2017-04-20 11:45:33 -040083 'Topo2ViewController', 'Topo2RegionNavigationService',
Steven Burrows448468c2017-04-13 16:09:30 -070084 function ($log, $timeout, wss, sus, t2rs, t2vs, t2ss, t2zs,
Steven Burrows6a4febe2017-04-20 11:45:33 -040085 ViewController, t2rns) {
Steven Burrowsec1f45c2016-08-08 16:14:41 +010086
Steven Burrowsaf96a212016-12-28 12:57:02 +000087 var Layout = ViewController.extend({
Steven Burrowsbd402842017-03-08 21:30:38 +000088 init: function (svg, forceG, uplink, dim, zoomer, opts) {
Steven Burrowsaf96a212016-12-28 12:57:02 +000089 instance = this;
Steven Burrowsec1f45c2016-08-08 16:14:41 +010090
Steven Burrows6a4febe2017-04-20 11:45:33 -040091 var navToRegion = this.navigateToRegionHandler.bind(this);
92 t2rns.addListener('region:navigation-start', navToRegion);
93
Steven Burrowsaf96a212016-12-28 12:57:02 +000094 this.svg = svg;
Steven Burrowsec1f45c2016-08-08 16:14:41 +010095
Steven Burrowsaf96a212016-12-28 12:57:02 +000096 // Append all the SVG Group elements to the forceG object
97 this.createForceElements();
98
Steven Burrowsaf96a212016-12-28 12:57:02 +000099 this.dim = dim;
100 this.zoomer = zoomer;
101
102 this.settings = angular.extend({}, defaultSettings, opts);
103
104 this.link = this.elements.linkG.selectAll('.link');
105 this.elements.linkLabelG.selectAll('.linkLabel');
106 this.node = this.elements.nodeG.selectAll('.node');
107 },
108 createForceElements: function () {
109
110 this.prevForce = this.forceG;
111
Simon Hunt95f4b422017-03-03 13:49:05 -0800112 this.forceG = d3.select('#topo2-zoomlayer')
113 .append('g').attr('class', 'topo2-force');
Steven Burrowsaf96a212016-12-28 12:57:02 +0000114
115 this.elements = {
Simon Hunt95f4b422017-03-03 13:49:05 -0800116 linkG: this.addElement(this.forceG, 'topo2-links'),
117 linkLabelG: this.addElement(this.forceG, 'topo2-linkLabels'),
118 numLinksLabels: this.addElement(this.forceG, 'topo2-numLinkLabels'),
119 nodeG: this.addElement(this.forceG, 'topo2-nodes'),
120 portLabels: this.addElement(this.forceG, 'topo2-portLabels')
Steven Burrowsaf96a212016-12-28 12:57:02 +0000121 };
122 },
123 addElement: function (parent, className) {
124 return parent.append('g').attr('class', className);
125 },
126 settingOrDefault: function (settingName, node) {
127 var nodeType = node.get('nodeType');
128 return this.settings[settingName][nodeType] || this.settings[settingName]._def_;
129 },
130 createForceLayout: function () {
Steven Burrowsaf96a212016-12-28 12:57:02 +0000131
132 this.force = d3.layout.force()
133 .size(t2vs.getDimensions())
134 .gravity(this.settings.gravity)
135 .friction(this.settings.friction)
136 .charge(this.settingOrDefault.bind(this, 'charge'))
137 .linkDistance(this.settingOrDefault.bind(this, 'linkDistance'))
138 .linkStrength(this.settingOrDefault.bind(this, 'linkStrength'))
Steven Burrows6de27f42017-03-30 16:21:27 +0100139 .nodes([])
140 .links([])
Steven Burrowsaf96a212016-12-28 12:57:02 +0000141 .on("tick", this.tick.bind(this))
Steven Burrowsaf96a212016-12-28 12:57:02 +0000142 .start();
143
Steven Burrowsaf96a212016-12-28 12:57:02 +0000144 this.drag = sus.createDragBehavior(this.force,
Steven Burrows5fa057e2017-03-15 17:07:56 +0000145 function () {}, // click event is no longer handled in the drag service
Steven Burrowsaf96a212016-12-28 12:57:02 +0000146 this.atDragEnd,
147 this.dragEnabled.bind(this),
Steven Burrows2b15ae72017-04-28 10:24:43 -0400148 clickEnabled,
149 t2zs
Steven Burrowsaf96a212016-12-28 12:57:02 +0000150 );
151
152 this.update();
153 },
154 centerLayout: function () {
Simon Hunt95f4b422017-03-03 13:49:05 -0800155 d3.select('#topo2-zoomlayer').attr('data-layout', t2rs.model.get('id'));
Steven Burrowsaf96a212016-12-28 12:57:02 +0000156
Simon Hunt95f4b422017-03-03 13:49:05 -0800157 var zoomer = d3.select('#topo2-zoomlayer').node().getBBox(),
Steven Burrowsaf96a212016-12-28 12:57:02 +0000158 layoutBBox = this.forceG.node().getBBox(),
159 scale = (zoomer.height - 150) / layoutBBox.height,
160 x = (zoomer.width / 2) - ((layoutBBox.x + layoutBBox.width / 2) * scale),
161 y = (zoomer.height / 2) - ((layoutBBox.y + layoutBBox.height / 2) * scale);
162
163 t2zs.panAndZoom([x, y], scale, 1000);
164 },
Steven Burrowsa31c5b02017-04-12 10:45:08 -0700165 setLinkPosition: function (link) {
166 link.setPosition.bind(link)();
167 },
Steven Burrowsaf96a212016-12-28 12:57:02 +0000168 tick: function () {
Steven Burrows42eb9e22017-02-06 14:20:24 +0000169
Steven Burrowsaf96a212016-12-28 12:57:02 +0000170 this.node
171 .attr({
172 transform: function (d) {
173 var dx = isNaN(d.x) ? 0 : d.x,
174 dy = isNaN(d.y) ? 0 : d.y;
175 return sus.translate(dx, dy);
176 }
177 });
Steven Burrowsa31c5b02017-04-12 10:45:08 -0700178
179 this.link
180 .each(this.setLinkPosition)
181 .attr("x1", function (d) { return d.get('position').x1; })
182 .attr("y1", function (d) { return d.get('position').y1; })
183 .attr("x2", function (d) { return d.get('position').x2; })
184 .attr("y2", function (d) { return d.get('position').y2; });
Steven Burrowsaf96a212016-12-28 12:57:02 +0000185 },
186
187 start: function () {
188 this.force.start();
189 },
190 update: function () {
Steven Burrows42eb9e22017-02-06 14:20:24 +0000191
192 if (updateTimer) {
193 $timeout.cancel(updateTimer);
194 }
195 updateTimer = $timeout(this._update.bind(this), 150);
196 },
197 _update: function () {
Steven Burrowsaf96a212016-12-28 12:57:02 +0000198 this.updateNodes();
199 this.updateLinks();
Steven Burrows3d8d9332017-03-30 15:05:52 +0100200 this.force.start();
Steven Burrowsaf96a212016-12-28 12:57:02 +0000201 },
202 updateNodes: function () {
203 var regionNodes = t2rs.regionNodes();
204
205 // select all the nodes in the layout:
206 this.node = this.elements.nodeG.selectAll('.node')
207 .data(regionNodes, function (d) { return d.get('id'); });
208
209 var entering = this.node.enter()
210 .append('g')
211 .attr({
212 id: function (d) { return sus.safeId(d.get('id')); },
213 class: function (d) { return d.svgClassName(); },
214 transform: function (d) {
215 // Need to guard against NaN here ??
216 return sus.translate(d.node.x, d.node.y);
217 },
218 opacity: 0
219 })
220 .call(this.drag)
221 .transition()
222 .attr('opacity', 1);
223
Steven Burrows448468c2017-04-13 16:09:30 -0700224 entering.each(function (d) { d.onEnter(this, d) });
Steven Burrows3d8d9332017-03-30 15:05:52 +0100225
226 this.force.nodes(regionNodes);
Steven Burrowsaf96a212016-12-28 12:57:02 +0000227 },
228 updateLinks: function () {
229
230 var regionLinks = t2rs.regionLinks();
231
232 this.link = this.elements.linkG.selectAll('.link')
Steven Burrows42eb9e22017-02-06 14:20:24 +0000233 .data(regionLinks, function (d) { return d.get('key'); });
Steven Burrowsaf96a212016-12-28 12:57:02 +0000234
235 // operate on entering links:
236 var entering = this.link.enter()
237 .append('line')
238 .call(this.calcPosition)
239 .attr({
240 x1: function (d) { return d.get('position').x1; },
241 y1: function (d) { return d.get('position').y1; },
242 x2: function (d) { return d.get('position').x2; },
243 y2: function (d) { return d.get('position').y2; },
244 stroke: linkConfig.light.inColor,
245 'stroke-width': linkConfig.inWidth
246 });
247
Steven Burrows448468c2017-04-13 16:09:30 -0700248 entering.each(function (d) { d.onEnter(this, d) });
Steven Burrowsaf96a212016-12-28 12:57:02 +0000249
250 // operate on exiting links:
251 this.link.exit()
Steven Burrows42eb9e22017-02-06 14:20:24 +0000252 .attr('stroke-dasharray', '3 3')
253 .style('opacity', 0.5)
Steven Burrowsaf96a212016-12-28 12:57:02 +0000254 .transition()
Steven Burrows42eb9e22017-02-06 14:20:24 +0000255 .duration(1500)
256 .attr({
257 'stroke-dasharray': '3 12',
258 })
Steven Burrowsaf96a212016-12-28 12:57:02 +0000259 .style('opacity', 0.0)
260 .remove();
Steven Burrows3d8d9332017-03-30 15:05:52 +0100261
262 this.force.links(regionLinks);
Steven Burrowsaf96a212016-12-28 12:57:02 +0000263 },
264 calcPosition: function () {
265 var lines = this;
Steven Burrowsaf96a212016-12-28 12:57:02 +0000266 lines.each(function (d) {
Steven Burrows32ce1d92017-04-13 13:18:44 -0700267 d.setPosition.bind(d)();
Steven Burrowsaf96a212016-12-28 12:57:02 +0000268 });
269 },
270 sendUpdateMeta: function (d, clearPos) {
271 var metaUi = {},
272 ll;
273
274 // if we are not clearing the position data (unpinning),
275 // attach the x, y, (and equivalent longitude, latitude)...
276 if (!clearPos) {
277 ll = d.lngLatFromCoord([d.x, d.y]);
278 metaUi = {
279 x: d.x,
280 y: d.y,
281 equivLoc: {
282 lng: ll[0],
283 lat: ll[1]
284 }
285 };
286 }
287 d.metaUi = metaUi;
288 wss.sendEvent('updateMeta2', {
289 id: d.get('id'),
290 class: d.get('class'),
291 memento: metaUi
292 });
293 },
294 setDimensions: function () {
295 if (this.force) {
296 this.force.size(t2vs.getDimensions());
297 }
298 },
299 dragEnabled: function () {
300 var ev = d3.event.sourceEvent;
301 // nodeLock means we aren't allowing nodes to be dragged...
302 return !nodeLock && !this.zoomingOrPanning(ev);
303 },
304 zoomingOrPanning: function (ev) {
305 return ev.metaKey || ev.altKey;
306 },
307 atDragEnd: function (d) {
308 // once we've finished moving, pin the node in position
Steven Burrowsb11a8b82017-03-10 16:00:31 +0000309 d.fix(true);
Steven Burrowsaf96a212016-12-28 12:57:02 +0000310 instance.sendUpdateMeta(d);
Steven Burrowsaf96a212016-12-28 12:57:02 +0000311 t2ss.clickConsumed(true);
312 },
313 transitionDownRegion: function () {
314
315 this.prevForce.transition()
316 .duration(1500)
317 .style('opacity', 0)
318 .remove();
319
320 this.forceG
321 .style('opacity', 0)
322 .transition()
323 .delay(500)
324 .duration(500)
Steven Burrows6a4febe2017-04-20 11:45:33 -0400325 .style('opacity', 1)
326 .each('end', function () {
327 t2rns.navigateToRegionComplete();
328 });
Steven Burrowsaf96a212016-12-28 12:57:02 +0000329 },
330 transitionUpRegion: function () {
331 this.prevForce.transition()
332 .duration(1000)
333 .style('opacity', 0)
334 .remove();
335
336 this.forceG
337 .style('opacity', 0)
338 .transition()
339 .delay(500)
340 .duration(500)
Steven Burrows6a4febe2017-04-20 11:45:33 -0400341 .style('opacity', 1)
342 .each('end', function () {
343 t2rns.navigateToRegionComplete();
344 });;
345 },
346 navigateToRegionHandler: function () {
347 this.createForceElements();
348 this.transitionDownRegion();
Steven Burrowsaf96a212016-12-28 12:57:02 +0000349 }
350 });
351
352 function getInstance(svg, forceG, uplink, dim, zoomer, opts) {
353 return instance || new Layout(svg, forceG, uplink, dim, zoomer, opts);
354 }
355
Steven Burrowsbd402842017-03-08 21:30:38 +0000356 return getInstance();
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100357 }
358 ]
359 );
360})();