blob: d2a49eaf9c4f9657f20409947881752148a6df1a [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
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100196 entering.filter('.device').each(t2d3.nodeEnter);
197 entering.filter('.sub-region').each(t2d3.nodeEnter);
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100198 entering.filter('.host').each(t2d3.hostEnter);
199
200 // operate on both existing and new nodes:
201 // node.filter('.device').each(function (device) {
202 // t2d3.updateDeviceColors(device);
203 // });
204 }
205
206 function _updateLinks() {
207
208 // var th = ts.theme();
209 var regionLinks = t2rs.regionLinks();
210
211 link = linkG.selectAll('.link')
212 .data(regionLinks, function (d) { return d.get('key'); });
213
214 // operate on existing links:
215 link.each(function (d) {
216 // this is supposed to be an existing link, but we have observed
217 // occasions (where links are deleted and added rapidly?) where
218 // the DOM element has not been defined. So protect against that...
219 if (d.el) {
220 restyleLinkElement(d, true);
221 }
222 });
223
224 // operate on entering links:
225 var entering = link.enter()
226 .append('line')
227 .call(calcPosition)
228 .attr({
229 x1: function (d) { return d.get('position').x1; },
230 y1: function (d) { return d.get('position').y1; },
231 x2: function (d) { return d.get('position').x2; },
232 y2: function (d) { return d.get('position').y2; },
233 stroke: linkConfig['light'].inColor,
234 'stroke-width': linkConfig.inWidth
235 });
236
237 entering.each(t2d3.linkEntering);
238
239 // operate on both existing and new links:
240 //link.each(...)
241
242 // add labels for how many links are in a thick line
243 // t2d3.applyNumLinkLabels(linkNums, numLinkLabelsG);
244
245 // apply or remove labels
246 // t2d3.applyLinkLabels();
247
248 // operate on exiting links:
249 link.exit()
250 .attr('stroke-dasharray', '3 3')
251 .attr('stroke', linkConfig['light'].outColor)
252 .style('opacity', 0.5)
253 .transition()
254 .duration(1500)
255 .attr({
256 'stroke-dasharray': '3 12',
257 'stroke-width': linkConfig.outWidth
258 })
259 .style('opacity', 0.0)
260 .remove();
261 }
262
263 function calcPosition() {
264 var lines = this,
265 linkSrcId,
266 linkNums = [];
267
268 lines.each(function (d) {
269 if (d.get('type') === 'hostLink') {
270 d.set('position', getDefaultPos(d));
271 }
272 });
273
274 function normalizeLinkSrc(link) {
275 // ensure source device is consistent across set of links
276 // temporary measure until link modeling is refactored
277 if (!linkSrcId) {
278 linkSrcId = link.source.id;
279 return false;
280 }
281
282 return link.source.id !== linkSrcId;
283 }
284
285 lines.each(function (d) {
286 d.set('position', getDefaultPos(d));
287 });
288 }
289
290 function getDefaultPos(link) {
291
292 return {
293 x1: link.get('source').x,
294 y1: link.get('source').y,
295 x2: link.get('target').x,
296 y2: link.get('target').y
297 };
298 }
299
300 function setDimensions() {
301 if (force) {
302 force.size(t2vs.getDimensions());
303 }
304 }
305
306
307 function start() {
308 force.start();
309 }
310
311 angular.module('ovTopo2')
312 .factory('Topo2LayoutService',
313 [
314 '$log', 'SvgUtilService', 'Topo2RegionService',
315 'Topo2D3Service', 'Topo2ViewService',
316
317 function (_$log_, _sus_, _t2rs_, _t2d3_, _t2vs_) {
318
319 $log = _$log_;
320 t2rs = _t2rs_;
321 t2d3 = _t2d3_;
322 t2vs = _t2vs_;
323 sus = _sus_;
324
325 return {
326 init: init,
327 update: update,
328 start: start,
329
330 setDimensions: setDimensions
331 }
332 }
333 ]
334 );
335})();