Simon Hunt

GUI -- TopoView - Migrated helper functions to topoModel.js.

- moved randomized functions to random.js (so we can mock them).

Change-Id: Ic56ce64c036d36f34798f0df9f03a7d09335a2ab
1 +/*
2 + * Copyright 2014,2015 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 -- Random -- Encapsulated randomness
19 + */
20 +(function () {
21 + 'use strict';
22 +
23 + var $log, fs;
24 +
25 + var halfRoot2 = 0.7071;
26 +
27 + // given some value, s, returns an integer between -s/2 and s/2
28 + // e.g. s = 100; result in the range [-50..50)
29 + function spread(s) {
30 + return Math.floor((Math.random() * s) - s / 2);
31 + }
32 +
33 + // for a given dimension, d, choose a random value somewhere between
34 + // 0 and d where the value is within (d / (2 * sqrt(2))) of d/2.
35 + function randDim(d) {
36 + return d / 2 + spread(d * halfRoot2);
37 + }
38 +
39 + angular.module('onosUtil')
40 + .factory('RandomService', ['$log', 'FnService',
41 +
42 + function (_$log_, _fs_) {
43 + $log = _$log_;
44 + fs = _fs_;
45 +
46 + return {
47 + spread: spread,
48 + randDim: randDim
49 + };
50 + }]);
51 +}());
...@@ -35,6 +35,7 @@ ...@@ -35,6 +35,7 @@
35 35
36 <script src="fw/util/util.js"></script> 36 <script src="fw/util/util.js"></script>
37 <script src="fw/util/fn.js"></script> 37 <script src="fw/util/fn.js"></script>
38 + <script src="fw/util/random.js"></script>
38 <script src="fw/util/theme.js"></script> 39 <script src="fw/util/theme.js"></script>
39 <script src="fw/util/keys.js"></script> 40 <script src="fw/util/keys.js"></script>
40 41
...@@ -81,6 +82,7 @@ ...@@ -81,6 +82,7 @@
81 <script src="view/topo/topo.js"></script> 82 <script src="view/topo/topo.js"></script>
82 <script src="view/topo/topoEvent.js"></script> 83 <script src="view/topo/topoEvent.js"></script>
83 <script src="view/topo/topoForce.js"></script> 84 <script src="view/topo/topoForce.js"></script>
85 + <script src="view/topo/topoModel.js"></script>
84 <script src="view/topo/topoPanel.js"></script> 86 <script src="view/topo/topoPanel.js"></script>
85 <script src="view/topo/topoInst.js"></script> 87 <script src="view/topo/topoInst.js"></script>
86 <script src="view/device/device.js"></script> 88 <script src="view/device/device.js"></script>
......
...@@ -130,8 +130,8 @@ ...@@ -130,8 +130,8 @@
130 130
131 131
132 // callback invoked when the SVG view has been resized.. 132 // callback invoked when the SVG view has been resized..
133 - function svgResized(dim) { 133 + function svgResized(s) {
134 - tfs.resize(dim); 134 + tfs.newDim([s.width, s.height]);
135 } 135 }
136 136
137 // --- Background Map ------------------------------------------------ 137 // --- Background Map ------------------------------------------------
...@@ -203,6 +203,7 @@ ...@@ -203,6 +203,7 @@
203 _ks_, _zs_, _gs_, _ms_, _sus_, tes, _tfs_, tps, _tis_) { 203 _ks_, _zs_, _gs_, _ms_, _sus_, tes, _tfs_, tps, _tis_) {
204 var self = this, 204 var self = this,
205 projection, 205 projection,
206 + dim,
206 uplink = { 207 uplink = {
207 // provides function calls back into this space 208 // provides function calls back into this space
208 showNoDevs: showNoDevs, 209 showNoDevs: showNoDevs,
...@@ -230,6 +231,7 @@ ...@@ -230,6 +231,7 @@
230 tes.closeSock(); 231 tes.closeSock();
231 tps.destroyPanels(); 232 tps.destroyPanels();
232 tis.destroyInst(); 233 tis.destroyInst();
234 + tfs.destroyForce();
233 }); 235 });
234 236
235 // svg layer and initialization of components 237 // svg layer and initialization of components
...@@ -237,6 +239,7 @@ ...@@ -237,6 +239,7 @@
237 svg = ovtopo.select('svg'); 239 svg = ovtopo.select('svg');
238 // set the svg size to match that of the window, less the masthead 240 // set the svg size to match that of the window, less the masthead
239 svg.attr(fs.windowSize(mast.mastHeight())); 241 svg.attr(fs.windowSize(mast.mastHeight()));
242 + dim = [svg.attr('width'), svg.attr('height')];
240 243
241 setUpKeys(); 244 setUpKeys();
242 setUpDefs(); 245 setUpDefs();
...@@ -250,7 +253,7 @@ ...@@ -250,7 +253,7 @@
250 ); 253 );
251 254
252 forceG = zoomLayer.append('g').attr('id', 'topo-force'); 255 forceG = zoomLayer.append('g').attr('id', 'topo-force');
253 - tfs.initForce(forceG, uplink, svg.attr('width'), svg.attr('height')); 256 + tfs.initForce(forceG, uplink, dim);
254 tis.initInst(); 257 tis.initInst();
255 tps.initPanels(); 258 tps.initPanels();
256 tes.openSock(); 259 tes.openSock();
......
1 +/*
2 + * Copyright 2015 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 Model Module.
19 + Auxiliary functions for the model of the topology; that is, our internal
20 + representations of devices, hosts, links, etc.
21 + */
22 +
23 +(function () {
24 + 'use strict';
25 +
26 + // injected refs
27 + var $log, fs, rnd, api;
28 +
29 + var dim; // dimensions of layout, as [w,h]
30 +
31 + // configuration 'constants'
32 + var defaultLinkType = 'direct',
33 + nearDist = 15;
34 +
35 +
36 + function coordFromLngLat(loc) {
37 + var p = api.projection();
38 + return p ? p([loc.lng, loc.lat]) : [0, 0];
39 + }
40 +
41 + function lngLatFromCoord(coord) {
42 + var p = api.projection();
43 + return p ? p.invert(coord) : [0, 0];
44 + }
45 +
46 + function positionNode(node, forUpdate) {
47 + var meta = node.metaUi,
48 + x = meta && meta.x,
49 + y = meta && meta.y,
50 + xy;
51 +
52 + // If we have [x,y] already, use that...
53 + if (x && y) {
54 + node.fixed = true;
55 + node.px = node.x = x;
56 + node.py = node.y = y;
57 + return;
58 + }
59 +
60 + var location = node.location,
61 + coord;
62 +
63 + if (location && location.type === 'latlng') {
64 + coord = coordFromLngLat(location);
65 + node.fixed = true;
66 + node.px = node.x = coord[0];
67 + node.py = node.y = coord[1];
68 + return true;
69 + }
70 +
71 + // if this is a node update (not a node add).. skip randomizer
72 + if (forUpdate) {
73 + return;
74 + }
75 +
76 + // Note: Placing incoming unpinned nodes at exactly the same point
77 + // (center of the view) causes them to explode outwards when
78 + // the force layout kicks in. So, we spread them out a bit
79 + // initially, to provide a more serene layout convergence.
80 + // Additionally, if the node is a host, we place it near
81 + // the device it is connected to.
82 +
83 + function rand() {
84 + return {
85 + x: rnd.randDim(dim[0]),
86 + y: rnd.randDim(dim[1])
87 + };
88 + }
89 +
90 + function near(node) {
91 + return {
92 + x: node.x + nearDist + rnd.spread(nearDist),
93 + y: node.y + nearDist + rnd.spread(nearDist)
94 + };
95 + }
96 +
97 + function getDevice(cp) {
98 + var d = api.lookup[cp.device];
99 + return d || rand();
100 + }
101 +
102 + xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
103 + angular.extend(node, xy);
104 + }
105 +
106 + function mkSvgCls(dh, t, on) {
107 + var ndh = 'node ' + dh,
108 + ndht = t ? ndh + ' ' + t : ndh;
109 + return on ? ndht + ' online' : ndht;
110 + }
111 +
112 + function createDeviceNode(device) {
113 + var node = device;
114 +
115 + // Augment as needed...
116 + node.class = 'device';
117 + node.svgClass = mkSvgCls('device', device.type, device.online);
118 + positionNode(node);
119 + return node;
120 + }
121 +
122 + function createHostNode(host) {
123 + var node = host;
124 +
125 + // Augment as needed...
126 + node.class = 'host';
127 + if (!node.type) {
128 + node.type = 'endstation';
129 + }
130 + node.svgClass = mkSvgCls('host', node.type);
131 + positionNode(node);
132 + return node;
133 + }
134 +
135 + function createHostLink(host) {
136 + var src = host.id,
137 + dst = host.cp.device,
138 + id = host.ingress,
139 + lnk = linkEndPoints(src, dst);
140 +
141 + if (!lnk) {
142 + return null;
143 + }
144 +
145 + // Synthesize link ...
146 + angular.extend(lnk, {
147 + key: id,
148 + class: 'link',
149 +
150 + type: function () { return 'hostLink'; },
151 + online: function () {
152 + // hostlink target is edge switch
153 + return lnk.target.online;
154 + },
155 + linkWidth: function () { return 1; }
156 + });
157 + return lnk;
158 + }
159 +
160 + function createLink(link) {
161 + var lnk = linkEndPoints(link.src, link.dst);
162 +
163 + if (!lnk) {
164 + return null;
165 + }
166 +
167 + angular.extend(lnk, {
168 + key: link.id,
169 + class: 'link',
170 + fromSource: link,
171 +
172 + // functions to aggregate dual link state
173 + type: function () {
174 + var s = lnk.fromSource,
175 + t = lnk.fromTarget;
176 + return (s && s.type) || (t && t.type) || defaultLinkType;
177 + },
178 + online: function () {
179 + var s = lnk.fromSource,
180 + t = lnk.fromTarget,
181 + both = lnk.source.online && lnk.target.online;
182 + return both && ((s && s.online) || (t && t.online));
183 + },
184 + linkWidth: function () {
185 + var s = lnk.fromSource,
186 + t = lnk.fromTarget,
187 + ws = (s && s.linkWidth) || 0,
188 + wt = (t && t.linkWidth) || 0;
189 + return Math.max(ws, wt);
190 + }
191 + });
192 + return lnk;
193 + }
194 +
195 +
196 + function linkEndPoints(srcId, dstId) {
197 + var srcNode = api.lookup[srcId],
198 + dstNode = api.lookup[dstId],
199 + sMiss = !srcNode ? missMsg('src', srcId) : '',
200 + dMiss = !dstNode ? missMsg('dst', dstId) : '';
201 +
202 + if (sMiss || dMiss) {
203 + $log.error('Node(s) not on map for link:' + sMiss + dMiss);
204 + //logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
205 + return null;
206 + }
207 + return {
208 + source: srcNode,
209 + target: dstNode,
210 + x1: srcNode.x,
211 + y1: srcNode.y,
212 + x2: dstNode.x,
213 + y2: dstNode.y
214 + };
215 + }
216 +
217 + function missMsg(what, id) {
218 + return '\n[' + what + '] "' + id + '" missing';
219 + }
220 +
221 + // ==========================
222 + // Module definition
223 +
224 + angular.module('ovTopo')
225 + .factory('TopoModelService',
226 + ['$log', 'FnService', 'RandomService',
227 +
228 + function (_$log_, _fs_, _rnd_) {
229 + $log = _$log_;
230 + fs = _fs_;
231 + rnd = _rnd_;
232 +
233 + function initModel(_api_, _dim_) {
234 + api = _api_;
235 + dim = _dim_;
236 + }
237 +
238 + function newDim(_dim_) {
239 + dim = _dim_;
240 + }
241 +
242 + return {
243 + initModel: initModel,
244 + newDim: newDim,
245 +
246 + positionNode: positionNode,
247 + createDeviceNode: createDeviceNode,
248 + createHostNode: createHostNode,
249 + createHostLink: createHostLink,
250 + createLink: createLink,
251 + coordFromLngLat: coordFromLngLat,
252 + lngLatFromCoord: lngLatFromCoord,
253 + }
254 + }]);
255 +}());
1 +/*
2 + * Copyright 2014,2015 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 -- Util -- Random Service - Unit Tests
19 + */
20 +describe('factory: fw/util/random.js', function() {
21 + var rnd, $log, fs;
22 +
23 + beforeEach(module('onosUtil'));
24 +
25 + beforeEach(inject(function (RandomService, _$log_, FnService) {
26 + rnd = RandomService;
27 + $log = _$log_;
28 + fs = FnService;
29 + }));
30 +
31 + // interesting use of a custom matcher...
32 + beforeEach(function () {
33 + jasmine.addMatchers({
34 + toBeWithinOf: function () {
35 + return {
36 + compare: function (actual, distance, base) {
37 + var lower = base - distance,
38 + upper = base + distance,
39 + result = {};
40 +
41 + result.pass = Math.abs(actual - base) <= distance;
42 +
43 + if (result.pass) {
44 + // for negation with ".not"
45 + result.message = 'Expected ' + actual +
46 + ' to be outside ' + lower + ' and ' +
47 + upper + ' (inclusive)';
48 + } else {
49 + result.message = 'Expected ' + actual +
50 + ' to be between ' + lower + ' and ' +
51 + upper + ' (inclusive)';
52 + }
53 + return result;
54 + }
55 + }
56 + }
57 + });
58 + });
59 +
60 + it('should define RandomService', function () {
61 + expect(rnd).toBeDefined();
62 + });
63 +
64 + it('should define api functions', function () {
65 + expect(fs.areFunctions(rnd, [
66 + 'spread', 'randDim'
67 + ])).toBeTruthy();
68 + });
69 +
70 + // really, can only do this heuristically.. hope this doesn't break
71 + it('should spread results across the range', function () {
72 + var load = 1000,
73 + s = 12,
74 + low = 0,
75 + high = 0,
76 + i, res,
77 + which = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
78 + minCount = load / s * 0.5; // generous error
79 +
80 + for (i=0; i<load; i++) {
81 + res = rnd.spread(s);
82 + if (res < low) low = res;
83 + if (res > high) high = res;
84 + which[res + s/2]++;
85 + }
86 + expect(low).toBe(-6);
87 + expect(high).toBe(5);
88 +
89 + // check we got a good number of hits in each bucket
90 + for (i=0; i<s; i++) {
91 + expect(which[i]).toBeGreaterThan(minCount);
92 + }
93 + });
94 +
95 + // really, can only do this heuristically.. hope this doesn't break
96 + it('should choose results across the dimension', function () {
97 + var load = 1000,
98 + dim = 100,
99 + low = 999,
100 + high = 0,
101 + i, res;
102 +
103 + for (i=0; i<load; i++) {
104 + res = rnd.randDim(dim);
105 + if (res < low) low = res;
106 + if (res > high) high = res;
107 + expect(res).toBeWithinOf(36, 50);
108 + }
109 + });
110 +});
...@@ -29,7 +29,7 @@ describe('factory: fw/util/theme.js', function() { ...@@ -29,7 +29,7 @@ describe('factory: fw/util/theme.js', function() {
29 ts.init(); 29 ts.init();
30 })); 30 }));
31 31
32 - it('should define MapService', function () { 32 + it('should define ThemeService', function () {
33 expect(ts).toBeDefined(); 33 expect(ts).toBeDefined();
34 }); 34 });
35 35
......
...@@ -34,8 +34,11 @@ describe('factory: view/topo/topoForce.js', function() { ...@@ -34,8 +34,11 @@ describe('factory: view/topo/topoForce.js', function() {
34 34
35 it('should define api functions', function () { 35 it('should define api functions', function () {
36 expect(fs.areFunctions(tfs, [ 36 expect(fs.areFunctions(tfs, [
37 - 'initForce', 'resize', 'updateDeviceColors', 37 + 'initForce', 'newDim', 'destroyForce',
38 - 'toggleHosts', 'toggleOffline','cycleDeviceLabels', 'unpin', 38 +
39 + 'updateDeviceColors', 'toggleHosts', 'toggleOffline',
40 + 'cycleDeviceLabels', 'unpin',
41 +
39 'addDevice', 'updateDevice', 'removeDevice', 42 'addDevice', 'updateDevice', 'removeDevice',
40 'addHost', 'updateHost', 'removeHost', 43 'addHost', 'updateHost', 'removeHost',
41 'addLink', 'updateLink', 'removeLink' 44 'addLink', 'updateLink', 'removeLink'
......
This diff is collapsed. Click to expand it.