blob: 2a8e95d317f8cfe00c0f826a469a6cdca8dcfe1f [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 Burrowsaf96a212016-12-28 12:57:02 +000083 'Topo2ViewController',
Steven Burrows448468c2017-04-13 16:09:30 -070084 function ($log, $timeout, wss, sus, t2rs, t2vs, t2ss, t2zs,
Steven Burrowsaf96a212016-12-28 12:57:02 +000085 ViewController) {
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 Burrowsaf96a212016-12-28 12:57:02 +000091 this.svg = svg;
Steven Burrowsec1f45c2016-08-08 16:14:41 +010092
Steven Burrowsaf96a212016-12-28 12:57:02 +000093 // Append all the SVG Group elements to the forceG object
94 this.createForceElements();
95
Steven Burrowsaf96a212016-12-28 12:57:02 +000096 this.dim = dim;
97 this.zoomer = zoomer;
98
99 this.settings = angular.extend({}, defaultSettings, opts);
100
101 this.link = this.elements.linkG.selectAll('.link');
102 this.elements.linkLabelG.selectAll('.linkLabel');
103 this.node = this.elements.nodeG.selectAll('.node');
104 },
105 createForceElements: function () {
106
107 this.prevForce = this.forceG;
108
Simon Hunt95f4b422017-03-03 13:49:05 -0800109 this.forceG = d3.select('#topo2-zoomlayer')
110 .append('g').attr('class', 'topo2-force');
Steven Burrowsaf96a212016-12-28 12:57:02 +0000111
112 this.elements = {
Simon Hunt95f4b422017-03-03 13:49:05 -0800113 linkG: this.addElement(this.forceG, 'topo2-links'),
114 linkLabelG: this.addElement(this.forceG, 'topo2-linkLabels'),
115 numLinksLabels: this.addElement(this.forceG, 'topo2-numLinkLabels'),
116 nodeG: this.addElement(this.forceG, 'topo2-nodes'),
117 portLabels: this.addElement(this.forceG, 'topo2-portLabels')
Steven Burrowsaf96a212016-12-28 12:57:02 +0000118 };
119 },
120 addElement: function (parent, className) {
121 return parent.append('g').attr('class', className);
122 },
123 settingOrDefault: function (settingName, node) {
124 var nodeType = node.get('nodeType');
125 return this.settings[settingName][nodeType] || this.settings[settingName]._def_;
126 },
127 createForceLayout: function () {
Steven Burrowsaf96a212016-12-28 12:57:02 +0000128
129 this.force = d3.layout.force()
130 .size(t2vs.getDimensions())
131 .gravity(this.settings.gravity)
132 .friction(this.settings.friction)
133 .charge(this.settingOrDefault.bind(this, 'charge'))
134 .linkDistance(this.settingOrDefault.bind(this, 'linkDistance'))
135 .linkStrength(this.settingOrDefault.bind(this, 'linkStrength'))
Steven Burrows6de27f42017-03-30 16:21:27 +0100136 .nodes([])
137 .links([])
Steven Burrowsaf96a212016-12-28 12:57:02 +0000138 .on("tick", this.tick.bind(this))
Steven Burrowsaf96a212016-12-28 12:57:02 +0000139 .start();
140
Steven Burrowsaf96a212016-12-28 12:57:02 +0000141 this.drag = sus.createDragBehavior(this.force,
Steven Burrows5fa057e2017-03-15 17:07:56 +0000142 function () {}, // click event is no longer handled in the drag service
Steven Burrowsaf96a212016-12-28 12:57:02 +0000143 this.atDragEnd,
144 this.dragEnabled.bind(this),
145 clickEnabled
146 );
147
148 this.update();
149 },
150 centerLayout: function () {
Simon Hunt95f4b422017-03-03 13:49:05 -0800151 d3.select('#topo2-zoomlayer').attr('data-layout', t2rs.model.get('id'));
Steven Burrowsaf96a212016-12-28 12:57:02 +0000152
Simon Hunt95f4b422017-03-03 13:49:05 -0800153 var zoomer = d3.select('#topo2-zoomlayer').node().getBBox(),
Steven Burrowsaf96a212016-12-28 12:57:02 +0000154 layoutBBox = this.forceG.node().getBBox(),
155 scale = (zoomer.height - 150) / layoutBBox.height,
156 x = (zoomer.width / 2) - ((layoutBBox.x + layoutBBox.width / 2) * scale),
157 y = (zoomer.height / 2) - ((layoutBBox.y + layoutBBox.height / 2) * scale);
158
159 t2zs.panAndZoom([x, y], scale, 1000);
160 },
Steven Burrowsa31c5b02017-04-12 10:45:08 -0700161 setLinkPosition: function (link) {
162 link.setPosition.bind(link)();
163 },
Steven Burrowsaf96a212016-12-28 12:57:02 +0000164 tick: function () {
Steven Burrows42eb9e22017-02-06 14:20:24 +0000165
Steven Burrowsaf96a212016-12-28 12:57:02 +0000166 this.node
167 .attr({
168 transform: function (d) {
169 var dx = isNaN(d.x) ? 0 : d.x,
170 dy = isNaN(d.y) ? 0 : d.y;
171 return sus.translate(dx, dy);
172 }
173 });
Steven Burrowsa31c5b02017-04-12 10:45:08 -0700174
175 this.link
176 .each(this.setLinkPosition)
177 .attr("x1", function (d) { return d.get('position').x1; })
178 .attr("y1", function (d) { return d.get('position').y1; })
179 .attr("x2", function (d) { return d.get('position').x2; })
180 .attr("y2", function (d) { return d.get('position').y2; });
Steven Burrowsaf96a212016-12-28 12:57:02 +0000181 },
182
183 start: function () {
184 this.force.start();
185 },
186 update: function () {
Steven Burrows42eb9e22017-02-06 14:20:24 +0000187
188 if (updateTimer) {
189 $timeout.cancel(updateTimer);
190 }
191 updateTimer = $timeout(this._update.bind(this), 150);
192 },
193 _update: function () {
Steven Burrowsaf96a212016-12-28 12:57:02 +0000194 this.updateNodes();
195 this.updateLinks();
Steven Burrows3d8d9332017-03-30 15:05:52 +0100196 this.force.start();
Steven Burrowsaf96a212016-12-28 12:57:02 +0000197 },
198 updateNodes: function () {
199 var regionNodes = t2rs.regionNodes();
200
201 // select all the nodes in the layout:
202 this.node = this.elements.nodeG.selectAll('.node')
203 .data(regionNodes, function (d) { return d.get('id'); });
204
205 var entering = this.node.enter()
206 .append('g')
207 .attr({
208 id: function (d) { return sus.safeId(d.get('id')); },
209 class: function (d) { return d.svgClassName(); },
210 transform: function (d) {
211 // Need to guard against NaN here ??
212 return sus.translate(d.node.x, d.node.y);
213 },
214 opacity: 0
215 })
216 .call(this.drag)
217 .transition()
218 .attr('opacity', 1);
219
Steven Burrows448468c2017-04-13 16:09:30 -0700220 entering.each(function (d) { d.onEnter(this, d) });
Steven Burrows3d8d9332017-03-30 15:05:52 +0100221
222 this.force.nodes(regionNodes);
Steven Burrowsaf96a212016-12-28 12:57:02 +0000223 },
224 updateLinks: function () {
225
226 var regionLinks = t2rs.regionLinks();
227
228 this.link = this.elements.linkG.selectAll('.link')
Steven Burrows42eb9e22017-02-06 14:20:24 +0000229 .data(regionLinks, function (d) { return d.get('key'); });
Steven Burrowsaf96a212016-12-28 12:57:02 +0000230
231 // operate on entering links:
232 var entering = this.link.enter()
233 .append('line')
234 .call(this.calcPosition)
235 .attr({
236 x1: function (d) { return d.get('position').x1; },
237 y1: function (d) { return d.get('position').y1; },
238 x2: function (d) { return d.get('position').x2; },
239 y2: function (d) { return d.get('position').y2; },
240 stroke: linkConfig.light.inColor,
241 'stroke-width': linkConfig.inWidth
242 });
243
Steven Burrows448468c2017-04-13 16:09:30 -0700244 entering.each(function (d) { d.onEnter(this, d) });
Steven Burrowsaf96a212016-12-28 12:57:02 +0000245
246 // operate on exiting links:
247 this.link.exit()
Steven Burrows42eb9e22017-02-06 14:20:24 +0000248 .attr('stroke-dasharray', '3 3')
249 .style('opacity', 0.5)
Steven Burrowsaf96a212016-12-28 12:57:02 +0000250 .transition()
Steven Burrows42eb9e22017-02-06 14:20:24 +0000251 .duration(1500)
252 .attr({
253 'stroke-dasharray': '3 12',
254 })
Steven Burrowsaf96a212016-12-28 12:57:02 +0000255 .style('opacity', 0.0)
256 .remove();
Steven Burrows3d8d9332017-03-30 15:05:52 +0100257
258 this.force.links(regionLinks);
Steven Burrowsaf96a212016-12-28 12:57:02 +0000259 },
260 calcPosition: function () {
261 var lines = this;
Steven Burrowsaf96a212016-12-28 12:57:02 +0000262 lines.each(function (d) {
Steven Burrows32ce1d92017-04-13 13:18:44 -0700263 d.setPosition.bind(d)();
Steven Burrowsaf96a212016-12-28 12:57:02 +0000264 });
265 },
266 sendUpdateMeta: function (d, clearPos) {
267 var metaUi = {},
268 ll;
269
270 // if we are not clearing the position data (unpinning),
271 // attach the x, y, (and equivalent longitude, latitude)...
272 if (!clearPos) {
273 ll = d.lngLatFromCoord([d.x, d.y]);
274 metaUi = {
275 x: d.x,
276 y: d.y,
277 equivLoc: {
278 lng: ll[0],
279 lat: ll[1]
280 }
281 };
282 }
283 d.metaUi = metaUi;
284 wss.sendEvent('updateMeta2', {
285 id: d.get('id'),
286 class: d.get('class'),
287 memento: metaUi
288 });
289 },
290 setDimensions: function () {
291 if (this.force) {
292 this.force.size(t2vs.getDimensions());
293 }
294 },
295 dragEnabled: function () {
296 var ev = d3.event.sourceEvent;
297 // nodeLock means we aren't allowing nodes to be dragged...
298 return !nodeLock && !this.zoomingOrPanning(ev);
299 },
300 zoomingOrPanning: function (ev) {
301 return ev.metaKey || ev.altKey;
302 },
303 atDragEnd: function (d) {
304 // once we've finished moving, pin the node in position
Steven Burrowsb11a8b82017-03-10 16:00:31 +0000305 d.fix(true);
Steven Burrowsaf96a212016-12-28 12:57:02 +0000306 instance.sendUpdateMeta(d);
Steven Burrowsaf96a212016-12-28 12:57:02 +0000307 t2ss.clickConsumed(true);
308 },
309 transitionDownRegion: function () {
310
311 this.prevForce.transition()
312 .duration(1500)
313 .style('opacity', 0)
314 .remove();
315
316 this.forceG
317 .style('opacity', 0)
318 .transition()
319 .delay(500)
320 .duration(500)
321 .style('opacity', 1);
322 },
323 transitionUpRegion: function () {
324 this.prevForce.transition()
325 .duration(1000)
326 .style('opacity', 0)
327 .remove();
328
329 this.forceG
330 .style('opacity', 0)
331 .transition()
332 .delay(500)
333 .duration(500)
334 .style('opacity', 1);
335 }
336 });
337
338 function getInstance(svg, forceG, uplink, dim, zoomer, opts) {
339 return instance || new Layout(svg, forceG, uplink, dim, zoomer, opts);
340 }
341
Steven Burrowsbd402842017-03-08 21:30:38 +0000342 return getInstance();
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100343 }
344 ]
345 );
346})();