Committed by
Gerrit Code Review
GUI -- Migrating the add/update device functionality to the Topology View. (WIP)
- still a lot of work to do. Change-Id: I0453b7e2ec20a8a8149fd9d6440a13a3d43fbfd6
Showing
9 changed files
with
832 additions
and
56 deletions
... | @@ -26,8 +26,8 @@ | ... | @@ -26,8 +26,8 @@ |
26 | cornerSize = vboxSize / 10, | 26 | cornerSize = vboxSize / 10, |
27 | viewBox = '0 0 ' + vboxSize + ' ' + vboxSize; | 27 | viewBox = '0 0 ' + vboxSize + ' ' + vboxSize; |
28 | 28 | ||
29 | - // maps icon id to the glyph id it uses. | 29 | + // Maps icon ID to the glyph ID it uses. |
30 | - // note: icon id maps to a CSS class for styling that icon | 30 | + // NOTE: icon ID maps to a CSS class for styling that icon |
31 | var glyphMapping = { | 31 | var glyphMapping = { |
32 | deviceOnline: 'checkMark', | 32 | deviceOnline: 'checkMark', |
33 | deviceOffline: 'xMark', | 33 | deviceOffline: 'xMark', |
... | @@ -36,6 +36,8 @@ | ... | @@ -36,6 +36,8 @@ |
36 | tableColSortNone: '-' | 36 | tableColSortNone: '-' |
37 | }; | 37 | }; |
38 | 38 | ||
39 | + | ||
40 | + | ||
39 | function ensureIconLibDefs() { | 41 | function ensureIconLibDefs() { |
40 | var body = d3.select('body'), | 42 | var body = d3.select('body'), |
41 | svg = body.select('svg#IconLibDefs'), | 43 | svg = body.select('svg#IconLibDefs'), |
... | @@ -48,13 +50,6 @@ | ... | @@ -48,13 +50,6 @@ |
48 | return svg.select('defs'); | 50 | return svg.select('defs'); |
49 | } | 51 | } |
50 | 52 | ||
51 | - angular.module('onosSvg') | ||
52 | - .factory('IconService', ['$log', 'FnService', 'GlyphService', | ||
53 | - function (_$log_, _fs_, _gs_) { | ||
54 | - $log = _$log_; | ||
55 | - fs = _fs_; | ||
56 | - gs = _gs_; | ||
57 | - | ||
58 | // div is a D3 selection of the <DIV> element into which icon should load | 53 | // div is a D3 selection of the <DIV> element into which icon should load |
59 | // iconCls is the CSS class used to identify the icon | 54 | // iconCls is the CSS class used to identify the icon |
60 | // size is dimension of icon in pixels. Defaults to 20. | 55 | // size is dimension of icon in pixels. Defaults to 20. |
... | @@ -103,9 +98,73 @@ | ... | @@ -103,9 +98,73 @@ |
103 | loadIcon(div, iconCls, size, true); | 98 | loadIcon(div, iconCls, size, true); |
104 | } | 99 | } |
105 | 100 | ||
101 | + | ||
102 | + // configuration for device and host icons in the topology view | ||
103 | + var config = { | ||
104 | + device: { | ||
105 | + dim: 36, | ||
106 | + rx: 4 | ||
107 | + }, | ||
108 | + host: { | ||
109 | + radius: { | ||
110 | + noGlyph: 9, | ||
111 | + withGlyph: 14 | ||
112 | + }, | ||
113 | + glyphed: { | ||
114 | + endstation: 1, | ||
115 | + bgpSpeaker: 1, | ||
116 | + router: 1 | ||
117 | + } | ||
118 | + } | ||
119 | + }; | ||
120 | + | ||
121 | + | ||
122 | + // Adds a device icon to the specified element, using the given glyph. | ||
123 | + // Returns the D3 selection of the icon. | ||
124 | + function addDeviceIcon(elem, glyphId) { | ||
125 | + var cfg = config.device, | ||
126 | + g = elem.append('g') | ||
127 | + .attr('class', 'svgIcon deviceIcon'); | ||
128 | + | ||
129 | + g.append('rect').attr({ | ||
130 | + x: 0, | ||
131 | + y: 0, | ||
132 | + rx: cfg.rx, | ||
133 | + width: cfg.dim, | ||
134 | + height: cfg.dim | ||
135 | + }); | ||
136 | + | ||
137 | + g.append('use').attr({ | ||
138 | + 'xlink:href': '#' + glyphId, | ||
139 | + width: cfg.dim, | ||
140 | + height: cfg.dim | ||
141 | + }); | ||
142 | + | ||
143 | + g.dim = cfg.dim; | ||
144 | + return g; | ||
145 | + } | ||
146 | + | ||
147 | + function addHostIcon(elem, glyphId) { | ||
148 | + // TODO: | ||
149 | + } | ||
150 | + | ||
151 | + | ||
152 | + // ========================= | ||
153 | + // === DEFINE THE MODULE | ||
154 | + | ||
155 | + angular.module('onosSvg') | ||
156 | + .factory('IconService', ['$log', 'FnService', 'GlyphService', | ||
157 | + function (_$log_, _fs_, _gs_) { | ||
158 | + $log = _$log_; | ||
159 | + fs = _fs_; | ||
160 | + gs = _gs_; | ||
161 | + | ||
106 | return { | 162 | return { |
107 | loadIcon: loadIcon, | 163 | loadIcon: loadIcon, |
108 | - loadEmbeddedIcon: loadEmbeddedIcon | 164 | + loadEmbeddedIcon: loadEmbeddedIcon, |
165 | + addDeviceIcon: addDeviceIcon, | ||
166 | + addHostIcon: addHostIcon, | ||
167 | + iconConfig: function () { return config; } | ||
109 | }; | 168 | }; |
110 | }]); | 169 | }]); |
111 | 170 | ... | ... |
... | @@ -22,28 +22,25 @@ | ... | @@ -22,28 +22,25 @@ |
22 | The Map Service provides a simple API for loading geographical maps into | 22 | The Map Service provides a simple API for loading geographical maps into |
23 | an SVG layer. For example, as a background to the Topology View. | 23 | an SVG layer. For example, as a background to the Topology View. |
24 | 24 | ||
25 | - e.g. var ok = MapService.loadMapInto(svgLayer, '*continental-us'); | 25 | + e.g. var promise = MapService.loadMapInto(svgLayer, '*continental-us'); |
26 | 26 | ||
27 | The Map Service makes use of the GeoDataService to load the required data | 27 | The Map Service makes use of the GeoDataService to load the required data |
28 | from the server and to create the appropriate geographical projection. | 28 | from the server and to create the appropriate geographical projection. |
29 | 29 | ||
30 | + A promise is returned to the caller, which is resolved with the | ||
31 | + map projection once created. | ||
30 | */ | 32 | */ |
31 | 33 | ||
32 | (function () { | 34 | (function () { |
33 | 'use strict'; | 35 | 'use strict'; |
34 | 36 | ||
35 | // injected references | 37 | // injected references |
36 | - var $log, fs, gds; | 38 | + var $log, $q, fs, gds; |
37 | - | ||
38 | - angular.module('onosSvg') | ||
39 | - .factory('MapService', ['$log', 'FnService', 'GeoDataService', | ||
40 | - function (_$log_, _fs_, _gds_) { | ||
41 | - $log = _$log_; | ||
42 | - fs = _fs_; | ||
43 | - gds = _gds_; | ||
44 | 39 | ||
45 | function loadMapInto(mapLayer, id, opts) { | 40 | function loadMapInto(mapLayer, id, opts) { |
46 | - var promise = gds.fetchTopoData(id); | 41 | + var promise = gds.fetchTopoData(id), |
42 | + deferredProjection = $q.defer(); | ||
43 | + | ||
47 | if (!promise) { | 44 | if (!promise) { |
48 | $log.warn('Failed to load map: ' + id); | 45 | $log.warn('Failed to load map: ' + id); |
49 | return false; | 46 | return false; |
... | @@ -52,15 +49,26 @@ | ... | @@ -52,15 +49,26 @@ |
52 | promise.then(function () { | 49 | promise.then(function () { |
53 | var gen = gds.createPathGenerator(promise.topodata, opts); | 50 | var gen = gds.createPathGenerator(promise.topodata, opts); |
54 | 51 | ||
52 | + deferredProjection.resolve(gen.settings.projection); | ||
53 | + | ||
55 | mapLayer.selectAll('path') | 54 | mapLayer.selectAll('path') |
56 | .data(gen.geodata.features) | 55 | .data(gen.geodata.features) |
57 | .enter() | 56 | .enter() |
58 | .append('path') | 57 | .append('path') |
59 | .attr('d', gen.pathgen); | 58 | .attr('d', gen.pathgen); |
60 | }); | 59 | }); |
61 | - return true; | 60 | + return deferredProjection.promise; |
62 | } | 61 | } |
63 | 62 | ||
63 | + | ||
64 | + angular.module('onosSvg') | ||
65 | + .factory('MapService', ['$log', '$q', 'FnService', 'GeoDataService', | ||
66 | + function (_$log_, _$q_, _fs_, _gds_) { | ||
67 | + $log = _$log_; | ||
68 | + $q = _$q_; | ||
69 | + fs = _fs_; | ||
70 | + gds = _gds_; | ||
71 | + | ||
64 | return { | 72 | return { |
65 | loadMapInto: loadMapInto | 73 | loadMapInto: loadMapInto |
66 | }; | 74 | }; | ... | ... |
... | @@ -240,13 +240,18 @@ | ... | @@ -240,13 +240,18 @@ |
240 | el.style('visibility', (b ? 'visible' : 'hidden')); | 240 | el.style('visibility', (b ? 'visible' : 'hidden')); |
241 | } | 241 | } |
242 | 242 | ||
243 | + function safeId(s) { | ||
244 | + return s.replace(/[^a-z0-9]/gi, '-'); | ||
245 | + } | ||
246 | + | ||
243 | return { | 247 | return { |
244 | createDragBehavior: createDragBehavior, | 248 | createDragBehavior: createDragBehavior, |
245 | loadGlow: loadGlow, | 249 | loadGlow: loadGlow, |
246 | cat7: cat7, | 250 | cat7: cat7, |
247 | translate: translate, | 251 | translate: translate, |
248 | stripPx: stripPx, | 252 | stripPx: stripPx, |
249 | - makeVisible: makeVisible | 253 | + makeVisible: makeVisible, |
254 | + safeId: safeId | ||
250 | }; | 255 | }; |
251 | }]); | 256 | }]); |
252 | }()); | 257 | }()); | ... | ... |
... | @@ -245,3 +245,94 @@ | ... | @@ -245,3 +245,94 @@ |
245 | /* TODO: add blue glow */ | 245 | /* TODO: add blue glow */ |
246 | /*filter: url(#blue-glow);*/ | 246 | /*filter: url(#blue-glow);*/ |
247 | } | 247 | } |
248 | + | ||
249 | + | ||
250 | +/* --- Topo Nodes --- */ | ||
251 | + | ||
252 | +#ov-topo svg .node { | ||
253 | + cursor: pointer; | ||
254 | +} | ||
255 | + | ||
256 | +#ov-topo svg .node.selected rect, | ||
257 | +#ov-topo svg .node.selected circle { | ||
258 | + fill: #f90; | ||
259 | + /* TODO: add blue glow filter */ | ||
260 | + /*filter: url(#blue-glow);*/ | ||
261 | +} | ||
262 | + | ||
263 | +#ov-topo svg .node text { | ||
264 | + pointer-events: none; | ||
265 | +} | ||
266 | + | ||
267 | +/* Device Nodes */ | ||
268 | + | ||
269 | +#ov-topo svg .node.device { | ||
270 | +} | ||
271 | + | ||
272 | +#ov-topo svg .node.device rect { | ||
273 | + stroke-width: 1.5; | ||
274 | +} | ||
275 | + | ||
276 | +#ov-topo svg .node.device.fixed rect { | ||
277 | + stroke-width: 1.5; | ||
278 | + stroke: #ccc; | ||
279 | +} | ||
280 | + | ||
281 | +/* note: device is offline without the 'online' class */ | ||
282 | +#ov-topo svg .node.device { | ||
283 | + fill: #777; | ||
284 | +} | ||
285 | + | ||
286 | +#ov-topo svg .node.device.online { | ||
287 | + fill: #6e7fa3; | ||
288 | +} | ||
289 | + | ||
290 | +/* note: device is offline without the 'online' class */ | ||
291 | +#ov-topo svg .node.device text { | ||
292 | + fill: #bbb; | ||
293 | + font: 10pt sans-serif; | ||
294 | +} | ||
295 | + | ||
296 | +#ov-topo svg .node.device.online text { | ||
297 | + fill: white; | ||
298 | +} | ||
299 | + | ||
300 | +#ov-topo svg .node.device .svgIcon rect { | ||
301 | + fill: #aaa; | ||
302 | +} | ||
303 | +#ov-topo svg .node.device .svgIcon use { | ||
304 | + fill: #777; | ||
305 | +} | ||
306 | +#ov-topo svg .node.device.selected .svgIcon rect { | ||
307 | + fill: #f90; | ||
308 | +} | ||
309 | +#ov-topo svg .node.device.online .svgIcon rect { | ||
310 | + fill: #ccc; | ||
311 | +} | ||
312 | +#ov-topo svg .node.device.online .svgIcon use { | ||
313 | + fill: #000; | ||
314 | +} | ||
315 | +#ov-topo svg .node.device.online.selected .svgIcon rect { | ||
316 | + fill: #f90; | ||
317 | +} | ||
318 | + | ||
319 | + | ||
320 | +/* Host Nodes */ | ||
321 | + | ||
322 | +#ov-topo svg .node.host { | ||
323 | + stroke: #000; | ||
324 | +} | ||
325 | + | ||
326 | +#ov-topo svg .node.host text { | ||
327 | + fill: #846; | ||
328 | + stroke: none; | ||
329 | + font: 9pt sans-serif; | ||
330 | +} | ||
331 | + | ||
332 | +svg .node.host circle { | ||
333 | + stroke: #000; | ||
334 | + fill: #edb; | ||
335 | +} | ||
336 | + | ||
337 | + | ||
338 | + | ... | ... |
... | @@ -28,7 +28,7 @@ | ... | @@ -28,7 +28,7 @@ |
28 | ]; | 28 | ]; |
29 | 29 | ||
30 | // references to injected services etc. | 30 | // references to injected services etc. |
31 | - var $log, fs, ks, zs, gs, ms, sus, tfs; | 31 | + var $log, fs, ks, zs, gs, ms, sus, tfs, tis; |
32 | 32 | ||
33 | // DOM elements | 33 | // DOM elements |
34 | var ovtopo, svg, defs, zoomLayer, mapG, forceG, noDevsLayer; | 34 | var ovtopo, svg, defs, zoomLayer, mapG, forceG, noDevsLayer; |
... | @@ -41,20 +41,61 @@ | ... | @@ -41,20 +41,61 @@ |
41 | // --- Short Cut Keys ------------------------------------------------ | 41 | // --- Short Cut Keys ------------------------------------------------ |
42 | 42 | ||
43 | var keyBindings = { | 43 | var keyBindings = { |
44 | - W: [logWarning, '(temp) log a warning'], | 44 | + //O: [toggleSummary, 'Toggle ONOS summary pane'], |
45 | - E: [logError, '(temp) log an error'], | 45 | + I: [toggleInstances, 'Toggle ONOS instances pane'], |
46 | - R: [resetZoom, 'Reset pan / zoom'] | 46 | + //D: [toggleDetails, 'Disable / enable details pane'], |
47 | + | ||
48 | + //H: [toggleHosts, 'Toggle host visibility'], | ||
49 | + //M: [toggleOffline, 'Toggle offline visibility'], | ||
50 | + //B: [toggleBg, 'Toggle background image'], | ||
51 | + //P: togglePorts, | ||
52 | + | ||
53 | + //X: [toggleNodeLock, 'Lock / unlock node positions'], | ||
54 | + //Z: [toggleOblique, 'Toggle oblique view (Experimental)'], | ||
55 | + L: [cycleLabels, 'Cycle device labels'], | ||
56 | + //U: [unpin, 'Unpin node (hover mouse over)'], | ||
57 | + R: [resetZoom, 'Reset pan / zoom'], | ||
58 | + | ||
59 | + //V: [showRelatedIntentsAction, 'Show all related intents'], | ||
60 | + //rightArrow: [showNextIntentAction, 'Show next related intent'], | ||
61 | + //leftArrow: [showPrevIntentAction, 'Show previous related intent'], | ||
62 | + //W: [showSelectedIntentTrafficAction, 'Monitor traffic of selected intent'], | ||
63 | + //A: [showAllTrafficAction, 'Monitor all traffic'], | ||
64 | + //F: [showDeviceLinkFlowsAction, 'Show device link flows'], | ||
65 | + | ||
66 | + //E: [equalizeMasters, 'Equalize mastership roles'], | ||
67 | + | ||
68 | + //esc: handleEscape, | ||
69 | + | ||
70 | + _helpFormat: [ | ||
71 | + ['O', 'I', 'D', '-', 'H', 'M', 'B', 'P' ], | ||
72 | + ['X', 'Z', 'L', 'U', 'R' ], | ||
73 | + ['V', 'rightArrow', 'leftArrow', 'W', 'A', 'F', '-', 'E' ] | ||
74 | + ] | ||
75 | + | ||
47 | }; | 76 | }; |
48 | 77 | ||
49 | - // ----------------- | 78 | + // mouse gestures |
50 | - // these functions are necessarily temporary examples.... | 79 | + var gestures = [ |
51 | - function logWarning() { | 80 | + ['click', 'Select the item and show details'], |
52 | - $log.warn('You have been warned!'); | 81 | + ['shift-click', 'Toggle selection state'], |
82 | + ['drag', 'Reposition (and pin) device / host'], | ||
83 | + ['cmd-scroll', 'Zoom in / out'], | ||
84 | + ['cmd-drag', 'Pan'] | ||
85 | + ]; | ||
86 | + | ||
87 | + function toggleInstances() { | ||
88 | + if (tis.isVisible()) { | ||
89 | + tis.hide(); | ||
90 | + } else { | ||
91 | + tis.show(); | ||
53 | } | 92 | } |
54 | - function logError() { | 93 | + tfs.updateDeviceColors(); |
55 | - $log.error('You are erroneous!'); | 94 | + } |
95 | + | ||
96 | + function cycleLabels() { | ||
97 | + $log.debug('Cycle Labels.....'); | ||
56 | } | 98 | } |
57 | - // ----------------- | ||
58 | 99 | ||
59 | function resetZoom() { | 100 | function resetZoom() { |
60 | zoomer.reset(); | 101 | zoomer.reset(); |
... | @@ -83,7 +124,6 @@ | ... | @@ -83,7 +124,6 @@ |
83 | function zoomCallback() { | 124 | function zoomCallback() { |
84 | var tr = zoomer.translate(), | 125 | var tr = zoomer.translate(), |
85 | sc = zoomer.scale(); | 126 | sc = zoomer.scale(); |
86 | - $log.log('ZOOM: translate = ' + tr + ', scale = ' + sc); | ||
87 | 127 | ||
88 | // keep the map lines constant width while zooming | 128 | // keep the map lines constant width while zooming |
89 | mapG.style('stroke-width', (2.0 / sc) + 'px'); | 129 | mapG.style('stroke-width', (2.0 / sc) + 'px'); |
... | @@ -150,16 +190,19 @@ | ... | @@ -150,16 +190,19 @@ |
150 | 190 | ||
151 | function setUpMap() { | 191 | function setUpMap() { |
152 | mapG = zoomLayer.append('g').attr('id', 'topo-map'); | 192 | mapG = zoomLayer.append('g').attr('id', 'topo-map'); |
153 | - //ms.loadMapInto(map, '*continental_us', {mapFillScale:0.5}); | 193 | + |
154 | - ms.loadMapInto(mapG, '*continental_us'); | ||
155 | //showCallibrationPoints(); | 194 | //showCallibrationPoints(); |
195 | + //return ms.loadMapInto(map, '*continental_us', {mapFillScale:0.5}); | ||
196 | + | ||
197 | + // returns a promise for the projection... | ||
198 | + return ms.loadMapInto(mapG, '*continental_us'); | ||
156 | } | 199 | } |
157 | 200 | ||
158 | // --- Force Layout -------------------------------------------------- | 201 | // --- Force Layout -------------------------------------------------- |
159 | 202 | ||
160 | - function setUpForce() { | 203 | + function setUpForce(xlink) { |
161 | forceG = zoomLayer.append('g').attr('id', 'topo-force'); | 204 | forceG = zoomLayer.append('g').attr('id', 'topo-force'); |
162 | - tfs.initForce(forceG, svg.attr('width'), svg.attr('height')); | 205 | + tfs.initForce(forceG, xlink, svg.attr('width'), svg.attr('height')); |
163 | } | 206 | } |
164 | 207 | ||
165 | // --- Controller Definition ----------------------------------------- | 208 | // --- Controller Definition ----------------------------------------- |
... | @@ -174,8 +217,12 @@ | ... | @@ -174,8 +217,12 @@ |
174 | 'TopoInstService', | 217 | 'TopoInstService', |
175 | 218 | ||
176 | function ($scope, _$log_, $loc, $timeout, _fs_, mast, | 219 | function ($scope, _$log_, $loc, $timeout, _fs_, mast, |
177 | - _ks_, _zs_, _gs_, _ms_, _sus_, tes, _tfs_, tps, tis) { | 220 | + _ks_, _zs_, _gs_, _ms_, _sus_, tes, _tfs_, tps, _tis_) { |
178 | - var self = this; | 221 | + var self = this, |
222 | + xlink = { | ||
223 | + showNoDevs: showNoDevs | ||
224 | + }; | ||
225 | + | ||
179 | $log = _$log_; | 226 | $log = _$log_; |
180 | fs = _fs_; | 227 | fs = _fs_; |
181 | ks = _ks_; | 228 | ks = _ks_; |
... | @@ -184,6 +231,7 @@ | ... | @@ -184,6 +231,7 @@ |
184 | ms = _ms_; | 231 | ms = _ms_; |
185 | sus = _sus_; | 232 | sus = _sus_; |
186 | tfs = _tfs_; | 233 | tfs = _tfs_; |
234 | + tis = _tis_; | ||
187 | 235 | ||
188 | self.notifyResize = function () { | 236 | self.notifyResize = function () { |
189 | svgResized(fs.windowSize(mast.mastHeight())); | 237 | svgResized(fs.windowSize(mast.mastHeight())); |
... | @@ -207,8 +255,8 @@ | ... | @@ -207,8 +255,8 @@ |
207 | setUpDefs(); | 255 | setUpDefs(); |
208 | setUpZoom(); | 256 | setUpZoom(); |
209 | setUpNoDevs(); | 257 | setUpNoDevs(); |
210 | - setUpMap(); | 258 | + xlink.projectionPromise = setUpMap(); |
211 | - setUpForce(); | 259 | + setUpForce(xlink); |
212 | 260 | ||
213 | tis.initInst(); | 261 | tis.initInst(); |
214 | tps.initPanels(); | 262 | tps.initPanels(); | ... | ... |
... | @@ -23,7 +23,7 @@ | ... | @@ -23,7 +23,7 @@ |
23 | 'use strict'; | 23 | 'use strict'; |
24 | 24 | ||
25 | // injected refs | 25 | // injected refs |
26 | - var $log, wss, wes, tps, tis; | 26 | + var $log, wss, wes, tps, tis, tfs; |
27 | 27 | ||
28 | // internal state | 28 | // internal state |
29 | var wsock; | 29 | var wsock; |
... | @@ -32,7 +32,9 @@ | ... | @@ -32,7 +32,9 @@ |
32 | showSummary: showSummary, | 32 | showSummary: showSummary, |
33 | addInstance: addInstance, | 33 | addInstance: addInstance, |
34 | updateInstance: updateInstance, | 34 | updateInstance: updateInstance, |
35 | - removeInstance: removeInstance | 35 | + removeInstance: removeInstance, |
36 | + addDevice: addDevice, | ||
37 | + updateDevice: updateDevice | ||
36 | // TODO: implement remaining handlers.. | 38 | // TODO: implement remaining handlers.. |
37 | 39 | ||
38 | }; | 40 | }; |
... | @@ -63,6 +65,16 @@ | ... | @@ -63,6 +65,16 @@ |
63 | tis.removeInstance(ev.payload); | 65 | tis.removeInstance(ev.payload); |
64 | } | 66 | } |
65 | 67 | ||
68 | + function addDevice(ev) { | ||
69 | + $log.debug(' **** Add Device **** ', ev.payload); | ||
70 | + tfs.addDevice(ev.payload); | ||
71 | + } | ||
72 | + | ||
73 | + function updateDevice(ev) { | ||
74 | + $log.debug(' **** Update Device **** ', ev.payload); | ||
75 | + tfs.updateDevice(ev.payload); | ||
76 | + } | ||
77 | + | ||
66 | // ========================== | 78 | // ========================== |
67 | 79 | ||
68 | var dispatcher = { | 80 | var dispatcher = { |
... | @@ -100,14 +112,15 @@ | ... | @@ -100,14 +112,15 @@ |
100 | angular.module('ovTopo') | 112 | angular.module('ovTopo') |
101 | .factory('TopoEventService', | 113 | .factory('TopoEventService', |
102 | ['$log', '$location', 'WebSocketService', 'WsEventService', | 114 | ['$log', '$location', 'WebSocketService', 'WsEventService', |
103 | - 'TopoPanelService', 'TopoInstService', | 115 | + 'TopoPanelService', 'TopoInstService', 'TopoForceService', |
104 | 116 | ||
105 | - function (_$log_, $loc, _wss_, _wes_, _tps_, _tis_) { | 117 | + function (_$log_, $loc, _wss_, _wes_, _tps_, _tis_, _tfs_) { |
106 | $log = _$log_; | 118 | $log = _$log_; |
107 | wss = _wss_; | 119 | wss = _wss_; |
108 | wes = _wes_; | 120 | wes = _wes_; |
109 | tps = _tps_; | 121 | tps = _tps_; |
110 | tis = _tis_; | 122 | tis = _tis_; |
123 | + tfs = _tfs_; | ||
111 | 124 | ||
112 | function bindDispatcher(TopoDomElementsPassedHere) { | 125 | function bindDispatcher(TopoDomElementsPassedHere) { |
113 | // TODO: store refs to topo DOM elements... | 126 | // TODO: store refs to topo DOM elements... | ... | ... |
... | @@ -23,10 +23,29 @@ | ... | @@ -23,10 +23,29 @@ |
23 | 'use strict'; | 23 | 'use strict'; |
24 | 24 | ||
25 | // injected refs | 25 | // injected refs |
26 | - var $log, sus; | 26 | + var $log, sus, is, ts, tis, xlink; |
27 | + | ||
28 | + // configuration | ||
29 | + var labelConfig = { | ||
30 | + imgPad: 16, | ||
31 | + padLR: 4, | ||
32 | + padTB: 3, | ||
33 | + marginLR: 3, | ||
34 | + marginTB: 2, | ||
35 | + port: { | ||
36 | + gap: 3, | ||
37 | + width: 18, | ||
38 | + height: 14 | ||
39 | + } | ||
40 | + }; | ||
41 | + | ||
42 | + var deviceIconConfig = { | ||
43 | + xoff: -20, | ||
44 | + yoff: -18 | ||
45 | + }; | ||
27 | 46 | ||
28 | // internal state | 47 | // internal state |
29 | - var settings, | 48 | + var settings, // merged default settings and options |
30 | force, // force layout object | 49 | force, // force layout object |
31 | drag, // drag behavior handler | 50 | drag, // drag behavior handler |
32 | network = { | 51 | network = { |
... | @@ -34,8 +53,10 @@ | ... | @@ -34,8 +53,10 @@ |
34 | links: [], | 53 | links: [], |
35 | lookup: {}, | 54 | lookup: {}, |
36 | revLinkToKey: {} | 55 | revLinkToKey: {} |
37 | - }; | 56 | + }, |
38 | - | 57 | + projection, // background map projection |
58 | + deviceLabelIndex = 0, // for device label cycling | ||
59 | + hostLabelIndex = 0; // for host label cycling | ||
39 | 60 | ||
40 | // SVG elements; | 61 | // SVG elements; |
41 | var linkG, linkLabelG, nodeG; | 62 | var linkG, linkLabelG, nodeG; |
... | @@ -71,12 +92,517 @@ | ... | @@ -71,12 +92,517 @@ |
71 | }; | 92 | }; |
72 | 93 | ||
73 | 94 | ||
95 | + // ========================== | ||
96 | + // === EVENT HANDLERS | ||
97 | + | ||
98 | + function addDevice(data) { | ||
99 | + var id = data.id, | ||
100 | + d; | ||
101 | + | ||
102 | + xlink.showNoDevs(false); | ||
103 | + | ||
104 | + // although this is an add device event, if we already have the | ||
105 | + // device, treat it as an update instead.. | ||
106 | + if (network.lookup[id]) { | ||
107 | + updateDevice(data); | ||
108 | + return; | ||
109 | + } | ||
110 | + | ||
111 | + d = createDeviceNode(data); | ||
112 | + network.nodes.push(d); | ||
113 | + network.lookup[id] = d; | ||
114 | + | ||
115 | + $log.debug("Created new device.. ", d.id, d.x, d.y); | ||
116 | + | ||
117 | + updateNodes(); | ||
118 | + fStart(); | ||
119 | + } | ||
120 | + | ||
121 | + function updateDevice(data) { | ||
122 | + var id = data.id, | ||
123 | + d = network.lookup[id], | ||
124 | + wasOnline; | ||
125 | + | ||
126 | + if (d) { | ||
127 | + wasOnline = d.online; | ||
128 | + angular.extend(d, data); | ||
129 | + if (positionNode(d, true)) { | ||
130 | + sendUpdateMeta(d, true); | ||
131 | + } | ||
132 | + updateNodes(); | ||
133 | + if (wasOnline !== d.online) { | ||
134 | + // TODO: re-instate link update, and offline visibility | ||
135 | + //findAttachedLinks(d.id).forEach(restyleLinkElement); | ||
136 | + //updateOfflineVisibility(d); | ||
137 | + } | ||
138 | + } else { | ||
139 | + // TODO: decide whether we want to capture logic errors | ||
140 | + //logicError('updateDevice lookup fail. ID = "' + id + '"'); | ||
141 | + } | ||
142 | + } | ||
143 | + | ||
144 | + function sendUpdateMeta(d, store) { | ||
145 | + var metaUi = {}, | ||
146 | + ll; | ||
147 | + | ||
148 | + // TODO: fix this code to send event to server... | ||
149 | + //if (store) { | ||
150 | + // ll = geoMapProj.invert([d.x, d.y]); | ||
151 | + // metaUi = { | ||
152 | + // x: d.x, | ||
153 | + // y: d.y, | ||
154 | + // lng: ll[0], | ||
155 | + // lat: ll[1] | ||
156 | + // }; | ||
157 | + //} | ||
158 | + //d.metaUi = metaUi; | ||
159 | + //sendMessage('updateMeta', { | ||
160 | + // id: d.id, | ||
161 | + // 'class': d.class, | ||
162 | + // memento: metaUi | ||
163 | + //}); | ||
164 | + } | ||
165 | + | ||
166 | + | ||
167 | + function updateNodes() { | ||
168 | + $log.debug('TODO updateNodes()...'); | ||
169 | + // TODO... | ||
170 | + } | ||
171 | + | ||
172 | + function fStart() { | ||
173 | + $log.debug('TODO fStart()...'); | ||
174 | + // TODO... | ||
175 | + } | ||
176 | + | ||
177 | + function fResume() { | ||
178 | + $log.debug('TODO fResume()...'); | ||
179 | + // TODO... | ||
180 | + } | ||
181 | + | ||
182 | + // ========================== | ||
183 | + // === Devices and hosts - helper functions | ||
184 | + | ||
185 | + function coordFromLngLat(loc) { | ||
186 | + // Our hope is that the projection is installed before we start | ||
187 | + // handling incoming nodes. But if not, we'll just return the origin. | ||
188 | + return projection ? projection([loc.lng, loc.lat]) : [0, 0]; | ||
189 | + } | ||
190 | + | ||
191 | + function positionNode(node, forUpdate) { | ||
192 | + var meta = node.metaUi, | ||
193 | + x = meta && meta.x, | ||
194 | + y = meta && meta.y, | ||
195 | + xy; | ||
196 | + | ||
197 | + // If we have [x,y] already, use that... | ||
198 | + if (x && y) { | ||
199 | + node.fixed = true; | ||
200 | + node.px = node.x = x; | ||
201 | + node.py = node.y = y; | ||
202 | + return; | ||
203 | + } | ||
204 | + | ||
205 | + var location = node.location, | ||
206 | + coord; | ||
207 | + | ||
208 | + if (location && location.type === 'latlng') { | ||
209 | + coord = coordFromLngLat(location); | ||
210 | + node.fixed = true; | ||
211 | + node.px = node.x = coord[0]; | ||
212 | + node.py = node.y = coord[1]; | ||
213 | + return true; | ||
214 | + } | ||
215 | + | ||
216 | + // if this is a node update (not a node add).. skip randomizer | ||
217 | + if (forUpdate) { | ||
218 | + return; | ||
219 | + } | ||
220 | + | ||
221 | + // Note: Placing incoming unpinned nodes at exactly the same point | ||
222 | + // (center of the view) causes them to explode outwards when | ||
223 | + // the force layout kicks in. So, we spread them out a bit | ||
224 | + // initially, to provide a more serene layout convergence. | ||
225 | + // Additionally, if the node is a host, we place it near | ||
226 | + // the device it is connected to. | ||
227 | + | ||
228 | + function spread(s) { | ||
229 | + return Math.floor((Math.random() * s) - s/2); | ||
230 | + } | ||
231 | + | ||
232 | + function randDim(dim) { | ||
233 | + return dim / 2 + spread(dim * 0.7071); | ||
234 | + } | ||
235 | + | ||
236 | + function rand() { | ||
237 | + return { | ||
238 | + x: randDim(network.view.width()), | ||
239 | + y: randDim(network.view.height()) | ||
240 | + }; | ||
241 | + } | ||
242 | + | ||
243 | + function near(node) { | ||
244 | + var min = 12, | ||
245 | + dx = spread(12), | ||
246 | + dy = spread(12); | ||
247 | + return { | ||
248 | + x: node.x + min + dx, | ||
249 | + y: node.y + min + dy | ||
250 | + }; | ||
251 | + } | ||
252 | + | ||
253 | + function getDevice(cp) { | ||
254 | + var d = network.lookup[cp.device]; | ||
255 | + return d || rand(); | ||
256 | + } | ||
257 | + | ||
258 | + xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand(); | ||
259 | + angular.extend(node, xy); | ||
260 | + } | ||
261 | + | ||
262 | + function createDeviceNode(device) { | ||
263 | + // start with the object as is | ||
264 | + var node = device, | ||
265 | + type = device.type, | ||
266 | + svgCls = type ? 'node device ' + type : 'node device'; | ||
267 | + | ||
268 | + // Augment as needed... | ||
269 | + node.class = 'device'; | ||
270 | + node.svgClass = device.online ? svgCls + ' online' : svgCls; | ||
271 | + positionNode(node); | ||
272 | + return node; | ||
273 | + } | ||
274 | + | ||
275 | + // ========================== | ||
276 | + // === Devices and hosts - D3 rendering | ||
277 | + | ||
278 | + // Returns the newly computed bounding box of the rectangle | ||
279 | + function adjustRectToFitText(n) { | ||
280 | + var text = n.select('text'), | ||
281 | + box = text.node().getBBox(), | ||
282 | + lab = labelConfig; | ||
283 | + | ||
284 | + text.attr('text-anchor', 'middle') | ||
285 | + .attr('y', '-0.8em') | ||
286 | + .attr('x', lab.imgPad/2); | ||
287 | + | ||
288 | + // translate the bbox so that it is centered on [x,y] | ||
289 | + box.x = -box.width / 2; | ||
290 | + box.y = -box.height / 2; | ||
291 | + | ||
292 | + // add padding | ||
293 | + box.x -= (lab.padLR + lab.imgPad/2); | ||
294 | + box.width += lab.padLR * 2 + lab.imgPad; | ||
295 | + box.y -= lab.padTB; | ||
296 | + box.height += lab.padTB * 2; | ||
297 | + | ||
298 | + return box; | ||
299 | + } | ||
300 | + | ||
301 | + function mkSvgClass(d) { | ||
302 | + return d.fixed ? d.svgClass + ' fixed' : d.svgClass; | ||
303 | + } | ||
304 | + | ||
305 | + function hostLabel(d) { | ||
306 | + var idx = (hostLabelIndex < d.labels.length) ? hostLabelIndex : 0; | ||
307 | + return d.labels[idx]; | ||
308 | + } | ||
309 | + function deviceLabel(d) { | ||
310 | + var idx = (deviceLabelIndex < d.labels.length) ? deviceLabelIndex : 0; | ||
311 | + return d.labels[idx]; | ||
312 | + } | ||
313 | + function trimLabel(label) { | ||
314 | + return (label && label.trim()) || ''; | ||
315 | + } | ||
316 | + | ||
317 | + function emptyBox() { | ||
318 | + return { | ||
319 | + x: -2, | ||
320 | + y: -2, | ||
321 | + width: 4, | ||
322 | + height: 4 | ||
323 | + }; | ||
324 | + } | ||
325 | + | ||
326 | + | ||
327 | + function updateDeviceLabel(d) { | ||
328 | + var label = trimLabel(deviceLabel(d)), | ||
329 | + noLabel = !label, | ||
330 | + node = d.el, | ||
331 | + dim = is.iconConfig().device.dim, | ||
332 | + devCfg = deviceIconConfig, | ||
333 | + box, dx, dy; | ||
334 | + | ||
335 | + node.select('text') | ||
336 | + .text(label) | ||
337 | + .style('opacity', 0) | ||
338 | + .transition() | ||
339 | + .style('opacity', 1); | ||
340 | + | ||
341 | + if (noLabel) { | ||
342 | + box = emptyBox(); | ||
343 | + dx = -dim/2; | ||
344 | + dy = -dim/2; | ||
345 | + } else { | ||
346 | + box = adjustRectToFitText(node); | ||
347 | + dx = box.x + devCfg.xoff; | ||
348 | + dy = box.y + devCfg.yoff; | ||
349 | + } | ||
350 | + | ||
351 | + node.select('rect') | ||
352 | + .transition() | ||
353 | + .attr(box); | ||
354 | + | ||
355 | + node.select('g.deviceIcon') | ||
356 | + .transition() | ||
357 | + .attr('transform', sus.translate(dx, dy)); | ||
358 | + } | ||
359 | + | ||
360 | + function updateHostLabel(d) { | ||
361 | + var label = trimLabel(hostLabel(d)); | ||
362 | + d.el.select('text').text(label); | ||
363 | + } | ||
364 | + | ||
365 | + function nodeMouseOver(m) { | ||
366 | + // TODO | ||
367 | + $log.debug("TODO nodeMouseOver()...", m); | ||
368 | + } | ||
369 | + | ||
370 | + function nodeMouseOut(m) { | ||
371 | + // TODO | ||
372 | + $log.debug("TODO nodeMouseOut()...", m); | ||
373 | + } | ||
374 | + | ||
375 | + function updateDeviceColors(d) { | ||
376 | + if (d) { | ||
377 | + setDeviceColor(d); | ||
378 | + } else { | ||
379 | + node.filter('.device').each(function (d) { | ||
380 | + setDeviceColor(d); | ||
381 | + }); | ||
382 | + } | ||
383 | + } | ||
384 | + | ||
385 | + var dCol = { | ||
386 | + black: '#000', | ||
387 | + paleblue: '#acf', | ||
388 | + offwhite: '#ddd', | ||
389 | + midgrey: '#888', | ||
390 | + lightgrey: '#bbb', | ||
391 | + orange: '#f90' | ||
392 | + }; | ||
393 | + | ||
394 | + // note: these are the device icon colors without affinity | ||
395 | + var dColTheme = { | ||
396 | + light: { | ||
397 | + online: { | ||
398 | + glyph: dCol.black, | ||
399 | + rect: dCol.paleblue | ||
400 | + }, | ||
401 | + offline: { | ||
402 | + glyph: dCol.midgrey, | ||
403 | + rect: dCol.lightgrey | ||
404 | + } | ||
405 | + }, | ||
406 | + // TODO: theme | ||
407 | + dark: { | ||
408 | + online: { | ||
409 | + glyph: dCol.black, | ||
410 | + rect: dCol.paleblue | ||
411 | + }, | ||
412 | + offline: { | ||
413 | + glyph: dCol.midgrey, | ||
414 | + rect: dCol.lightgrey | ||
415 | + } | ||
416 | + } | ||
417 | + }; | ||
418 | + | ||
419 | + function devBaseColor(d) { | ||
420 | + var o = d.online ? 'online' : 'offline'; | ||
421 | + return dColTheme[ts.theme()][o]; | ||
422 | + } | ||
423 | + | ||
424 | + function setDeviceColor(d) { | ||
425 | + var o = d.online, | ||
426 | + s = d.el.classed('selected'), | ||
427 | + c = devBaseColor(d), | ||
428 | + a = instColor(d.master, o), | ||
429 | + g, r, | ||
430 | + icon = d.el.select('g.deviceIcon'); | ||
431 | + | ||
432 | + if (s) { | ||
433 | + g = c.glyph; | ||
434 | + r = dCol.orange; | ||
435 | + } else if (tis.isVisible()) { | ||
436 | + g = o ? a : c.glyph; | ||
437 | + r = o ? dCol.offwhite : a; | ||
438 | + } else { | ||
439 | + g = c.glyph; | ||
440 | + r = c.rect; | ||
441 | + } | ||
442 | + | ||
443 | + icon.select('use') | ||
444 | + .style('fill', g); | ||
445 | + icon.select('rect') | ||
446 | + .style('fill', r); | ||
447 | + } | ||
448 | + | ||
449 | + function instColor(id, online) { | ||
450 | + return sus.cat7().getColor(id, !online, ts.theme()); | ||
451 | + } | ||
452 | + | ||
453 | + //============ | ||
454 | + | ||
455 | + function updateNodes() { | ||
456 | + node = nodeG.selectAll('.node') | ||
457 | + .data(network.nodes, function (d) { return d.id; }); | ||
458 | + | ||
459 | + // operate on existing nodes... | ||
460 | + node.filter('.device').each(function (d) { | ||
461 | + var node = d.el; | ||
462 | + node.classed('online', d.online); | ||
463 | + updateDeviceLabel(d); | ||
464 | + positionNode(d, true); | ||
465 | + }); | ||
466 | + | ||
467 | + node.filter('.host').each(function (d) { | ||
468 | + updateHostLabel(d); | ||
469 | + positionNode(d, true); | ||
470 | + }); | ||
471 | + | ||
472 | + // operate on entering nodes: | ||
473 | + var entering = node.enter() | ||
474 | + .append('g') | ||
475 | + .attr({ | ||
476 | + id: function (d) { return sus.safeId(d.id); }, | ||
477 | + class: mkSvgClass, | ||
478 | + transform: function (d) { return sus.translate(d.x, d.y); }, | ||
479 | + opacity: 0 | ||
480 | + }) | ||
481 | + .call(drag) | ||
482 | + .on('mouseover', nodeMouseOver) | ||
483 | + .on('mouseout', nodeMouseOut) | ||
484 | + .transition() | ||
485 | + .attr('opacity', 1); | ||
486 | + | ||
487 | + // augment device nodes... | ||
488 | + entering.filter('.device').each(function (d) { | ||
489 | + var node = d3.select(this), | ||
490 | + glyphId = d.type || 'unknown', | ||
491 | + label = trimLabel(deviceLabel(d)), | ||
492 | + noLabel = !label, | ||
493 | + box, dx, dy, icon; | ||
494 | + | ||
495 | + // provide ref to element from backing data.... | ||
496 | + d.el = node; | ||
497 | + | ||
498 | + node.append('rect').attr({ rx: 5, ry: 5 }); | ||
499 | + node.append('text').text(label).attr('dy', '1.1em'); | ||
500 | + box = adjustRectToFitText(node); | ||
501 | + node.select('rect').attr(box); | ||
502 | + | ||
503 | + icon = is.addDeviceIcon(node, glyphId); | ||
504 | + d.iconDim = icon.dim; | ||
505 | + | ||
506 | + if (noLabel) { | ||
507 | + dx = -icon.dim/2; | ||
508 | + dy = -icon.dim/2; | ||
509 | + } else { | ||
510 | + box = adjustRectToFitText(node); | ||
511 | + dx = box.x + iconConfig.xoff; | ||
512 | + dy = box.y + iconConfig.yoff; | ||
513 | + } | ||
514 | + | ||
515 | + icon.attr('transform', sus.translate(dx, dy)); | ||
516 | + }); | ||
517 | + | ||
518 | + // augment host nodes... | ||
519 | + entering.filter('.host').each(function (d) { | ||
520 | + var node = d3.select(this), | ||
521 | + cfg = config.icons.host, | ||
522 | + r = cfg.radius[d.type] || cfg.defaultRadius, | ||
523 | + textDy = r + 10, | ||
524 | + //TODO: iid = iconGlyphUrl(d), | ||
525 | + _dummy; | ||
526 | + | ||
527 | + // provide ref to element from backing data.... | ||
528 | + d.el = node; | ||
529 | + | ||
530 | + //TODO: showHostVis(node); | ||
531 | + | ||
532 | + node.append('circle').attr('r', r); | ||
533 | + if (iid) { | ||
534 | + //TODO: addHostIcon(node, r, iid); | ||
535 | + } | ||
536 | + node.append('text') | ||
537 | + .text(hostLabel) | ||
538 | + .attr('dy', textDy) | ||
539 | + .attr('text-anchor', 'middle'); | ||
540 | + }); | ||
541 | + | ||
542 | + // operate on both existing and new nodes, if necessary | ||
543 | + updateDeviceColors(); | ||
544 | + | ||
545 | + // operate on exiting nodes: | ||
546 | + // Note that the node is removed after 2 seconds. | ||
547 | + // Sub element animations should be shorter than 2 seconds. | ||
548 | + var exiting = node.exit() | ||
549 | + .transition() | ||
550 | + .duration(2000) | ||
551 | + .style('opacity', 0) | ||
552 | + .remove(); | ||
553 | + | ||
554 | + // host node exits.... | ||
555 | + exiting.filter('.host').each(function (d) { | ||
556 | + var node = d.el; | ||
557 | + node.select('use') | ||
558 | + .style('opacity', 0.5) | ||
559 | + .transition() | ||
560 | + .duration(800) | ||
561 | + .style('opacity', 0); | ||
562 | + | ||
563 | + node.select('text') | ||
564 | + .style('opacity', 0.5) | ||
565 | + .transition() | ||
566 | + .duration(800) | ||
567 | + .style('opacity', 0); | ||
568 | + | ||
569 | + node.select('circle') | ||
570 | + .style('stroke-fill', '#555') | ||
571 | + .style('fill', '#888') | ||
572 | + .style('opacity', 0.5) | ||
573 | + .transition() | ||
574 | + .duration(1500) | ||
575 | + .attr('r', 0); | ||
576 | + }); | ||
577 | + | ||
578 | + // device node exits.... | ||
579 | + exiting.filter('.device').each(function (d) { | ||
580 | + var node = d.el; | ||
581 | + node.select('use') | ||
582 | + .style('opacity', 0.5) | ||
583 | + .transition() | ||
584 | + .duration(800) | ||
585 | + .style('opacity', 0); | ||
586 | + | ||
587 | + node.selectAll('rect') | ||
588 | + .style('stroke-fill', '#555') | ||
589 | + .style('fill', '#888') | ||
590 | + .style('opacity', 0.5); | ||
591 | + }); | ||
592 | + fResume(); | ||
593 | + } | ||
594 | + | ||
595 | + | ||
596 | + // ========================== | ||
74 | // force layout tick function | 597 | // force layout tick function |
75 | function tick() { | 598 | function tick() { |
76 | 599 | ||
77 | } | 600 | } |
78 | 601 | ||
79 | 602 | ||
603 | + // ========================== | ||
604 | + // === MOUSE GESTURE HANDLERS | ||
605 | + | ||
80 | function selectCb() { } | 606 | function selectCb() { } |
81 | function atDragEnd() {} | 607 | function atDragEnd() {} |
82 | function dragEnabled() {} | 608 | function dragEnabled() {} |
... | @@ -84,23 +610,38 @@ | ... | @@ -84,23 +610,38 @@ |
84 | 610 | ||
85 | 611 | ||
86 | // ========================== | 612 | // ========================== |
613 | + // Module definition | ||
87 | 614 | ||
88 | angular.module('ovTopo') | 615 | angular.module('ovTopo') |
89 | .factory('TopoForceService', | 616 | .factory('TopoForceService', |
90 | - ['$log', 'SvgUtilService', | 617 | + ['$log', 'SvgUtilService', 'IconService', 'ThemeService', |
618 | + 'TopoInstService', | ||
91 | 619 | ||
92 | - function (_$log_, _sus_) { | 620 | + function (_$log_, _sus_, _is_, _ts_, _tis_) { |
93 | $log = _$log_; | 621 | $log = _$log_; |
94 | sus = _sus_; | 622 | sus = _sus_; |
623 | + is = _is_; | ||
624 | + ts = _ts_; | ||
625 | + tis = _tis_; | ||
95 | 626 | ||
96 | // forceG is the SVG group to display the force layout in | 627 | // forceG is the SVG group to display the force layout in |
628 | + // xlink is the cross-link api from the main topo source file | ||
97 | // w, h are the initial dimensions of the SVG | 629 | // w, h are the initial dimensions of the SVG |
98 | // opts are, well, optional :) | 630 | // opts are, well, optional :) |
99 | - function initForce(forceG, w, h, opts) { | 631 | + function initForce(forceG, _xlink_, w, h, opts) { |
100 | $log.debug('initForce().. WxH = ' + w + 'x' + h); | 632 | $log.debug('initForce().. WxH = ' + w + 'x' + h); |
633 | + xlink = _xlink_; | ||
101 | 634 | ||
102 | settings = angular.extend({}, defaultSettings, opts); | 635 | settings = angular.extend({}, defaultSettings, opts); |
103 | 636 | ||
637 | + // when the projection promise is resolved, cache the projection | ||
638 | + xlink.projectionPromise.then( | ||
639 | + function (proj) { | ||
640 | + projection = proj; | ||
641 | + $log.debug('** We installed the projection: ', proj); | ||
642 | + } | ||
643 | + ); | ||
644 | + | ||
104 | linkG = forceG.append('g').attr('id', 'topo-links'); | 645 | linkG = forceG.append('g').attr('id', 'topo-links'); |
105 | linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels'); | 646 | linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels'); |
106 | nodeG = forceG.append('g').attr('id', 'topo-nodes'); | 647 | nodeG = forceG.append('g').attr('id', 'topo-nodes'); |
... | @@ -127,12 +668,16 @@ | ... | @@ -127,12 +668,16 @@ |
127 | function resize(dim) { | 668 | function resize(dim) { |
128 | force.size([dim.width, dim.height]); | 669 | force.size([dim.width, dim.height]); |
129 | // Review -- do we need to nudge the layout ? | 670 | // Review -- do we need to nudge the layout ? |
130 | - | ||
131 | } | 671 | } |
132 | 672 | ||
133 | return { | 673 | return { |
134 | initForce: initForce, | 674 | initForce: initForce, |
135 | - resize: resize | 675 | + resize: resize, |
676 | + | ||
677 | + updateDeviceColors: updateDeviceColors, | ||
678 | + | ||
679 | + addDevice: addDevice, | ||
680 | + updateDevice: updateDevice | ||
136 | }; | 681 | }; |
137 | }]); | 682 | }]); |
138 | }()); | 683 | }()); | ... | ... |
... | @@ -325,7 +325,10 @@ | ... | @@ -325,7 +325,10 @@ |
325 | destroyInst: destroyInst, | 325 | destroyInst: destroyInst, |
326 | addInstance: addInstance, | 326 | addInstance: addInstance, |
327 | updateInstance: updateInstance, | 327 | updateInstance: updateInstance, |
328 | - removeInstance: removeInstance | 328 | + removeInstance: removeInstance, |
329 | + isVisible: function () { return oiBox.isVisible(); }, | ||
330 | + show: function () { oiBox.show(); }, | ||
331 | + hide: function () { oiBox.hide(); } | ||
329 | }; | 332 | }; |
330 | }]); | 333 | }]); |
331 | }()); | 334 | }()); | ... | ... |
-
Please register or login to post a comment