blob: 8cfaadfb639455d530d6b842dc3af382a96e070e [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
25 var $log, sus, t2rs, t2d3, t2vs;
26
27 var linkG, linkLabelG, numLinkLabelsG, nodeG, portLabelG;
28 var link, linkLabel, node;
29
30 var nodes, links;
31
32 var force;
33
34 // default settings for force layout
35 var defaultSettings = {
36 gravity: 0.4,
37 friction: 0.7,
38 charge: {
39 // note: key is node.class
40 device: -8000,
41 host: -5000,
42 _def_: -12000
43 },
44 linkDistance: {
45 // note: key is link.type
46 direct: 100,
47 optical: 120,
48 hostLink: 3,
49 _def_: 50
50 },
51 linkStrength: {
52 // note: key is link.type
53 // range: {0.0 ... 1.0}
54 //direct: 1.0,
55 //optical: 1.0,
56 //hostLink: 1.0,
57 _def_: 1.0
58 }
59 };
60
61 // configuration
62 var linkConfig = {
63 light: {
64 baseColor: '#939598',
65 inColor: '#66f',
66 outColor: '#f00'
67 },
68 dark: {
69 // TODO : theme
70 baseColor: '#939598',
71 inColor: '#66f',
72 outColor: '#f00'
73 },
74 inWidth: 12,
75 outWidth: 10
76 };
77
78 // internal state
79 var settings, // merged default settings and options
80 force, // force layout object
81 drag, // drag behavior handler
82 network = {
83 nodes: [],
84 links: [],
85 linksByDevice: {},
86 lookup: {},
87 revLinkToKey: {}
88 },
89 lu, // shorthand for lookup
90 rlk, // shorthand for revLinktoKey
91 showHosts = false, // whether hosts are displayed
92 showOffline = true, // whether offline devices are displayed
93 nodeLock = false, // whether nodes can be dragged or not (locked)
94 fTimer, // timer for delayed force layout
95 fNodesTimer, // timer for delayed nodes update
96 fLinksTimer, // timer for delayed links update
97 dim, // the dimensions of the force layout [w,h]
98 linkNums = []; // array of link number labels
99
100 var tickStuff = {
101 nodeAttr: {
102 transform: function (d) {
103 var dx = isNaN(d.x) ? 0 : d.x,
104 dy = isNaN(d.y) ? 0 : d.y;
105 return sus.translate(dx, dy);
106 }
107 },
108 linkAttr: {
109 x1: function (d) { return d.get('position').x1; },
110 y1: function (d) { return d.get('position').y1; },
111 x2: function (d) { return d.get('position').x2; },
112 y2: function (d) { return d.get('position').y2; }
113 },
114 linkLabelAttr: {
115 transform: function (d) {
116 var lnk = tms.findLinkById(d.get('key'));
117 if (lnk) {
118 return t2d3.transformLabel(lnk.get('position'));
119 }
120 }
121 }
122 };
123
124 function init(_svg_, forceG, _uplink_, _dim_, opts) {
125
126 $log.debug("Initialising Topology Layout");
127
128 settings = angular.extend({}, defaultSettings, opts);
129
130 linkG = forceG.append('g').attr('id', 'topo-links');
131 linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels');
132 numLinkLabelsG = forceG.append('g').attr('id', 'topo-numLinkLabels');
133 nodeG = forceG.append('g').attr('id', 'topo-nodes');
134 portLabelG = forceG.append('g').attr('id', 'topo-portLabels');
135
136 link = linkG.selectAll('.link');
137 linkLabel = linkLabelG.selectAll('.linkLabel');
138 node = nodeG.selectAll('.node');
139
140 force = d3.layout.force()
141 .size(t2vs.getDimensions())
142 .nodes(t2rs.regionNodes())
143 .links(t2rs.regionLinks())
144 .gravity(settings.gravity)
145 .friction(settings.friction)
146 .charge(settings.charge._def_)
147 .linkDistance(settings.linkDistance._def_)
148 .linkStrength(settings.linkStrength._def_)
149 .on('tick', tick);
150 }
151
152 function tick() {
153 // guard against null (which can happen when our view pages out)...
154 if (node && node.size()) {
155 node.attr(tickStuff.nodeAttr);
156 }
157 if (link && link.size()) {
158 link.call(calcPosition)
159 .attr(tickStuff.linkAttr);
160 // t2d3.applyNumLinkLabels(linkNums, numLinkLabelsG);
161 }
162 if (linkLabel && linkLabel.size()) {
163 linkLabel.attr(tickStuff.linkLabelAttr);
164 }
165 }
166
167 function update() {
168 _updateNodes();
169 _updateLinks();
170 }
171
172 function _updateNodes() {
173
174 var regionNodes = t2rs.regionNodes();
175
176 // select all the nodes in the layout:
177 node = nodeG.selectAll('.node')
178 .data(regionNodes, function (d) { return d.get('id'); });
179
180 var entering = node.enter()
181 .append('g')
182 .attr({
183 id: function (d) { return sus.safeId(d.get('id')); },
184 class: function (d) { return d.svgClassName() },
185 transform: function (d) {
186 // Need to guard against NaN here ??
187 return sus.translate(d.node.x, d.node.y);
188 },
189 opacity: 0
190 })
191 // .on('mouseover', tss.nodeMouseOver)
192 // .on('mouseout', tss.nodeMouseOut)
193 .transition()
194 .attr('opacity', 1);
195
196 entering.filter('.device').each(t2d3.deviceEnter);
197 entering.filter('.host').each(t2d3.hostEnter);
198
199 // operate on both existing and new nodes:
200 // node.filter('.device').each(function (device) {
201 // t2d3.updateDeviceColors(device);
202 // });
203 }
204
205 function _updateLinks() {
206
207 // var th = ts.theme();
208 var regionLinks = t2rs.regionLinks();
209
210 link = linkG.selectAll('.link')
211 .data(regionLinks, function (d) { return d.get('key'); });
212
213 // operate on existing links:
214 link.each(function (d) {
215 // this is supposed to be an existing link, but we have observed
216 // occasions (where links are deleted and added rapidly?) where
217 // the DOM element has not been defined. So protect against that...
218 if (d.el) {
219 restyleLinkElement(d, true);
220 }
221 });
222
223 // operate on entering links:
224 var entering = link.enter()
225 .append('line')
226 .call(calcPosition)
227 .attr({
228 x1: function (d) { return d.get('position').x1; },
229 y1: function (d) { return d.get('position').y1; },
230 x2: function (d) { return d.get('position').x2; },
231 y2: function (d) { return d.get('position').y2; },
232 stroke: linkConfig['light'].inColor,
233 'stroke-width': linkConfig.inWidth
234 });
235
236 entering.each(t2d3.linkEntering);
237
238 // operate on both existing and new links:
239 //link.each(...)
240
241 // add labels for how many links are in a thick line
242 // t2d3.applyNumLinkLabels(linkNums, numLinkLabelsG);
243
244 // apply or remove labels
245 // t2d3.applyLinkLabels();
246
247 // operate on exiting links:
248 link.exit()
249 .attr('stroke-dasharray', '3 3')
250 .attr('stroke', linkConfig['light'].outColor)
251 .style('opacity', 0.5)
252 .transition()
253 .duration(1500)
254 .attr({
255 'stroke-dasharray': '3 12',
256 'stroke-width': linkConfig.outWidth
257 })
258 .style('opacity', 0.0)
259 .remove();
260 }
261
262 function calcPosition() {
263 var lines = this,
264 linkSrcId,
265 linkNums = [];
266
267 lines.each(function (d) {
268 if (d.get('type') === 'hostLink') {
269 d.set('position', getDefaultPos(d));
270 }
271 });
272
273 function normalizeLinkSrc(link) {
274 // ensure source device is consistent across set of links
275 // temporary measure until link modeling is refactored
276 if (!linkSrcId) {
277 linkSrcId = link.source.id;
278 return false;
279 }
280
281 return link.source.id !== linkSrcId;
282 }
283
284 lines.each(function (d) {
285 d.set('position', getDefaultPos(d));
286 });
287 }
288
289 function getDefaultPos(link) {
290
291 return {
292 x1: link.get('source').x,
293 y1: link.get('source').y,
294 x2: link.get('target').x,
295 y2: link.get('target').y
296 };
297 }
298
299 function setDimensions() {
300 if (force) {
301 force.size(t2vs.getDimensions());
302 }
303 }
304
305
306 function start() {
307 force.start();
308 }
309
310 angular.module('ovTopo2')
311 .factory('Topo2LayoutService',
312 [
313 '$log', 'SvgUtilService', 'Topo2RegionService',
314 'Topo2D3Service', 'Topo2ViewService',
315
316 function (_$log_, _sus_, _t2rs_, _t2d3_, _t2vs_) {
317
318 $log = _$log_;
319 t2rs = _t2rs_;
320 t2d3 = _t2d3_;
321 t2vs = _t2vs_;
322 sus = _sus_;
323
324 return {
325 init: init,
326 update: update,
327 start: start,
328
329 setDimensions: setDimensions
330 }
331 }
332 ]
333 );
334})();