blob: 9fd92d785ff208d03b0d8f6a4dbbd9c028e654dc [file] [log] [blame]
Simon Hunt195cb382014-11-03 17:50:51 -08001/*
2 * Copyright 2014 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/*
Simon Hunt142d0032014-11-04 20:13:09 -080018 ONOS network topology viewer - version 1.1
Simon Hunt195cb382014-11-03 17:50:51 -080019
20 @author Simon Hunt
21 */
22
23(function (onos) {
24 'use strict';
25
Simon Hunt1a9eff92014-11-07 11:06:34 -080026 // shorter names for library APIs
Simon Huntbb282f52014-11-10 11:08:19 -080027 var d3u = onos.lib.d3util,
28 trace;
Simon Hunt1a9eff92014-11-07 11:06:34 -080029
Simon Hunt195cb382014-11-03 17:50:51 -080030 // configuration data
31 var config = {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -080032 useLiveData: true,
Simon Huntfc274c92014-11-11 11:05:46 -080033 fnTrace: true,
Simon Hunt195cb382014-11-03 17:50:51 -080034 debugOn: false,
35 debug: {
Simon Hunt99c13842014-11-06 18:23:12 -080036 showNodeXY: true,
37 showKeyHandler: false
Simon Hunt195cb382014-11-03 17:50:51 -080038 },
39 options: {
40 layering: true,
41 collisionPrevention: true,
Simon Hunt142d0032014-11-04 20:13:09 -080042 showBackground: true
Simon Hunt195cb382014-11-03 17:50:51 -080043 },
44 backgroundUrl: 'img/us-map.png',
Thomas Vachuska7d638d32014-11-07 10:24:43 -080045 webSockUrl: 'ws/topology',
Simon Hunt195cb382014-11-03 17:50:51 -080046 data: {
47 live: {
48 jsonUrl: 'rs/topology/graph',
49 detailPrefix: 'rs/topology/graph/',
50 detailSuffix: ''
51 },
52 fake: {
53 jsonUrl: 'json/network2.json',
54 detailPrefix: 'json/',
55 detailSuffix: '.json'
56 }
57 },
Simon Hunt99c13842014-11-06 18:23:12 -080058 labels: {
59 imgPad: 16,
60 padLR: 4,
61 padTB: 3,
62 marginLR: 3,
63 marginTB: 2,
64 port: {
65 gap: 3,
66 width: 18,
67 height: 14
68 }
69 },
Simon Hunt1a9eff92014-11-07 11:06:34 -080070 topo: {
71 linkInColor: '#66f',
72 linkInWidth: 14
73 },
Simon Hunt99c13842014-11-06 18:23:12 -080074 icons: {
75 w: 28,
76 h: 28,
77 xoff: -12,
78 yoff: -8
79 },
Simon Hunt195cb382014-11-03 17:50:51 -080080 iconUrl: {
81 device: 'img/device.png',
82 host: 'img/host.png',
83 pkt: 'img/pkt.png',
84 opt: 'img/opt.png'
85 },
Simon Hunt195cb382014-11-03 17:50:51 -080086 force: {
Simon Hunt7cd48f32014-11-09 23:42:50 -080087 note_for_links: 'link.type is used to differentiate',
Simon Huntc7ee0662014-11-05 16:44:37 -080088 linkDistance: {
Simon Hunt7cd48f32014-11-09 23:42:50 -080089 direct: 100,
90 optical: 120,
Thomas Vachuska3266abf2014-11-13 09:28:46 -080091 hostLink: 3
Simon Huntc7ee0662014-11-05 16:44:37 -080092 },
93 linkStrength: {
Simon Hunt7cd48f32014-11-09 23:42:50 -080094 direct: 1.0,
95 optical: 1.0,
96 hostLink: 1.0
Simon Huntc7ee0662014-11-05 16:44:37 -080097 },
Simon Hunt7cd48f32014-11-09 23:42:50 -080098 note_for_nodes: 'node.class is used to differentiate',
Simon Huntc7ee0662014-11-05 16:44:37 -080099 charge: {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800100 device: -8000,
Thomas Vachuska3266abf2014-11-13 09:28:46 -0800101 host: -5000
Simon Huntc7ee0662014-11-05 16:44:37 -0800102 },
103 pad: 20,
Simon Hunt195cb382014-11-03 17:50:51 -0800104 translate: function() {
105 return 'translate(' +
Simon Huntc7ee0662014-11-05 16:44:37 -0800106 config.force.pad + ',' +
107 config.force.pad + ')';
Simon Hunt195cb382014-11-03 17:50:51 -0800108 }
Paul Greyson6cb8ca02014-11-12 18:09:02 -0800109 },
110 // see below in creation of viewBox on main svg
111 logicalSize: 1000
Simon Hunt195cb382014-11-03 17:50:51 -0800112 };
113
Simon Hunt142d0032014-11-04 20:13:09 -0800114 // radio buttons
115 var btnSet = [
Simon Hunt934c3ce2014-11-05 11:45:07 -0800116 { text: 'All Layers', cb: showAllLayers },
117 { text: 'Packet Only', cb: showPacketLayer },
118 { text: 'Optical Only', cb: showOpticalLayer }
119 ];
120
121 // key bindings
122 var keyDispatch = {
Simon Hunt01095ff2014-11-13 16:37:29 -0800123 //M: testMe, // TODO: remove (testing only)
124 //S: injectStartupEvents, // TODO: remove (testing only)
125 //space: injectTestEvent, // TODO: remove (testing only)
Simon Hunt99c13842014-11-06 18:23:12 -0800126
Simon Hunt01095ff2014-11-13 16:37:29 -0800127 B: toggleBg,
Simon Hunt934c3ce2014-11-05 11:45:07 -0800128 L: cycleLabels,
129 P: togglePorts,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800130 U: unpin,
Paul Greysonfcba0e82014-11-13 10:21:16 -0800131 R: resetZoomPan,
Simon Hunt01095ff2014-11-13 16:37:29 -0800132 esc: deselectAll,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800133
Simon Huntb53e0682014-11-12 13:32:01 -0800134 W: requestTraffic, // bag of selections
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800135 X: cancelTraffic,
136 Z: requestPath // host-to-host intent (and monitor)
Simon Hunt934c3ce2014-11-05 11:45:07 -0800137 };
Simon Hunt142d0032014-11-04 20:13:09 -0800138
Simon Hunt195cb382014-11-03 17:50:51 -0800139 // state variables
Simon Hunt99c13842014-11-06 18:23:12 -0800140 var network = {
Simon Hunt50128c02014-11-08 13:36:15 -0800141 view: null, // view token reference
Simon Hunt99c13842014-11-06 18:23:12 -0800142 nodes: [],
143 links: [],
144 lookup: {}
145 },
Simon Hunt56d51852014-11-09 13:03:35 -0800146 scenario = {
147 evDir: 'json/ev/',
148 evScenario: '/scenario.json',
149 evPrefix: '/ev_',
150 evOnos: '_onos.json',
151 evUi: '_ui.json',
152 ctx: null,
153 params: {},
154 evNumber: 0,
155 view: null,
156 debug: false
157 },
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800158 webSock,
Simon Hunt0c6d4192014-11-12 12:07:10 -0800159 sid = 0,
Simon Hunt56d51852014-11-09 13:03:35 -0800160 deviceLabelIndex = 0,
161 hostLabelIndex = 0,
Simon Hunt61d04042014-11-11 17:27:16 -0800162 detailPane,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800163 selectOrder = [],
164 selections = {},
Simon Hunt6ac93f32014-11-13 12:17:27 -0800165 hovered = null,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800166
Simon Hunt195cb382014-11-03 17:50:51 -0800167 highlighted = null,
Simon Hunt195cb382014-11-03 17:50:51 -0800168 viewMode = 'showAll',
169 portLabelsOn = false;
170
Simon Hunt934c3ce2014-11-05 11:45:07 -0800171 // D3 selections
172 var svg,
Paul Greysonfcba0e82014-11-13 10:21:16 -0800173 zoomPanContainer,
Simon Hunt934c3ce2014-11-05 11:45:07 -0800174 bgImg,
Simon Huntc7ee0662014-11-05 16:44:37 -0800175 topoG,
176 nodeG,
177 linkG,
178 node,
Simon Hunt0c6d4192014-11-12 12:07:10 -0800179 link,
180 mask;
Simon Hunt195cb382014-11-03 17:50:51 -0800181
Paul Greyson6cb8ca02014-11-12 18:09:02 -0800182 // the projection for the map background
183 var geoMapProjection;
184
Paul Greysonfcba0e82014-11-13 10:21:16 -0800185 // the zoom function
186 var zoom;
187
Simon Hunt142d0032014-11-04 20:13:09 -0800188 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800189 // For Debugging / Development
Simon Hunt195cb382014-11-03 17:50:51 -0800190
Simon Hunt99c13842014-11-06 18:23:12 -0800191 function note(label, msg) {
192 console.log('NOTE: ' + label + ': ' + msg);
Simon Hunt195cb382014-11-03 17:50:51 -0800193 }
194
Simon Hunt99c13842014-11-06 18:23:12 -0800195 function debug(what) {
196 return config.debugOn && config.debug[what];
Simon Hunt934c3ce2014-11-05 11:45:07 -0800197 }
198
Simon Huntfc274c92014-11-11 11:05:46 -0800199 function fnTrace(msg, id) {
200 if (config.fnTrace) {
201 console.log('FN: ' + msg + ' [' + id + ']');
202 }
203 }
Simon Hunt99c13842014-11-06 18:23:12 -0800204
Simon Hunt934c3ce2014-11-05 11:45:07 -0800205 // ==============================
206 // Key Callbacks
207
Simon Hunt99c13842014-11-06 18:23:12 -0800208 function testMe(view) {
Simon Hunt50128c02014-11-08 13:36:15 -0800209 view.alert('test');
Simon Hunt99c13842014-11-06 18:23:12 -0800210 }
211
Simon Hunt56d51852014-11-09 13:03:35 -0800212 function abortIfLive() {
Simon Hunt50128c02014-11-08 13:36:15 -0800213 if (config.useLiveData) {
Simon Huntb53e0682014-11-12 13:32:01 -0800214 network.view.alert("Sorry, currently using live data..");
Simon Hunt56d51852014-11-09 13:03:35 -0800215 return true;
Simon Hunt50128c02014-11-08 13:36:15 -0800216 }
Simon Hunt56d51852014-11-09 13:03:35 -0800217 return false;
218 }
Simon Hunt50128c02014-11-08 13:36:15 -0800219
Simon Hunt56d51852014-11-09 13:03:35 -0800220 function testDebug(msg) {
221 if (scenario.debug) {
222 scenario.view.alert(msg);
223 }
224 }
Simon Hunt99c13842014-11-06 18:23:12 -0800225
Simon Hunt56d51852014-11-09 13:03:35 -0800226 function injectTestEvent(view) {
227 if (abortIfLive()) { return; }
228 var sc = scenario,
229 evn = ++sc.evNumber,
230 pfx = sc.evDir + sc.ctx + sc.evPrefix + evn,
231 onosUrl = pfx + sc.evOnos,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800232 uiUrl = pfx + sc.evUi,
233 stack = [
234 { url: onosUrl, cb: handleServerEvent },
235 { url: uiUrl, cb: handleUiEvent }
236 ];
237 recurseFetchEvent(stack, evn);
Simon Hunt56d51852014-11-09 13:03:35 -0800238 }
239
Simon Hunt7cd48f32014-11-09 23:42:50 -0800240 function recurseFetchEvent(stack, evn) {
241 var v = scenario.view,
242 frame;
243 if (stack.length === 0) {
Simon Huntfc274c92014-11-11 11:05:46 -0800244 v.alert('Oops!\n\nNo event #' + evn + ' found.');
Simon Hunt7cd48f32014-11-09 23:42:50 -0800245 return;
246 }
247 frame = stack.shift();
248
249 d3.json(frame.url, function (err, data) {
Simon Hunt99c13842014-11-06 18:23:12 -0800250 if (err) {
Simon Hunt56d51852014-11-09 13:03:35 -0800251 if (err.status === 404) {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800252 // if we didn't find the data, try the next stack frame
253 recurseFetchEvent(stack, evn);
Simon Hunt56d51852014-11-09 13:03:35 -0800254 } else {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800255 v.alert('non-404 error:\n\n' + frame.url + '\n\n' + err);
Simon Hunt56d51852014-11-09 13:03:35 -0800256 }
Simon Hunt99c13842014-11-06 18:23:12 -0800257 } else {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800258 testDebug('loaded: ' + frame.url);
259 frame.cb(data);
Simon Hunt99c13842014-11-06 18:23:12 -0800260 }
261 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800262
Simon Hunt56d51852014-11-09 13:03:35 -0800263 }
Simon Hunt50128c02014-11-08 13:36:15 -0800264
Simon Hunt56d51852014-11-09 13:03:35 -0800265 function handleUiEvent(data) {
Simon Huntbb282f52014-11-10 11:08:19 -0800266 scenario.view.alert('UI Tx: ' + data.event + '\n\n' +
267 JSON.stringify(data));
Simon Hunt56d51852014-11-09 13:03:35 -0800268 }
269
270 function injectStartupEvents(view) {
271 var last = scenario.params.lastAuto || 0;
272 if (abortIfLive()) { return; }
273
274 while (scenario.evNumber < last) {
Simon Hunt1a9eff92014-11-07 11:06:34 -0800275 injectTestEvent(view);
276 }
277 }
278
Simon Hunt934c3ce2014-11-05 11:45:07 -0800279 function toggleBg() {
280 var vis = bgImg.style('visibility');
281 bgImg.style('visibility', (vis === 'hidden') ? 'visible' : 'hidden');
282 }
283
Simon Hunt99c13842014-11-06 18:23:12 -0800284 function cycleLabels() {
Simon Huntbb282f52014-11-10 11:08:19 -0800285 deviceLabelIndex = (deviceLabelIndex === network.deviceLabelCount - 1)
286 ? 0 : deviceLabelIndex + 1;
Simon Hunt5f36d342014-11-08 21:33:14 -0800287
Simon Hunt99c13842014-11-06 18:23:12 -0800288 network.nodes.forEach(function (d) {
Simon Huntbb282f52014-11-10 11:08:19 -0800289 if (d.class === 'device') {
290 updateDeviceLabel(d);
291 }
Simon Hunt99c13842014-11-06 18:23:12 -0800292 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800293 }
294
295 function togglePorts(view) {
Simon Hunt50128c02014-11-08 13:36:15 -0800296 view.alert('togglePorts() callback')
Simon Hunt934c3ce2014-11-05 11:45:07 -0800297 }
298
Simon Hunt6ac93f32014-11-13 12:17:27 -0800299 function unpin() {
300 if (hovered) {
301 hovered.fixed = false;
302 hovered.el.classed('fixed', false);
303 network.force.resume();
304 }
Simon Hunt934c3ce2014-11-05 11:45:07 -0800305 }
306
307 // ==============================
308 // Radio Button Callbacks
309
Simon Hunt195cb382014-11-03 17:50:51 -0800310 function showAllLayers() {
Simon Hunt142d0032014-11-04 20:13:09 -0800311// network.node.classed('inactive', false);
312// network.link.classed('inactive', false);
313// d3.selectAll('svg .port').classed('inactive', false);
314// d3.selectAll('svg .portText').classed('inactive', false);
Simon Hunt934c3ce2014-11-05 11:45:07 -0800315 // TODO ...
Simon Hunt50128c02014-11-08 13:36:15 -0800316 network.view.alert('showAllLayers() callback');
Simon Hunt195cb382014-11-03 17:50:51 -0800317 }
318
319 function showPacketLayer() {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800320 showAllLayers();
321 // TODO ...
Simon Hunt50128c02014-11-08 13:36:15 -0800322 network.view.alert('showPacketLayer() callback');
Simon Hunt195cb382014-11-03 17:50:51 -0800323 }
324
325 function showOpticalLayer() {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800326 showAllLayers();
327 // TODO ...
Simon Hunt50128c02014-11-08 13:36:15 -0800328 network.view.alert('showOpticalLayer() callback');
Simon Hunt195cb382014-11-03 17:50:51 -0800329 }
330
Simon Hunt142d0032014-11-04 20:13:09 -0800331 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800332 // Private functions
333
Simon Hunt99c13842014-11-06 18:23:12 -0800334 function safeId(s) {
335 return s.replace(/[^a-z0-9]/gi, '-');
336 }
337
Simon Huntc7ee0662014-11-05 16:44:37 -0800338 // set the size of the given element to that of the view (reduced if padded)
339 function setSize(el, view, pad) {
340 var padding = pad ? pad * 2 : 0;
Simon Hunt934c3ce2014-11-05 11:45:07 -0800341 el.attr({
Simon Huntc7ee0662014-11-05 16:44:37 -0800342 width: view.width() - padding,
343 height: view.height() - padding
Simon Hunt934c3ce2014-11-05 11:45:07 -0800344 });
345 }
346
Simon Hunt934c3ce2014-11-05 11:45:07 -0800347
Simon Hunt99c13842014-11-06 18:23:12 -0800348 // ==============================
349 // Event handlers for server-pushed events
350
Simon Huntbb282f52014-11-10 11:08:19 -0800351 function logicError(msg) {
352 // TODO, report logic error to server, via websock, so it can be logged
353 network.view.alert('Logic Error:\n\n' + msg);
Simon Huntfc274c92014-11-11 11:05:46 -0800354 console.warn(msg);
Simon Huntbb282f52014-11-10 11:08:19 -0800355 }
356
Simon Hunt99c13842014-11-06 18:23:12 -0800357 var eventDispatch = {
358 addDevice: addDevice,
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800359 addLink: addLink,
Simon Hunt56d51852014-11-09 13:03:35 -0800360 addHost: addHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800361
Simon Huntbb282f52014-11-10 11:08:19 -0800362 updateDevice: updateDevice,
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800363 updateLink: updateLink,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800364 updateHost: updateHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800365
Simon Huntbb282f52014-11-10 11:08:19 -0800366 removeDevice: stillToImplement,
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800367 removeLink: removeLink,
Simon Hunt44031102014-11-11 13:20:36 -0800368 removeHost: removeHost,
Simon Huntb53e0682014-11-12 13:32:01 -0800369
Simon Hunt61d04042014-11-11 17:27:16 -0800370 showDetails: showDetails,
Simon Huntb53e0682014-11-12 13:32:01 -0800371 showPath: showPath,
372 showTraffic: showTraffic
Simon Hunt99c13842014-11-06 18:23:12 -0800373 };
374
375 function addDevice(data) {
Simon Huntfc274c92014-11-11 11:05:46 -0800376 fnTrace('addDevice', data.payload.id);
Simon Hunt99c13842014-11-06 18:23:12 -0800377 var device = data.payload,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800378 nodeData = createDeviceNode(device);
Simon Hunt7cd48f32014-11-09 23:42:50 -0800379 network.nodes.push(nodeData);
380 network.lookup[nodeData.id] = nodeData;
Simon Hunt99c13842014-11-06 18:23:12 -0800381 updateNodes();
382 network.force.start();
383 }
384
Simon Hunt99c13842014-11-06 18:23:12 -0800385 function addLink(data) {
Simon Huntfc274c92014-11-11 11:05:46 -0800386 fnTrace('addLink', data.payload.id);
Simon Hunt99c13842014-11-06 18:23:12 -0800387 var link = data.payload,
388 lnk = createLink(link);
Simon Hunt99c13842014-11-06 18:23:12 -0800389 if (lnk) {
Simon Hunt99c13842014-11-06 18:23:12 -0800390 network.links.push(lnk);
Thomas Vachuska4830d392014-11-09 17:09:56 -0800391 network.lookup[lnk.id] = lnk;
Simon Hunt99c13842014-11-06 18:23:12 -0800392 updateLinks();
393 network.force.start();
394 }
395 }
396
Simon Hunt56d51852014-11-09 13:03:35 -0800397 function addHost(data) {
Simon Huntfc274c92014-11-11 11:05:46 -0800398 fnTrace('addHost', data.payload.id);
Simon Hunt56d51852014-11-09 13:03:35 -0800399 var host = data.payload,
400 node = createHostNode(host),
401 lnk;
Simon Hunt56d51852014-11-09 13:03:35 -0800402 network.nodes.push(node);
403 network.lookup[host.id] = node;
404 updateNodes();
405
406 lnk = createHostLink(host);
407 if (lnk) {
Simon Hunt44031102014-11-11 13:20:36 -0800408 node.linkData = lnk; // cache ref on its host
Simon Hunt56d51852014-11-09 13:03:35 -0800409 network.links.push(lnk);
Thomas Vachuska4830d392014-11-09 17:09:56 -0800410 network.lookup[host.ingress] = lnk;
411 network.lookup[host.egress] = lnk;
Simon Hunt56d51852014-11-09 13:03:35 -0800412 updateLinks();
413 }
414 network.force.start();
415 }
416
Simon Hunt44031102014-11-11 13:20:36 -0800417 // TODO: fold updateX(...) methods into one base method; remove duplication
Simon Huntbb282f52014-11-10 11:08:19 -0800418 function updateDevice(data) {
Simon Huntfc274c92014-11-11 11:05:46 -0800419 fnTrace('updateDevice', data.payload.id);
Simon Huntbb282f52014-11-10 11:08:19 -0800420 var device = data.payload,
421 id = device.id,
422 nodeData = network.lookup[id];
423 if (nodeData) {
424 $.extend(nodeData, device);
425 updateDeviceState(nodeData);
426 } else {
427 logicError('updateDevice lookup fail. ID = "' + id + '"');
428 }
429 }
430
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800431 function updateLink(data) {
Simon Huntfc274c92014-11-11 11:05:46 -0800432 fnTrace('updateLink', data.payload.id);
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800433 var link = data.payload,
434 id = link.id,
435 linkData = network.lookup[id];
436 if (linkData) {
437 $.extend(linkData, link);
438 updateLinkState(linkData);
439 } else {
440 logicError('updateLink lookup fail. ID = "' + id + '"');
441 }
442 }
443
Simon Hunt7cd48f32014-11-09 23:42:50 -0800444 function updateHost(data) {
Simon Huntfc274c92014-11-11 11:05:46 -0800445 fnTrace('updateHost', data.payload.id);
Simon Hunt7cd48f32014-11-09 23:42:50 -0800446 var host = data.payload,
Simon Huntbb282f52014-11-10 11:08:19 -0800447 id = host.id,
448 hostData = network.lookup[id];
449 if (hostData) {
450 $.extend(hostData, host);
451 updateHostState(hostData);
452 } else {
453 logicError('updateHost lookup fail. ID = "' + id + '"');
454 }
Simon Hunt7cd48f32014-11-09 23:42:50 -0800455 }
456
Simon Hunt44031102014-11-11 13:20:36 -0800457 // TODO: fold removeX(...) methods into base method - remove dup code
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800458 function removeLink(data) {
Simon Huntfc274c92014-11-11 11:05:46 -0800459 fnTrace('removeLink', data.payload.id);
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800460 var link = data.payload,
461 id = link.id,
462 linkData = network.lookup[id];
463 if (linkData) {
464 removeLinkElement(linkData);
465 } else {
466 logicError('removeLink lookup fail. ID = "' + id + '"');
467 }
468 }
469
Simon Hunt44031102014-11-11 13:20:36 -0800470 function removeHost(data) {
471 fnTrace('removeHost', data.payload.id);
472 var host = data.payload,
473 id = host.id,
474 hostData = network.lookup[id];
475 if (hostData) {
476 removeHostElement(hostData);
477 } else {
478 logicError('removeHost lookup fail. ID = "' + id + '"');
479 }
480 }
481
Simon Hunt61d04042014-11-11 17:27:16 -0800482 function showDetails(data) {
483 fnTrace('showDetails', data.payload.id);
484 populateDetails(data.payload);
Simon Huntb53e0682014-11-12 13:32:01 -0800485 // TODO: Add single-select actions ...
Simon Hunt61d04042014-11-11 17:27:16 -0800486 detailPane.show();
487 }
488
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800489 function showPath(data) {
Simon Huntfc274c92014-11-11 11:05:46 -0800490 fnTrace('showPath', data.payload.id);
Thomas Vachuska4830d392014-11-09 17:09:56 -0800491 var links = data.payload.links,
492 s = [ data.event + "\n" + links.length ];
493 links.forEach(function (d, i) {
494 s.push(d);
495 });
496 network.view.alert(s.join('\n'));
497
498 links.forEach(function (d, i) {
499 var link = network.lookup[d];
500 if (link) {
Simon Hunt7cd48f32014-11-09 23:42:50 -0800501 link.el.classed('showPath', true);
Thomas Vachuska4830d392014-11-09 17:09:56 -0800502 }
503 });
504
505 // TODO: add selection-highlite lines to links
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800506 }
507
Simon Huntb53e0682014-11-12 13:32:01 -0800508 function showTraffic(data) {
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800509 fnTrace('showTraffic', data.payload.id);
Thomas Vachuska3266abf2014-11-13 09:28:46 -0800510 var paths = data.payload.paths;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800511
Thomas Vachuska3266abf2014-11-13 09:28:46 -0800512 // Revert any links hilighted previously.
513 network.links.forEach(function (d) {
514 d.el.classed('showPath', false);
515 });
516
517 // Now hilight all links in the paths payload.
518 paths.forEach(function (d) {
519 var links = d.links;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800520 links.forEach(function (d, i) {
521 var link = network.lookup[d];
522 if (link) {
523 link.el.classed('showPath', true);
524 }
525 });
526 });
527 //network.view.alert("showTraffic() -- TODO")
Simon Huntb53e0682014-11-12 13:32:01 -0800528 }
529
Simon Hunt56d51852014-11-09 13:03:35 -0800530 // ...............................
531
532 function stillToImplement(data) {
533 var p = data.payload;
534 note(data.event, p.id);
Simon Hunt7cd48f32014-11-09 23:42:50 -0800535 network.view.alert('Not yet implemented: "' + data.event + '"');
Simon Hunt56d51852014-11-09 13:03:35 -0800536 }
Simon Hunt99c13842014-11-06 18:23:12 -0800537
538 function unknownEvent(data) {
Simon Hunt50128c02014-11-08 13:36:15 -0800539 network.view.alert('Unknown event type: "' + data.event + '"');
Simon Hunt99c13842014-11-06 18:23:12 -0800540 }
541
542 function handleServerEvent(data) {
543 var fn = eventDispatch[data.event] || unknownEvent;
544 fn(data);
545 }
546
547 // ==============================
Simon Hunt61d04042014-11-11 17:27:16 -0800548 // Out-going messages...
549
Simon Huntb53e0682014-11-12 13:32:01 -0800550 function userFeedback(msg) {
551 // for now, use the alert pane as is. Maybe different alert style in
552 // the future (centered on view; dismiss button?)
553 network.view.alert(msg);
554 }
555
556 function nSel() {
557 return selectOrder.length;
558 }
Simon Hunt61d04042014-11-11 17:27:16 -0800559 function getSel(idx) {
560 return selections[selectOrder[idx]];
561 }
Simon Huntb53e0682014-11-12 13:32:01 -0800562 function getSelId(idx) {
563 return getSel(idx).obj.id;
564 }
565 function allSelectionsClass(cls) {
566 for (var i=0, n=nSel(); i<n; i++) {
567 if (getSel(i).obj.class !== cls) {
568 return false;
569 }
570 }
571 return true;
572 }
Simon Hunt61d04042014-11-11 17:27:16 -0800573
Thomas Vachuska3266abf2014-11-13 09:28:46 -0800574 function requestTraffic(hoverNode) {
575 if (nSel() > 0 || hoverNode) {
576 var nodes = hoverNode ? selectOrder.concat(hoverNode.id) : selectOrder;
Simon Huntb53e0682014-11-12 13:32:01 -0800577 sendMessage('requestTraffic', {
Thomas Vachuska3266abf2014-11-13 09:28:46 -0800578 ids: nodes
Simon Huntb53e0682014-11-12 13:32:01 -0800579 });
580 } else {
581 userFeedback('Request-Traffic requires one or\n' +
582 'more items to be selected.');
583 }
584 }
585
Simon Hunt61d04042014-11-11 17:27:16 -0800586 function requestPath() {
Simon Huntb53e0682014-11-12 13:32:01 -0800587 if (nSel() === 2 && allSelectionsClass('host')) {
588 sendMessage('requestPath', {
589 one: getSelId(0),
590 two: getSelId(1)
591 });
592 } else {
593 userFeedback('Request-Path requires two\n' +
594 'hosts to be selected.');
595 }
Simon Hunt61d04042014-11-11 17:27:16 -0800596 }
597
Thomas Vachuska3266abf2014-11-13 09:28:46 -0800598 function cancelTraffic(hoverNode) {
599 if (hoverNode && selectOrder.length) {
600 requestTraffic();
601 } else {
602 // FIXME: from where do we get the intent id(s) to send to the server?
603 sendMessage('cancelTraffic', {
604 ids: ["need_the_intent_id"]
605 });
606 }
Simon Huntac9e24f2014-11-12 10:12:21 -0800607 }
608
Simon Hunt61d04042014-11-11 17:27:16 -0800609 // request details for the selected element
610 function requestDetails() {
611 var data = getSel(0).obj,
612 payload = {
613 id: data.id,
614 class: data.class
615 };
616 sendMessage('requestDetails', payload);
617 }
618
619 // ==============================
Simon Hunt99c13842014-11-06 18:23:12 -0800620 // force layout modification functions
621
622 function translate(x, y) {
623 return 'translate(' + x + ',' + y + ')';
624 }
625
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800626 function missMsg(what, id) {
627 return '\n[' + what + '] "' + id + '" missing ';
628 }
629
630 function linkEndPoints(srcId, dstId) {
631 var srcNode = network.lookup[srcId],
632 dstNode = network.lookup[dstId],
633 sMiss = !srcNode ? missMsg('src', srcId) : '',
634 dMiss = !dstNode ? missMsg('dst', dstId) : '';
635
636 if (sMiss || dMiss) {
637 logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
638 return null;
639 }
640 return {
641 source: srcNode,
642 target: dstNode,
643 x1: srcNode.x,
644 y1: srcNode.y,
645 x2: dstNode.x,
646 y2: dstNode.y
647 };
648 }
649
Simon Hunt56d51852014-11-09 13:03:35 -0800650 function createHostLink(host) {
651 var src = host.id,
652 dst = host.cp.device,
Simon Hunt7cd48f32014-11-09 23:42:50 -0800653 id = host.ingress,
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800654 lnk = linkEndPoints(src, dst);
Simon Hunt56d51852014-11-09 13:03:35 -0800655
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800656 if (!lnk) {
Simon Hunt56d51852014-11-09 13:03:35 -0800657 return null;
658 }
659
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800660 // Synthesize link ...
661 $.extend(lnk, {
Thomas Vachuska4830d392014-11-09 17:09:56 -0800662 id: id,
Simon Hunt56d51852014-11-09 13:03:35 -0800663 class: 'link',
Simon Hunt7cd48f32014-11-09 23:42:50 -0800664 type: 'hostLink',
Simon Hunt56d51852014-11-09 13:03:35 -0800665 svgClass: 'link hostLink',
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800666 linkWidth: 1
Simon Hunt7cd48f32014-11-09 23:42:50 -0800667 });
Simon Hunt99c13842014-11-06 18:23:12 -0800668 return lnk;
669 }
670
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800671 function createLink(link) {
672 var lnk = linkEndPoints(link.src, link.dst),
673 type = link.type;
674
675 if (!lnk) {
676 return null;
677 }
678
679 // merge in remaining data
680 $.extend(lnk, link, {
681 class: 'link',
682 svgClass: type ? 'link ' + type : 'link'
683 });
684 return lnk;
Simon Hunt1a9eff92014-11-07 11:06:34 -0800685 }
686
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800687 var widthRatio = 1.4,
688 linkScale = d3.scale.linear()
689 .domain([1, 12])
690 .range([widthRatio, 12 * widthRatio])
691 .clamp(true);
692
693 function updateLinkWidth (d) {
694 // TODO: watch out for .showPath/.showTraffic classes
695 d.el.transition()
696 .duration(1000)
697 .attr('stroke-width', linkScale(d.linkWidth));
698 }
699
700
Simon Hunt99c13842014-11-06 18:23:12 -0800701 function updateLinks() {
702 link = linkG.selectAll('.link')
703 .data(network.links, function (d) { return d.id; });
704
705 // operate on existing links, if necessary
706 // link .foo() .bar() ...
707
708 // operate on entering links:
709 var entering = link.enter()
710 .append('line')
711 .attr({
Simon Hunt99c13842014-11-06 18:23:12 -0800712 class: function (d) { return d.svgClass; },
713 x1: function (d) { return d.x1; },
714 y1: function (d) { return d.y1; },
715 x2: function (d) { return d.x2; },
716 y2: function (d) { return d.y2; },
Simon Hunt1a9eff92014-11-07 11:06:34 -0800717 stroke: config.topo.linkInColor,
718 'stroke-width': config.topo.linkInWidth
Simon Hunt99c13842014-11-06 18:23:12 -0800719 })
720 .transition().duration(1000)
721 .attr({
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800722 'stroke-width': function (d) { return linkScale(d.linkWidth); },
Simon Hunt99c13842014-11-06 18:23:12 -0800723 stroke: '#666' // TODO: remove explicit stroke, rather...
724 });
725
726 // augment links
Simon Hunt7cd48f32014-11-09 23:42:50 -0800727 entering.each(function (d) {
728 var link = d3.select(this);
729 // provide ref to element selection from backing data....
730 d.el = link;
Simon Hunt99c13842014-11-06 18:23:12 -0800731
Simon Hunt7cd48f32014-11-09 23:42:50 -0800732 // TODO: add src/dst port labels etc.
733 });
Thomas Vachuska4830d392014-11-09 17:09:56 -0800734
735 // operate on both existing and new links, if necessary
736 //link .foo() .bar() ...
737
738 // operate on exiting links:
Thomas Vachuska4830d392014-11-09 17:09:56 -0800739 link.exit()
Thomas Vachuska4830d392014-11-09 17:09:56 -0800740 .attr({
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800741 'stroke-dasharray': '3, 3'
Thomas Vachuska4830d392014-11-09 17:09:56 -0800742 })
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800743 .style('opacity', 0.4)
744 .transition()
Simon Huntea80eb42014-11-11 13:46:57 -0800745 .duration(1500)
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800746 .attr({
747 'stroke-dasharray': '3, 12'
748 })
749 .transition()
Simon Huntea80eb42014-11-11 13:46:57 -0800750 .duration(500)
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800751 .style('opacity', 0.0)
Thomas Vachuska4830d392014-11-09 17:09:56 -0800752 .remove();
Simon Hunt99c13842014-11-06 18:23:12 -0800753 }
754
755 function createDeviceNode(device) {
756 // start with the object as is
757 var node = device,
Simon Huntbb282f52014-11-10 11:08:19 -0800758 type = device.type,
759 svgCls = type ? 'node device ' + type : 'node device';
Simon Hunt99c13842014-11-06 18:23:12 -0800760
761 // Augment as needed...
762 node.class = 'device';
Simon Huntbb282f52014-11-10 11:08:19 -0800763 node.svgClass = device.online ? svgCls + ' online' : svgCls;
Simon Hunt99c13842014-11-06 18:23:12 -0800764 positionNode(node);
765
766 // cache label array length
767 network.deviceLabelCount = device.labels.length;
Simon Hunt99c13842014-11-06 18:23:12 -0800768 return node;
769 }
770
Simon Hunt56d51852014-11-09 13:03:35 -0800771 function createHostNode(host) {
772 // start with the object as is
773 var node = host;
774
775 // Augment as needed...
776 node.class = 'host';
Simon Hunt7cd48f32014-11-09 23:42:50 -0800777 if (!node.type) {
778 // TODO: perhaps type would be: {phone, tablet, laptop, endstation} ?
779 node.type = 'endstation';
780 }
Simon Hunt56d51852014-11-09 13:03:35 -0800781 node.svgClass = 'node host';
782 // TODO: consider placing near its switch, if [x,y] not defined
783 positionNode(node);
784
785 // cache label array length
786 network.hostLabelCount = host.labels.length;
Simon Hunt56d51852014-11-09 13:03:35 -0800787 return node;
788 }
789
Simon Hunt99c13842014-11-06 18:23:12 -0800790 function positionNode(node) {
791 var meta = node.metaUi,
Simon Huntac9e24f2014-11-12 10:12:21 -0800792 x = meta && meta.x,
793 y = meta && meta.y,
794 xy;
Simon Hunt99c13842014-11-06 18:23:12 -0800795
Simon Huntac9e24f2014-11-12 10:12:21 -0800796 // If we have [x,y] already, use that...
Simon Hunt99c13842014-11-06 18:23:12 -0800797 if (x && y) {
798 node.fixed = true;
Simon Huntac9e24f2014-11-12 10:12:21 -0800799 node.x = x;
800 node.y = y;
801 return;
Simon Hunt99c13842014-11-06 18:23:12 -0800802 }
Simon Huntac9e24f2014-11-12 10:12:21 -0800803
Paul Greyson6cb8ca02014-11-12 18:09:02 -0800804 var location = node.location;
805 if (location && location.type === 'latlng') {
806 var coord = geoMapProjection([location.lng, location.lat]);
807 node.fixed = true;
808 node.x = coord[0];
809 node.y = coord[1];
810 return;
811 }
812
Simon Huntac9e24f2014-11-12 10:12:21 -0800813 // Note: Placing incoming unpinned nodes at exactly the same point
814 // (center of the view) causes them to explode outwards when
815 // the force layout kicks in. So, we spread them out a bit
816 // initially, to provide a more serene layout convergence.
817 // Additionally, if the node is a host, we place it near
818 // the device it is connected to.
819
820 function spread(s) {
821 return Math.floor((Math.random() * s) - s/2);
822 }
823
824 function randDim(dim) {
825 return dim / 2 + spread(dim * 0.7071);
826 }
827
828 function rand() {
829 return {
830 x: randDim(network.view.width()),
831 y: randDim(network.view.height())
832 };
833 }
834
835 function near(node) {
836 var min = 12,
837 dx = spread(12),
838 dy = spread(12);
839 return {
840 x: node.x + min + dx,
841 y: node.y + min + dy
842 };
843 }
844
845 function getDevice(cp) {
846 var d = network.lookup[cp.device];
847 return d || rand();
848 }
849
850 xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
851 $.extend(node, xy);
Simon Hunt99c13842014-11-06 18:23:12 -0800852 }
853
Simon Hunt99c13842014-11-06 18:23:12 -0800854 function iconUrl(d) {
855 return 'img/' + d.type + '.png';
856 }
857
858 // returns the newly computed bounding box of the rectangle
859 function adjustRectToFitText(n) {
860 var text = n.select('text'),
861 box = text.node().getBBox(),
862 lab = config.labels;
863
864 text.attr('text-anchor', 'middle')
865 .attr('y', '-0.8em')
866 .attr('x', lab.imgPad/2);
867
868 // translate the bbox so that it is centered on [x,y]
869 box.x = -box.width / 2;
870 box.y = -box.height / 2;
871
872 // add padding
873 box.x -= (lab.padLR + lab.imgPad/2);
874 box.width += lab.padLR * 2 + lab.imgPad;
875 box.y -= lab.padTB;
876 box.height += lab.padTB * 2;
877
878 return box;
879 }
880
Simon Hunt1a9eff92014-11-07 11:06:34 -0800881 function mkSvgClass(d) {
882 return d.fixed ? d.svgClass + ' fixed' : d.svgClass;
883 }
884
Simon Hunt7cd48f32014-11-09 23:42:50 -0800885 function hostLabel(d) {
886 var idx = (hostLabelIndex < d.labels.length) ? hostLabelIndex : 0;
887 return d.labels[idx];
888 }
889 function deviceLabel(d) {
890 var idx = (deviceLabelIndex < d.labels.length) ? deviceLabelIndex : 0;
891 return d.labels[idx];
892 }
893 function niceLabel(label) {
894 return (label && label.trim()) ? label : '.';
895 }
896
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800897 function updateDeviceLabel(d) {
898 var label = niceLabel(deviceLabel(d)),
899 node = d.el,
900 box;
901
902 node.select('text')
903 .text(label)
904 .style('opacity', 0)
905 .transition()
906 .style('opacity', 1);
907
908 box = adjustRectToFitText(node);
909
910 node.select('rect')
911 .transition()
912 .attr(box);
913
914 node.select('image')
915 .transition()
916 .attr('x', box.x + config.icons.xoff)
917 .attr('y', box.y + config.icons.yoff);
918 }
919
920 function updateHostLabel(d) {
921 var label = hostLabel(d),
922 host = d.el;
923
924 host.select('text').text(label);
925 }
926
Simon Huntbb282f52014-11-10 11:08:19 -0800927 function updateDeviceState(nodeData) {
928 nodeData.el.classed('online', nodeData.online);
929 updateDeviceLabel(nodeData);
930 // TODO: review what else might need to be updated
931 }
932
Simon Hunt3f03d4a2014-11-10 20:14:37 -0800933 function updateLinkState(linkData) {
934 updateLinkWidth(linkData);
935 // TODO: review what else might need to be updated
936 // update label, if showing
937 }
938
Simon Huntbb282f52014-11-10 11:08:19 -0800939 function updateHostState(hostData) {
940 updateHostLabel(hostData);
941 // TODO: review what else might need to be updated
942 }
943
Simon Hunt6ac93f32014-11-13 12:17:27 -0800944 function nodeMouseOver(d) {
945 console.log("Hover:", d);
946 hovered = d;
947 if (d.class === 'host') {
948 //requestTraffic(d);
949 }
950 }
951
952 function nodeMouseOut(d) {
953 console.log("Unhover:", d);
954 hovered = null;
955 if (d.class === 'host') {
956 //cancelTraffic(d);
957 }
958 }
Simon Huntbb282f52014-11-10 11:08:19 -0800959
Simon Hunt99c13842014-11-06 18:23:12 -0800960 function updateNodes() {
961 node = nodeG.selectAll('.node')
962 .data(network.nodes, function (d) { return d.id; });
963
964 // operate on existing nodes, if necessary
Simon Hunt7cd48f32014-11-09 23:42:50 -0800965 // update host labels
Simon Hunt99c13842014-11-06 18:23:12 -0800966 //node .foo() .bar() ...
967
968 // operate on entering nodes:
969 var entering = node.enter()
970 .append('g')
971 .attr({
972 id: function (d) { return safeId(d.id); },
Simon Hunt1a9eff92014-11-07 11:06:34 -0800973 class: mkSvgClass,
Simon Hunt99c13842014-11-06 18:23:12 -0800974 transform: function (d) { return translate(d.x, d.y); },
975 opacity: 0
976 })
Simon Hunt1a9eff92014-11-07 11:06:34 -0800977 .call(network.drag)
Simon Hunt6ac93f32014-11-13 12:17:27 -0800978 .on('mouseover', nodeMouseOver)
979 .on('mouseout', nodeMouseOut)
Simon Hunt99c13842014-11-06 18:23:12 -0800980 .transition()
981 .attr('opacity', 1);
982
983 // augment device nodes...
984 entering.filter('.device').each(function (d) {
985 var node = d3.select(this),
986 icon = iconUrl(d),
Simon Hunt7cd48f32014-11-09 23:42:50 -0800987 label = niceLabel(deviceLabel(d)),
Simon Hunt99c13842014-11-06 18:23:12 -0800988 box;
989
Simon Hunt7cd48f32014-11-09 23:42:50 -0800990 // provide ref to element from backing data....
991 d.el = node;
992
Simon Hunt99c13842014-11-06 18:23:12 -0800993 node.append('rect')
994 .attr({
995 'rx': 5,
996 'ry': 5
997 });
998
999 node.append('text')
Simon Hunt7cd48f32014-11-09 23:42:50 -08001000 .text(label)
Simon Hunt99c13842014-11-06 18:23:12 -08001001 .attr('dy', '1.1em');
1002
1003 box = adjustRectToFitText(node);
1004
1005 node.select('rect')
1006 .attr(box);
1007
1008 if (icon) {
1009 var cfg = config.icons;
1010 node.append('svg:image')
1011 .attr({
1012 x: box.x + config.icons.xoff,
1013 y: box.y + config.icons.yoff,
1014 width: cfg.w,
1015 height: cfg.h,
1016 'xlink:href': icon
1017 });
1018 }
1019
1020 // debug function to show the modelled x,y coordinates of nodes...
1021 if (debug('showNodeXY')) {
1022 node.select('rect').attr('fill-opacity', 0.5);
1023 node.append('circle')
1024 .attr({
1025 class: 'debug',
1026 cx: 0,
1027 cy: 0,
1028 r: '3px'
1029 });
Simon Huntc7ee0662014-11-05 16:44:37 -08001030 }
1031 });
Simon Hunt934c3ce2014-11-05 11:45:07 -08001032
Simon Hunt56d51852014-11-09 13:03:35 -08001033 // augment host nodes...
1034 entering.filter('.host').each(function (d) {
1035 var node = d3.select(this),
Simon Hunt56d51852014-11-09 13:03:35 -08001036 box;
1037
Simon Hunt7cd48f32014-11-09 23:42:50 -08001038 // provide ref to element from backing data....
1039 d.el = node;
1040
Simon Hunt56d51852014-11-09 13:03:35 -08001041 node.append('circle')
1042 .attr('r', 8); // TODO: define host circle radius
1043
Simon Hunt56d51852014-11-09 13:03:35 -08001044 node.append('text')
Simon Hunt7cd48f32014-11-09 23:42:50 -08001045 .text(hostLabel)
1046 .attr('dy', '1.3em')
1047 .attr('text-anchor', 'middle');
Simon Hunt56d51852014-11-09 13:03:35 -08001048
1049 // debug function to show the modelled x,y coordinates of nodes...
1050 if (debug('showNodeXY')) {
1051 node.select('circle').attr('fill-opacity', 0.5);
1052 node.append('circle')
1053 .attr({
1054 class: 'debug',
1055 cx: 0,
1056 cy: 0,
1057 r: '3px'
1058 });
1059 }
1060 });
Simon Huntc7ee0662014-11-05 16:44:37 -08001061
Simon Hunt99c13842014-11-06 18:23:12 -08001062 // operate on both existing and new nodes, if necessary
1063 //node .foo() .bar() ...
Simon Huntc7ee0662014-11-05 16:44:37 -08001064
Simon Hunt99c13842014-11-06 18:23:12 -08001065 // operate on exiting nodes:
Simon Huntea80eb42014-11-11 13:46:57 -08001066 // Note that the node is removed after 2 seconds.
1067 // Sub element animations should be shorter than 2 seconds.
1068 var exiting = node.exit()
Simon Hunt44031102014-11-11 13:20:36 -08001069 .transition()
1070 .duration(2000)
Simon Huntea80eb42014-11-11 13:46:57 -08001071 .style('opacity', 0)
Simon Hunt99c13842014-11-06 18:23:12 -08001072 .remove();
Simon Huntea80eb42014-11-11 13:46:57 -08001073
1074 // host node exits....
1075 exiting.filter('.host').each(function (d) {
1076 var node = d3.select(this);
1077
1078 node.select('text')
1079 .style('opacity', 0.5)
1080 .transition()
1081 .duration(1000)
1082 .style('opacity', 0);
1083 // note, leave <g>.remove to remove this element
1084
1085 node.select('circle')
1086 .style('stroke-fill', '#555')
1087 .style('fill', '#888')
1088 .style('opacity', 0.5)
1089 .transition()
1090 .duration(1500)
1091 .attr('r', 0);
1092 // note, leave <g>.remove to remove this element
1093
1094 });
1095
1096 // TODO: device node exits
Simon Huntc7ee0662014-11-05 16:44:37 -08001097 }
1098
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001099 function find(id, array) {
1100 for (var idx = 0, n = array.length; idx < n; idx++) {
1101 if (array[idx].id === id) {
1102 return idx;
1103 }
1104 }
1105 return -1;
1106 }
1107
1108 function removeLinkElement(linkData) {
1109 // remove from lookup cache
1110 delete network.lookup[linkData.id];
1111 // remove from links array
1112 var idx = find(linkData.id, network.links);
Simon Huntfc274c92014-11-11 11:05:46 -08001113 network.links.splice(idx, 1);
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001114 // remove from SVG
1115 updateLinks();
Simon Hunt44031102014-11-11 13:20:36 -08001116 network.force.resume();
Simon Hunt3f03d4a2014-11-10 20:14:37 -08001117 }
Simon Huntc7ee0662014-11-05 16:44:37 -08001118
Simon Hunt44031102014-11-11 13:20:36 -08001119 function removeHostElement(hostData) {
1120 // first, remove associated hostLink...
1121 removeLinkElement(hostData.linkData);
1122
1123 // remove from lookup cache
1124 delete network.lookup[hostData.id];
1125 // remove from nodes array
1126 var idx = find(hostData.id, network.nodes);
1127 network.nodes.splice(idx, 1);
1128 // remove from SVG
1129 updateNodes();
1130 network.force.resume();
1131 }
1132
1133
Simon Huntc7ee0662014-11-05 16:44:37 -08001134 function tick() {
1135 node.attr({
Simon Hunt99c13842014-11-06 18:23:12 -08001136 transform: function (d) { return translate(d.x, d.y); }
Simon Huntc7ee0662014-11-05 16:44:37 -08001137 });
1138
1139 link.attr({
1140 x1: function (d) { return d.source.x; },
1141 y1: function (d) { return d.source.y; },
1142 x2: function (d) { return d.target.x; },
1143 y2: function (d) { return d.target.y; }
1144 });
1145 }
Simon Hunt934c3ce2014-11-05 11:45:07 -08001146
1147 // ==============================
Thomas Vachuska7d638d32014-11-07 10:24:43 -08001148 // Web-Socket for live data
1149
1150 function webSockUrl() {
1151 return document.location.toString()
1152 .replace(/\#.*/, '')
1153 .replace('http://', 'ws://')
1154 .replace('https://', 'wss://')
1155 .replace('index2.html', config.webSockUrl);
1156 }
1157
1158 webSock = {
1159 ws : null,
1160
1161 connect : function() {
1162 webSock.ws = new WebSocket(webSockUrl());
1163
1164 webSock.ws.onopen = function() {
Simon Hunt0c6d4192014-11-12 12:07:10 -08001165 noWebSock(false);
Thomas Vachuska7d638d32014-11-07 10:24:43 -08001166 };
1167
1168 webSock.ws.onmessage = function(m) {
1169 if (m.data) {
Simon Huntbb282f52014-11-10 11:08:19 -08001170 wsTraceRx(m.data);
Thomas Vachuskad472c6e2014-11-07 19:11:05 -08001171 handleServerEvent(JSON.parse(m.data));
Thomas Vachuska7d638d32014-11-07 10:24:43 -08001172 }
1173 };
1174
1175 webSock.ws.onclose = function(m) {
1176 webSock.ws = null;
Simon Hunt0c6d4192014-11-12 12:07:10 -08001177 noWebSock(true);
Thomas Vachuska7d638d32014-11-07 10:24:43 -08001178 };
1179 },
1180
1181 send : function(text) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001182 if (text != null) {
Thomas Vachuska7d638d32014-11-07 10:24:43 -08001183 webSock._send(text);
1184 }
1185 },
1186
1187 _send : function(message) {
1188 if (webSock.ws) {
1189 webSock.ws.send(message);
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001190 } else {
Simon Hunt56d51852014-11-09 13:03:35 -08001191 network.view.alert('no web socket open\n\n' + message);
Thomas Vachuska7d638d32014-11-07 10:24:43 -08001192 }
1193 }
1194
1195 };
1196
Simon Hunt0c6d4192014-11-12 12:07:10 -08001197 function noWebSock(b) {
1198 mask.style('display',b ? 'block' : 'none');
1199 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001200
Simon Hunt61d04042014-11-11 17:27:16 -08001201 // TODO: use cache of pending messages (key = sid) to reconcile responses
1202
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001203 function sendMessage(evType, payload) {
1204 var toSend = {
Simon Huntbb282f52014-11-10 11:08:19 -08001205 event: evType,
1206 sid: ++sid,
1207 payload: payload
1208 },
1209 asText = JSON.stringify(toSend);
1210 wsTraceTx(asText);
1211 webSock.send(asText);
1212 }
1213
1214 function wsTraceTx(msg) {
1215 wsTrace('tx', msg);
1216 }
1217 function wsTraceRx(msg) {
1218 wsTrace('rx', msg);
1219 }
1220 function wsTrace(rxtx, msg) {
Simon Huntbb282f52014-11-10 11:08:19 -08001221 console.log('[' + rxtx + '] ' + msg);
1222 // TODO: integrate with trace view
1223 //if (trace) {
1224 // trace.output(rxtx, msg);
1225 //}
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001226 }
1227
1228
1229 // ==============================
1230 // Selection stuff
1231
1232 function selectObject(obj, el) {
1233 var n,
Simon Hunt01095ff2014-11-13 16:37:29 -08001234 srcEv = d3.event.sourceEvent,
1235 meta = srcEv.metaKey,
1236 shift = srcEv.shiftKey;
1237
1238 if ((metaSelect() && !meta) || (!metaSelect() && meta)) {
1239 return;
1240 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001241
1242 if (el) {
1243 n = d3.select(el);
1244 } else {
1245 node.each(function(d) {
1246 if (d == obj) {
1247 n = d3.select(el = this);
1248 }
1249 });
1250 }
1251 if (!n) return;
1252
Simon Hunt01095ff2014-11-13 16:37:29 -08001253 if (shift && n.classed('selected')) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001254 deselectObject(obj.id);
Simon Hunt61d04042014-11-11 17:27:16 -08001255 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001256 return;
1257 }
1258
Simon Hunt01095ff2014-11-13 16:37:29 -08001259 if (!shift) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001260 deselectAll();
1261 }
1262
Simon Huntc31d5692014-11-12 13:27:18 -08001263 selections[obj.id] = { obj: obj, el: el };
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001264 selectOrder.push(obj.id);
1265
1266 n.classed('selected', true);
Simon Hunt61d04042014-11-11 17:27:16 -08001267 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001268 }
1269
1270 function deselectObject(id) {
Simon Huntc31d5692014-11-12 13:27:18 -08001271 var obj = selections[id],
1272 idx;
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001273 if (obj) {
1274 d3.select(obj.el).classed('selected', false);
Simon Hunt61d04042014-11-11 17:27:16 -08001275 delete selections[id];
Simon Huntc31d5692014-11-12 13:27:18 -08001276 idx = $.inArray(id, selectOrder);
1277 if (idx >= 0) {
1278 selectOrder.splice(idx, 1);
1279 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001280 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001281 }
1282
1283 function deselectAll() {
1284 // deselect all nodes in the network...
1285 node.classed('selected', false);
1286 selections = {};
1287 selectOrder = [];
Simon Hunt61d04042014-11-11 17:27:16 -08001288 updateDetailPane();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001289 }
1290
Simon Hunt61d04042014-11-11 17:27:16 -08001291 // update the state of the detail pane, based on current selections
1292 function updateDetailPane() {
1293 var nSel = selectOrder.length;
1294 if (!nSel) {
1295 detailPane.hide();
1296 } else if (nSel === 1) {
1297 singleSelect();
1298 } else {
1299 multiSelect();
1300 }
1301 }
1302
1303 function singleSelect() {
1304 requestDetails();
Simon Huntb53e0682014-11-12 13:32:01 -08001305 // NOTE: detail pane will be shown from showDetails event callback
Simon Hunt61d04042014-11-11 17:27:16 -08001306 }
1307
1308 function multiSelect() {
Simon Huntb53e0682014-11-12 13:32:01 -08001309 populateMultiSelect();
1310 // TODO: Add multi-select actions ...
1311 }
1312
1313 function addSep(tbody) {
1314 var tr = tbody.append('tr');
1315 $('<hr>').appendTo(tr.append('td').attr('colspan', 2));
1316 }
1317
1318 function addProp(tbody, label, value) {
1319 var tr = tbody.append('tr');
1320
1321 tr.append('td')
1322 .attr('class', 'label')
1323 .text(label + ' :');
1324
1325 tr.append('td')
1326 .attr('class', 'value')
1327 .text(value);
1328 }
1329
1330 function populateMultiSelect() {
1331 detailPane.empty();
1332
1333 var title = detailPane.append("h2"),
1334 table = detailPane.append("table"),
1335 tbody = table.append("tbody");
1336
1337 title.text('Multi-Select...');
1338
1339 selectOrder.forEach(function (d, i) {
1340 addProp(tbody, i+1, d);
1341 });
Simon Hunt61d04042014-11-11 17:27:16 -08001342 }
1343
1344 function populateDetails(data) {
1345 detailPane.empty();
1346
1347 var title = detailPane.append("h2"),
1348 table = detailPane.append("table"),
1349 tbody = table.append("tbody");
1350
1351 $('<img src="img/' + data.type + '.png">').appendTo(title);
1352 $('<span>').attr('class', 'icon').text(data.id).appendTo(title);
1353
1354 data.propOrder.forEach(function(p) {
1355 if (p === '-') {
1356 addSep(tbody);
1357 } else {
1358 addProp(tbody, p, data.props[p]);
1359 }
1360 });
Simon Hunt61d04042014-11-11 17:27:16 -08001361 }
1362
Paul Greysonfcba0e82014-11-13 10:21:16 -08001363 function zoomPan(scale, translate) {
1364 zoomPanContainer.attr("transform", "translate(" + translate + ")scale(" + scale + ")");
1365 // keep the map lines constant width while zooming
1366 bgImg.style("stroke-width", 2.0 / scale + "px");
1367 }
1368
1369 function resetZoomPan() {
1370 zoomPan(1, [0,0]);
1371 zoom.scale(1).translate([0,0]);
1372 }
1373
1374 function setupZoomPan() {
1375 function zoomed() {
Simon Hunt01095ff2014-11-13 16:37:29 -08001376 if (!metaSelect() ^ !d3.event.sourceEvent.metaKey) {
Paul Greysonfcba0e82014-11-13 10:21:16 -08001377 zoomPan(d3.event.scale, d3.event.translate);
1378 }
1379 }
1380
1381 zoom = d3.behavior.zoom()
1382 .translate([0, 0])
1383 .scale(1)
1384 .scaleExtent([1, 8])
1385 .on("zoom", zoomed);
1386
1387 svg.call(zoom);
1388 }
1389
Simon Hunt61d04042014-11-11 17:27:16 -08001390 // ==============================
1391 // Test harness code
Simon Hunt56d51852014-11-09 13:03:35 -08001392
1393 function prepareScenario(view, ctx, dbg) {
1394 var sc = scenario,
1395 urlSc = sc.evDir + ctx + sc.evScenario;
1396
1397 if (!ctx) {
1398 view.alert("No scenario specified (null ctx)");
1399 return;
1400 }
1401
1402 sc.view = view;
1403 sc.ctx = ctx;
1404 sc.debug = dbg;
1405 sc.evNumber = 0;
1406
1407 d3.json(urlSc, function(err, data) {
Simon Huntbb282f52014-11-10 11:08:19 -08001408 var p = data && data.params || {},
1409 desc = data && data.description || null,
Simon Huntfc274c92014-11-11 11:05:46 -08001410 intro = data && data.title;
Simon Huntbb282f52014-11-10 11:08:19 -08001411
Simon Hunt56d51852014-11-09 13:03:35 -08001412 if (err) {
1413 view.alert('No scenario found:\n\n' + urlSc + '\n\n' + err);
1414 } else {
1415 sc.params = p;
Simon Huntbb282f52014-11-10 11:08:19 -08001416 if (desc) {
1417 intro += '\n\n ' + desc.join('\n ');
1418 }
1419 view.alert(intro);
Simon Hunt56d51852014-11-09 13:03:35 -08001420 }
1421 });
1422
1423 }
1424
Simon Hunt01095ff2014-11-13 16:37:29 -08001425 // ==============================
1426 // Toggle Buttons in masthead
Simon Hunt0c6d4192014-11-12 12:07:10 -08001427
Simon Huntf8e5b4e02014-11-13 11:17:57 -08001428 // TODO: toggle button (and other widgets in the masthead) should be provided
1429 // by the framework; not generated by the view.
1430
Simon Hunt01095ff2014-11-13 16:37:29 -08001431 var showTrafficOnHover,
1432 metaToSelect;
Simon Huntf8e5b4e02014-11-13 11:17:57 -08001433
1434 function addButtonBar(view) {
1435 var bb = d3.select('#mast')
1436 .append('span').classed('right', true).attr('id', 'bb');
1437
Simon Hunt01095ff2014-11-13 16:37:29 -08001438 metaToSelect = bb.append('span')
1439 .classed('btn', true)
1440 .text('Meta to select')
1441 .on('click', toggleMetaSelect);
1442
1443 showTrafficOnHover = bb.append('span')
Simon Huntf8e5b4e02014-11-13 11:17:57 -08001444 .classed('btn', true)
1445 .text('Show traffic on hover')
Simon Hunt01095ff2014-11-13 16:37:29 -08001446 .on('click', toggleTrafficHover);
Simon Huntf8e5b4e02014-11-13 11:17:57 -08001447 }
1448
Simon Hunt01095ff2014-11-13 16:37:29 -08001449 function toggleTrafficHover() {
Simon Huntf8e5b4e02014-11-13 11:17:57 -08001450 showTrafficOnHover.classed('active', !trafficHover());
1451 }
1452
1453 function trafficHover() {
1454 return showTrafficOnHover.classed('active');
1455 }
1456
Simon Hunt01095ff2014-11-13 16:37:29 -08001457 function toggleMetaSelect() {
1458 metaToSelect.classed('active', !metaSelect());
1459 }
1460
1461 function metaSelect() {
1462 return metaToSelect.classed('active');
1463 }
1464
Thomas Vachuska7d638d32014-11-07 10:24:43 -08001465 // ==============================
Simon Hunt142d0032014-11-04 20:13:09 -08001466 // View life-cycle callbacks
Simon Hunt195cb382014-11-03 17:50:51 -08001467
Simon Huntf67722a2014-11-10 09:32:06 -08001468 function preload(view, ctx, flags) {
Simon Hunt142d0032014-11-04 20:13:09 -08001469 var w = view.width(),
1470 h = view.height(),
Simon Huntc7ee0662014-11-05 16:44:37 -08001471 fcfg = config.force,
1472 fpad = fcfg.pad,
1473 forceDim = [w - 2*fpad, h - 2*fpad];
Simon Hunt195cb382014-11-03 17:50:51 -08001474
Simon Huntbb282f52014-11-10 11:08:19 -08001475 // TODO: set trace api
1476 //trace = onos.exported.webSockTrace;
1477
Simon Hunt142d0032014-11-04 20:13:09 -08001478 // NOTE: view.$div is a D3 selection of the view's div
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001479 var viewBox = '0 0 ' + config.logicalSize + ' ' + config.logicalSize;
1480 svg = view.$div.append('svg').attr('viewBox', viewBox);
Simon Hunt934c3ce2014-11-05 11:45:07 -08001481 setSize(svg, view);
1482
Paul Greysonfcba0e82014-11-13 10:21:16 -08001483 zoomPanContainer = svg.append('g').attr('id', 'zoomPanContainer');
1484
1485 setupZoomPan();
1486
Simon Hunt1a9eff92014-11-07 11:06:34 -08001487 // add blue glow filter to svg layer
Paul Greysonfcba0e82014-11-13 10:21:16 -08001488 d3u.appendGlow(zoomPanContainer);
Simon Hunt1a9eff92014-11-07 11:06:34 -08001489
Simon Huntc7ee0662014-11-05 16:44:37 -08001490 // group for the topology
Paul Greysonfcba0e82014-11-13 10:21:16 -08001491 topoG = zoomPanContainer.append('g')
Simon Huntd3b7d512014-11-12 15:48:41 -08001492 .attr('id', 'topo-G')
Simon Huntc7ee0662014-11-05 16:44:37 -08001493 .attr('transform', fcfg.translate());
1494
1495 // subgroups for links and nodes
1496 linkG = topoG.append('g').attr('id', 'links');
1497 nodeG = topoG.append('g').attr('id', 'nodes');
1498
1499 // selection of nodes and links
1500 link = linkG.selectAll('.link');
1501 node = nodeG.selectAll('.node');
1502
Simon Hunt7cd48f32014-11-09 23:42:50 -08001503 function chrg(d) {
1504 return fcfg.charge[d.class] || -12000;
1505 }
Simon Hunt99c13842014-11-06 18:23:12 -08001506 function ldist(d) {
Simon Hunt7cd48f32014-11-09 23:42:50 -08001507 return fcfg.linkDistance[d.type] || 50;
Simon Hunt99c13842014-11-06 18:23:12 -08001508 }
1509 function lstrg(d) {
Simon Hunt7cd48f32014-11-09 23:42:50 -08001510 // 0.0 - 1.0
1511 return fcfg.linkStrength[d.type] || 1.0;
Simon Hunt99c13842014-11-06 18:23:12 -08001512 }
1513
Simon Hunt1a9eff92014-11-07 11:06:34 -08001514 function selectCb(d, self) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001515 selectObject(d, self);
Simon Hunt1a9eff92014-11-07 11:06:34 -08001516 }
1517
1518 function atDragEnd(d, self) {
Simon Hunt56d51852014-11-09 13:03:35 -08001519 // once we've finished moving, pin the node in position
1520 d.fixed = true;
1521 d3.select(self).classed('fixed', true);
1522 if (config.useLiveData) {
Simon Hunt902c9922014-11-11 11:59:31 -08001523 sendUpdateMeta(d);
Simon Hunt1a9eff92014-11-07 11:06:34 -08001524 }
1525 }
1526
Simon Hunt902c9922014-11-11 11:59:31 -08001527 function sendUpdateMeta(d) {
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001528 sendMessage('updateMeta', {
1529 id: d.id,
1530 'class': d.class,
Simon Hunt902c9922014-11-11 11:59:31 -08001531 'memento': {
Simon Hunt01095ff2014-11-13 16:37:29 -08001532 x: d.x,
1533 y: d.y
Simon Hunt902c9922014-11-11 11:59:31 -08001534 }
Thomas Vachuskad1be50d2014-11-08 16:10:20 -08001535 });
1536 }
1537
Simon Huntc7ee0662014-11-05 16:44:37 -08001538 // set up the force layout
1539 network.force = d3.layout.force()
1540 .size(forceDim)
1541 .nodes(network.nodes)
1542 .links(network.links)
Simon Hunt7cd48f32014-11-09 23:42:50 -08001543 .gravity(0.4)
1544 .friction(0.7)
1545 .charge(chrg)
Simon Hunt99c13842014-11-06 18:23:12 -08001546 .linkDistance(ldist)
1547 .linkStrength(lstrg)
Simon Huntc7ee0662014-11-05 16:44:37 -08001548 .on('tick', tick);
Simon Hunt195cb382014-11-03 17:50:51 -08001549
Simon Hunt01095ff2014-11-13 16:37:29 -08001550 network.drag = d3u.createDragBehavior(network.force,
1551 selectCb, atDragEnd, metaSelect);
Simon Hunt0c6d4192014-11-12 12:07:10 -08001552
1553 // create mask layer for when we lose connection to server.
1554 mask = view.$div.append('div').attr('id','topo-mask');
1555 para(mask, 'Oops!');
1556 para(mask, 'Web-socket connection to server closed...');
1557 para(mask, 'Try refreshing the page.');
Simon Hunt1a9eff92014-11-07 11:06:34 -08001558 }
Simon Hunt195cb382014-11-03 17:50:51 -08001559
Simon Hunt01095ff2014-11-13 16:37:29 -08001560 function para(sel, text) {
1561 sel.append('p').text(text);
1562 }
1563
1564
Simon Hunt56d51852014-11-09 13:03:35 -08001565 function load(view, ctx, flags) {
Simon Huntf67722a2014-11-10 09:32:06 -08001566 // resize, in case the window was resized while we were not loaded
1567 resize(view, ctx, flags);
1568
Simon Hunt99c13842014-11-06 18:23:12 -08001569 // cache the view token, so network topo functions can access it
1570 network.view = view;
Simon Hunt56d51852014-11-09 13:03:35 -08001571 config.useLiveData = !flags.local;
1572
1573 if (!config.useLiveData) {
1574 prepareScenario(view, ctx, flags.debug);
1575 }
Simon Hunt99c13842014-11-06 18:23:12 -08001576
1577 // set our radio buttons and key bindings
Simon Hunt934c3ce2014-11-05 11:45:07 -08001578 view.setRadio(btnSet);
1579 view.setKeys(keyDispatch);
Simon Hunt195cb382014-11-03 17:50:51 -08001580
Simon Huntf8e5b4e02014-11-13 11:17:57 -08001581 // patch in our "button bar" for now
1582 // TODO: implement a more official frameworky way of doing this..
1583 addButtonBar(view);
1584
Simon Huntd3b7d512014-11-12 15:48:41 -08001585 // Load map data asynchronously; complete startup after that..
1586 loadGeoJsonData();
1587 }
1588
1589 // TODO: move these to config/state portion of script
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001590 var geoJsonUrl = 'json/map/continental_us.json', // TODO: Paul
Simon Huntd3b7d512014-11-12 15:48:41 -08001591 geoJson;
1592
1593 function loadGeoJsonData() {
1594 d3.json(geoJsonUrl, function (err, data) {
1595 if (err) {
1596 // fall back to USA map background
1597 loadStaticMap();
1598 } else {
1599 geoJson = data;
1600 loadGeoMap();
1601 }
1602
1603 // finally, connect to the server...
1604 if (config.useLiveData) {
1605 webSock.connect();
1606 }
1607 });
1608 }
1609
1610 function showBg() {
1611 return config.options.showBackground ? 'visible' : 'hidden';
1612 }
1613
1614 function loadStaticMap() {
1615 fnTrace('loadStaticMap', config.backgroundUrl);
1616 var w = network.view.width(),
1617 h = network.view.height();
1618
1619 // load the background image
1620 bgImg = svg.insert('svg:image', '#topo-G')
1621 .attr({
1622 id: 'topo-bg',
1623 width: w,
1624 height: h,
1625 'xlink:href': config.backgroundUrl
1626 })
1627 .style({
1628 visibility: showBg()
1629 });
1630 }
1631
1632 function loadGeoMap() {
1633 fnTrace('loadGeoMap', geoJsonUrl);
Simon Huntd3b7d512014-11-12 15:48:41 -08001634
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001635 // extracts the topojson data into geocoordinate-based geometry
1636 var topoData = topojson.feature(geoJson, geoJson.objects.states);
Simon Huntd3b7d512014-11-12 15:48:41 -08001637
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001638 // see: http://bl.ocks.org/mbostock/4707858
1639 geoMapProjection = d3.geo.mercator();
1640 var path = d3.geo.path().projection(geoMapProjection);
Simon Huntd3b7d512014-11-12 15:48:41 -08001641
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001642 geoMapProjection
1643 .scale(1)
1644 .translate([0, 0]);
Simon Huntd3b7d512014-11-12 15:48:41 -08001645
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001646 // [[x1,y1],[x2,y2]]
1647 var b = path.bounds(topoData);
Paul Greysonfcba0e82014-11-13 10:21:16 -08001648 // size map to 95% of minimum dimension to fill space
1649 var s = .95 / Math.min((b[1][0] - b[0][0]) / config.logicalSize, (b[1][1] - b[0][1]) / config.logicalSize);
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001650 var t = [(config.logicalSize - s * (b[1][0] + b[0][0])) / 2, (config.logicalSize - s * (b[1][1] + b[0][1])) / 2];
Simon Huntd3b7d512014-11-12 15:48:41 -08001651
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001652 geoMapProjection
1653 .scale(s)
1654 .translate(t);
1655
Paul Greysonfcba0e82014-11-13 10:21:16 -08001656 bgImg = zoomPanContainer.insert("g", '#topo-G');
Paul Greyson6cb8ca02014-11-12 18:09:02 -08001657 bgImg.attr('id', 'map').selectAll('path')
1658 .data(topoData.features)
1659 .enter()
1660 .append('path')
1661 .attr('d', path);
Simon Hunt195cb382014-11-03 17:50:51 -08001662 }
1663
Simon Huntf67722a2014-11-10 09:32:06 -08001664 function resize(view, ctx, flags) {
Simon Hunt934c3ce2014-11-05 11:45:07 -08001665 setSize(svg, view);
Simon Hunt142d0032014-11-04 20:13:09 -08001666 }
1667
1668
1669 // ==============================
1670 // View registration
Simon Hunt195cb382014-11-03 17:50:51 -08001671
Simon Hunt25248912014-11-04 11:25:48 -08001672 onos.ui.addView('topo', {
Simon Hunt142d0032014-11-04 20:13:09 -08001673 preload: preload,
1674 load: load,
1675 resize: resize
Simon Hunt195cb382014-11-03 17:50:51 -08001676 });
1677
Simon Hunt61d04042014-11-11 17:27:16 -08001678 detailPane = onos.ui.addFloatingPanel('topo-detail');
1679
Simon Hunt195cb382014-11-03 17:50:51 -08001680}(ONOS));