blob: c5e8251640b99bb41960e1fd9fa7d955719d5afa [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),
148 clickEnabled
149 );
150
151 this.update();
152 },
153 centerLayout: function () {
Simon Hunt95f4b422017-03-03 13:49:05 -0800154 d3.select('#topo2-zoomlayer').attr('data-layout', t2rs.model.get('id'));
Steven Burrowsaf96a212016-12-28 12:57:02 +0000155
Simon Hunt95f4b422017-03-03 13:49:05 -0800156 var zoomer = d3.select('#topo2-zoomlayer').node().getBBox(),
Steven Burrowsaf96a212016-12-28 12:57:02 +0000157 layoutBBox = this.forceG.node().getBBox(),
158 scale = (zoomer.height - 150) / layoutBBox.height,
159 x = (zoomer.width / 2) - ((layoutBBox.x + layoutBBox.width / 2) * scale),
160 y = (zoomer.height / 2) - ((layoutBBox.y + layoutBBox.height / 2) * scale);
161
162 t2zs.panAndZoom([x, y], scale, 1000);
163 },
Steven Burrowsa31c5b02017-04-12 10:45:08 -0700164 setLinkPosition: function (link) {
165 link.setPosition.bind(link)();
166 },
Steven Burrowsaf96a212016-12-28 12:57:02 +0000167 tick: function () {
Steven Burrows42eb9e22017-02-06 14:20:24 +0000168
Steven Burrowsaf96a212016-12-28 12:57:02 +0000169 this.node
170 .attr({
171 transform: function (d) {
172 var dx = isNaN(d.x) ? 0 : d.x,
173 dy = isNaN(d.y) ? 0 : d.y;
174 return sus.translate(dx, dy);
175 }
176 });
Steven Burrowsa31c5b02017-04-12 10:45:08 -0700177
178 this.link
179 .each(this.setLinkPosition)
180 .attr("x1", function (d) { return d.get('position').x1; })
181 .attr("y1", function (d) { return d.get('position').y1; })
182 .attr("x2", function (d) { return d.get('position').x2; })
183 .attr("y2", function (d) { return d.get('position').y2; });
Steven Burrowsaf96a212016-12-28 12:57:02 +0000184 },
185
186 start: function () {
187 this.force.start();
188 },
189 update: function () {
Steven Burrows42eb9e22017-02-06 14:20:24 +0000190
191 if (updateTimer) {
192 $timeout.cancel(updateTimer);
193 }
194 updateTimer = $timeout(this._update.bind(this), 150);
195 },
196 _update: function () {
Steven Burrowsaf96a212016-12-28 12:57:02 +0000197 this.updateNodes();
198 this.updateLinks();
Steven Burrows3d8d9332017-03-30 15:05:52 +0100199 this.force.start();
Steven Burrowsaf96a212016-12-28 12:57:02 +0000200 },
201 updateNodes: function () {
202 var regionNodes = t2rs.regionNodes();
203
204 // select all the nodes in the layout:
205 this.node = this.elements.nodeG.selectAll('.node')
206 .data(regionNodes, function (d) { return d.get('id'); });
207
208 var entering = this.node.enter()
209 .append('g')
210 .attr({
211 id: function (d) { return sus.safeId(d.get('id')); },
212 class: function (d) { return d.svgClassName(); },
213 transform: function (d) {
214 // Need to guard against NaN here ??
215 return sus.translate(d.node.x, d.node.y);
216 },
217 opacity: 0
218 })
219 .call(this.drag)
220 .transition()
221 .attr('opacity', 1);
222
Steven Burrows448468c2017-04-13 16:09:30 -0700223 entering.each(function (d) { d.onEnter(this, d) });
Steven Burrows3d8d9332017-03-30 15:05:52 +0100224
225 this.force.nodes(regionNodes);
Steven Burrowsaf96a212016-12-28 12:57:02 +0000226 },
227 updateLinks: function () {
228
229 var regionLinks = t2rs.regionLinks();
230
231 this.link = this.elements.linkG.selectAll('.link')
Steven Burrows42eb9e22017-02-06 14:20:24 +0000232 .data(regionLinks, function (d) { return d.get('key'); });
Steven Burrowsaf96a212016-12-28 12:57:02 +0000233
234 // operate on entering links:
235 var entering = this.link.enter()
236 .append('line')
237 .call(this.calcPosition)
238 .attr({
239 x1: function (d) { return d.get('position').x1; },
240 y1: function (d) { return d.get('position').y1; },
241 x2: function (d) { return d.get('position').x2; },
242 y2: function (d) { return d.get('position').y2; },
243 stroke: linkConfig.light.inColor,
244 'stroke-width': linkConfig.inWidth
245 });
246
Steven Burrows448468c2017-04-13 16:09:30 -0700247 entering.each(function (d) { d.onEnter(this, d) });
Steven Burrowsaf96a212016-12-28 12:57:02 +0000248
249 // operate on exiting links:
250 this.link.exit()
Steven Burrows42eb9e22017-02-06 14:20:24 +0000251 .attr('stroke-dasharray', '3 3')
252 .style('opacity', 0.5)
Steven Burrowsaf96a212016-12-28 12:57:02 +0000253 .transition()
Steven Burrows42eb9e22017-02-06 14:20:24 +0000254 .duration(1500)
255 .attr({
256 'stroke-dasharray': '3 12',
257 })
Steven Burrowsaf96a212016-12-28 12:57:02 +0000258 .style('opacity', 0.0)
259 .remove();
Steven Burrows3d8d9332017-03-30 15:05:52 +0100260
261 this.force.links(regionLinks);
Steven Burrowsaf96a212016-12-28 12:57:02 +0000262 },
263 calcPosition: function () {
264 var lines = this;
Steven Burrowsaf96a212016-12-28 12:57:02 +0000265 lines.each(function (d) {
Steven Burrows32ce1d92017-04-13 13:18:44 -0700266 d.setPosition.bind(d)();
Steven Burrowsaf96a212016-12-28 12:57:02 +0000267 });
268 },
269 sendUpdateMeta: function (d, clearPos) {
270 var metaUi = {},
271 ll;
272
273 // if we are not clearing the position data (unpinning),
274 // attach the x, y, (and equivalent longitude, latitude)...
275 if (!clearPos) {
276 ll = d.lngLatFromCoord([d.x, d.y]);
277 metaUi = {
278 x: d.x,
279 y: d.y,
280 equivLoc: {
281 lng: ll[0],
282 lat: ll[1]
283 }
284 };
285 }
286 d.metaUi = metaUi;
287 wss.sendEvent('updateMeta2', {
288 id: d.get('id'),
289 class: d.get('class'),
290 memento: metaUi
291 });
292 },
293 setDimensions: function () {
294 if (this.force) {
295 this.force.size(t2vs.getDimensions());
296 }
297 },
298 dragEnabled: function () {
299 var ev = d3.event.sourceEvent;
300 // nodeLock means we aren't allowing nodes to be dragged...
301 return !nodeLock && !this.zoomingOrPanning(ev);
302 },
303 zoomingOrPanning: function (ev) {
304 return ev.metaKey || ev.altKey;
305 },
306 atDragEnd: function (d) {
307 // once we've finished moving, pin the node in position
Steven Burrowsb11a8b82017-03-10 16:00:31 +0000308 d.fix(true);
Steven Burrowsaf96a212016-12-28 12:57:02 +0000309 instance.sendUpdateMeta(d);
Steven Burrowsaf96a212016-12-28 12:57:02 +0000310 t2ss.clickConsumed(true);
311 },
312 transitionDownRegion: function () {
313
314 this.prevForce.transition()
315 .duration(1500)
316 .style('opacity', 0)
317 .remove();
318
319 this.forceG
320 .style('opacity', 0)
321 .transition()
322 .delay(500)
323 .duration(500)
Steven Burrows6a4febe2017-04-20 11:45:33 -0400324 .style('opacity', 1)
325 .each('end', function () {
326 t2rns.navigateToRegionComplete();
327 });
Steven Burrowsaf96a212016-12-28 12:57:02 +0000328 },
329 transitionUpRegion: function () {
330 this.prevForce.transition()
331 .duration(1000)
332 .style('opacity', 0)
333 .remove();
334
335 this.forceG
336 .style('opacity', 0)
337 .transition()
338 .delay(500)
339 .duration(500)
Steven Burrows6a4febe2017-04-20 11:45:33 -0400340 .style('opacity', 1)
341 .each('end', function () {
342 t2rns.navigateToRegionComplete();
343 });;
344 },
345 navigateToRegionHandler: function () {
346 this.createForceElements();
347 this.transitionDownRegion();
Steven Burrowsaf96a212016-12-28 12:57:02 +0000348 }
349 });
350
351 function getInstance(svg, forceG, uplink, dim, zoomer, opts) {
352 return instance || new Layout(svg, forceG, uplink, dim, zoomer, opts);
353 }
354
Steven Burrowsbd402842017-03-08 21:30:38 +0000355 return getInstance();
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100356 }
357 ]
358 );
359})();