GUI -- Test events: unpinned the first node; increased a few link widths.
- added alerts pane to framework. - added library registration mechanism to framework. - created d3Utils library - reimplemented drag behavior of nodes. Change-Id: I501f4ab6eded8393948cede903573580599258b1
Showing
14 changed files
with
324 additions
and
67 deletions
web/gui/src/main/webapp/d3Utils.js
0 → 100644
| 1 | +/* | ||
| 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 | +/* | ||
| 18 | + Utility functions for D3 visualizations. | ||
| 19 | + | ||
| 20 | + @author Simon Hunt | ||
| 21 | + */ | ||
| 22 | + | ||
| 23 | +(function (onos) { | ||
| 24 | + 'use strict'; | ||
| 25 | + | ||
| 26 | + function createDragBehavior(force, selectCb, atDragEnd) { | ||
| 27 | + var draggedThreshold = d3.scale.linear() | ||
| 28 | + .domain([0, 0.1]) | ||
| 29 | + .range([5, 20]) | ||
| 30 | + .clamp(true), | ||
| 31 | + drag; | ||
| 32 | + | ||
| 33 | + // TODO: better validation of parameters | ||
| 34 | + if (!$.isFunction(selectCb)) { | ||
| 35 | + alert('d3util.createDragBehavior(): selectCb is not a function') | ||
| 36 | + } | ||
| 37 | + if (!$.isFunction(atDragEnd)) { | ||
| 38 | + alert('d3util.createDragBehavior(): atDragEnd is not a function') | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + function dragged(d) { | ||
| 42 | + var threshold = draggedThreshold(force.alpha()), | ||
| 43 | + dx = d.oldX - d.px, | ||
| 44 | + dy = d.oldY - d.py; | ||
| 45 | + if (Math.abs(dx) >= threshold || Math.abs(dy) >= threshold) { | ||
| 46 | + d.dragged = true; | ||
| 47 | + } | ||
| 48 | + return d.dragged; | ||
| 49 | + } | ||
| 50 | + | ||
| 51 | + drag = d3.behavior.drag() | ||
| 52 | + .origin(function(d) { return d; }) | ||
| 53 | + .on('dragstart', function(d) { | ||
| 54 | + d.oldX = d.x; | ||
| 55 | + d.oldY = d.y; | ||
| 56 | + d.dragged = false; | ||
| 57 | + d.fixed |= 2; | ||
| 58 | + }) | ||
| 59 | + .on('drag', function(d) { | ||
| 60 | + d.px = d3.event.x; | ||
| 61 | + d.py = d3.event.y; | ||
| 62 | + if (dragged(d)) { | ||
| 63 | + if (!force.alpha()) { | ||
| 64 | + force.alpha(.025); | ||
| 65 | + } | ||
| 66 | + } | ||
| 67 | + }) | ||
| 68 | + .on('dragend', function(d) { | ||
| 69 | + if (!dragged(d)) { | ||
| 70 | + // consider this the same as a 'click' (selection of node) | ||
| 71 | + selectCb(d, this); // TODO: set 'this' context instead of param | ||
| 72 | + } | ||
| 73 | + d.fixed &= ~6; | ||
| 74 | + | ||
| 75 | + // hook at the end of a drag gesture | ||
| 76 | + atDragEnd(d, this); // TODO: set 'this' context instead of param | ||
| 77 | + }); | ||
| 78 | + | ||
| 79 | + return drag; | ||
| 80 | + } | ||
| 81 | + | ||
| 82 | + function appendGlow(svg) { | ||
| 83 | + // TODO: parameterize color | ||
| 84 | + | ||
| 85 | + var glow = svg.append('filter') | ||
| 86 | + .attr('x', '-50%') | ||
| 87 | + .attr('y', '-50%') | ||
| 88 | + .attr('width', '200%') | ||
| 89 | + .attr('height', '200%') | ||
| 90 | + .attr('id', 'blue-glow'); | ||
| 91 | + | ||
| 92 | + glow.append('feColorMatrix') | ||
| 93 | + .attr('type', 'matrix') | ||
| 94 | + .attr('values', '0 0 0 0 0 ' + | ||
| 95 | + '0 0 0 0 0 ' + | ||
| 96 | + '0 0 0 0 .7 ' + | ||
| 97 | + '0 0 0 1 0 '); | ||
| 98 | + | ||
| 99 | + glow.append('feGaussianBlur') | ||
| 100 | + .attr('stdDeviation', 3) | ||
| 101 | + .attr('result', 'coloredBlur'); | ||
| 102 | + | ||
| 103 | + glow.append('feMerge').selectAll('feMergeNode') | ||
| 104 | + .data(['coloredBlur', 'SourceGraphic']) | ||
| 105 | + .enter().append('feMergeNode') | ||
| 106 | + .attr('in', String); | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + // === register the functions as a library | ||
| 110 | + onos.ui.addLib('d3util', { | ||
| 111 | + createDragBehavior: createDragBehavior, | ||
| 112 | + appendGlow: appendGlow | ||
| 113 | + }); | ||
| 114 | + | ||
| 115 | +}(ONOS)); |
| ... | @@ -64,6 +64,9 @@ | ... | @@ -64,6 +64,9 @@ |
| 64 | <div id="overlays"> | 64 | <div id="overlays"> |
| 65 | <!-- NOTE: overlays injected here, as needed --> | 65 | <!-- NOTE: overlays injected here, as needed --> |
| 66 | </div> | 66 | </div> |
| 67 | + <div id="alerts"> | ||
| 68 | + <!-- NOTE: alert content injected here, as needed --> | ||
| 69 | + </div> | ||
| 67 | </div> | 70 | </div> |
| 68 | 71 | ||
| 69 | <!-- Initialize the UI...--> | 72 | <!-- Initialize the UI...--> |
| ... | @@ -76,6 +79,9 @@ | ... | @@ -76,6 +79,9 @@ |
| 76 | }); | 79 | }); |
| 77 | </script> | 80 | </script> |
| 78 | 81 | ||
| 82 | + <!-- Library module files included here --> | ||
| 83 | + <script src="d3Utils.js"></script> | ||
| 84 | + | ||
| 79 | <!-- Framework module files included here --> | 85 | <!-- Framework module files included here --> |
| 80 | <script src="mast2.js"></script> | 86 | <script src="mast2.js"></script> |
| 81 | 87 | ... | ... |
| ... | @@ -6,7 +6,7 @@ | ... | @@ -6,7 +6,7 @@ |
| 6 | "dst": "of:0000ffffffffff05", | 6 | "dst": "of:0000ffffffffff05", |
| 7 | "dstPort": "10", | 7 | "dstPort": "10", |
| 8 | "type": "optical", | 8 | "type": "optical", |
| 9 | - "linkWidth": 2, | 9 | + "linkWidth": 6, |
| 10 | "props" : { | 10 | "props" : { |
| 11 | "BW": "80 G" | 11 | "BW": "80 G" |
| 12 | } | 12 | } | ... | ... |
| ... | @@ -6,7 +6,7 @@ | ... | @@ -6,7 +6,7 @@ |
| 6 | "dst": "of:0000ffffffffff05", | 6 | "dst": "of:0000ffffffffff05", |
| 7 | "dstPort": "30", | 7 | "dstPort": "30", |
| 8 | "type": "optical", | 8 | "type": "optical", |
| 9 | - "linkWidth": 2, | 9 | + "linkWidth": 6, |
| 10 | "props" : { | 10 | "props" : { |
| 11 | "BW": "70 G" | 11 | "BW": "70 G" |
| 12 | } | 12 | } | ... | ... |
| ... | @@ -6,7 +6,7 @@ | ... | @@ -6,7 +6,7 @@ |
| 6 | "dst": "of:0000ffffffffff08", | 6 | "dst": "of:0000ffffffffff08", |
| 7 | "dstPort": "20", | 7 | "dstPort": "20", |
| 8 | "type": "optical", | 8 | "type": "optical", |
| 9 | - "linkWidth": 2, | 9 | + "linkWidth": 6, |
| 10 | "props" : { | 10 | "props" : { |
| 11 | "BW": "70 G" | 11 | "BW": "70 G" |
| 12 | } | 12 | } | ... | ... |
| ... | @@ -6,7 +6,7 @@ | ... | @@ -6,7 +6,7 @@ |
| 6 | "dst": "of:0000ffffffffff08", | 6 | "dst": "of:0000ffffffffff08", |
| 7 | "dstPort": "30", | 7 | "dstPort": "30", |
| 8 | "type": "optical", | 8 | "type": "optical", |
| 9 | - "linkWidth": 2, | 9 | + "linkWidth": 6, |
| 10 | "props" : { | 10 | "props" : { |
| 11 | "BW": "70 G" | 11 | "BW": "70 G" |
| 12 | } | 12 | } | ... | ... |
| ... | @@ -6,7 +6,7 @@ | ... | @@ -6,7 +6,7 @@ |
| 6 | "dst": "of:0000ffffffffff08", | 6 | "dst": "of:0000ffffffffff08", |
| 7 | "dstPort": "10", | 7 | "dstPort": "10", |
| 8 | "type": "optical", | 8 | "type": "optical", |
| 9 | - "linkWidth": 2, | 9 | + "linkWidth": 6, |
| 10 | "props" : { | 10 | "props" : { |
| 11 | "BW": "70 G" | 11 | "BW": "70 G" |
| 12 | } | 12 | } | ... | ... |
| ... | @@ -32,6 +32,34 @@ div.onosView.currentView { | ... | @@ -32,6 +32,34 @@ div.onosView.currentView { |
| 32 | display: block; | 32 | display: block; |
| 33 | } | 33 | } |
| 34 | 34 | ||
| 35 | +div#alerts { | ||
| 36 | + display: none; | ||
| 37 | + position: absolute; | ||
| 38 | + z-index: 2000; | ||
| 39 | + opacity: 0.65; | ||
| 40 | + background-color: #006; | ||
| 41 | + color: white; | ||
| 42 | + top: 80px; | ||
| 43 | + left: 40px; | ||
| 44 | + padding: 3px 6px; | ||
| 45 | + box-shadow: 4px 6px 12px #777; | ||
| 46 | +} | ||
| 47 | + | ||
| 48 | +div#alerts pre { | ||
| 49 | + margin: 0.2em 6px; | ||
| 50 | +} | ||
| 51 | + | ||
| 52 | +div#alerts span.close { | ||
| 53 | + color: #6af; | ||
| 54 | + float: right; | ||
| 55 | + right: 2px; | ||
| 56 | + cursor: pointer; | ||
| 57 | +} | ||
| 58 | + | ||
| 59 | +div#alerts span.close:hover { | ||
| 60 | + color: #fff; | ||
| 61 | +} | ||
| 62 | + | ||
| 35 | /* | 63 | /* |
| 36 | * ============================================================== | 64 | * ============================================================== |
| 37 | * END OF NEW ONOS.JS file | 65 | * END OF NEW ONOS.JS file |
| ... | @@ -54,12 +82,6 @@ svg #bg { | ... | @@ -54,12 +82,6 @@ svg #bg { |
| 54 | * Network Graph elements ====================================== | 82 | * Network Graph elements ====================================== |
| 55 | */ | 83 | */ |
| 56 | 84 | ||
| 57 | -svg .link { | ||
| 58 | - opacity: .7; | ||
| 59 | -} | ||
| 60 | - | ||
| 61 | -svg .link.host { | ||
| 62 | -} | ||
| 63 | 85 | ||
| 64 | svg g.portLayer rect.port { | 86 | svg g.portLayer rect.port { |
| 65 | fill: #ccc; | 87 | fill: #ccc; |
| ... | @@ -70,33 +92,13 @@ svg g.portLayer text { | ... | @@ -70,33 +92,13 @@ svg g.portLayer text { |
| 70 | pointer-events: none; | 92 | pointer-events: none; |
| 71 | } | 93 | } |
| 72 | 94 | ||
| 73 | -svg .node.device rect { | ||
| 74 | - stroke-width: 1.5px; | ||
| 75 | -} | ||
| 76 | - | ||
| 77 | -svg .node.device.fixed rect { | ||
| 78 | - stroke-width: 1.5; | ||
| 79 | - stroke: #ccc; | ||
| 80 | -} | ||
| 81 | 95 | ||
| 82 | -svg .node.device.roadm rect { | ||
| 83 | - fill: #03c; | ||
| 84 | -} | ||
| 85 | - | ||
| 86 | -svg .node.device.switch rect { | ||
| 87 | - fill: #06f; | ||
| 88 | -} | ||
| 89 | 96 | ||
| 90 | svg .node.host circle { | 97 | svg .node.host circle { |
| 91 | fill: #c96; | 98 | fill: #c96; |
| 92 | stroke: #000; | 99 | stroke: #000; |
| 93 | } | 100 | } |
| 94 | 101 | ||
| 95 | -svg .node text { | ||
| 96 | - fill: white; | ||
| 97 | - font: 10pt sans-serif; | ||
| 98 | - pointer-events: none; | ||
| 99 | -} | ||
| 100 | 102 | ||
| 101 | /* for debugging */ | 103 | /* for debugging */ |
| 102 | svg .node circle.debug { | 104 | svg .node circle.debug { |
| ... | @@ -110,10 +112,6 @@ svg .node rect.debug { | ... | @@ -110,10 +112,6 @@ svg .node rect.debug { |
| 110 | } | 112 | } |
| 111 | 113 | ||
| 112 | 114 | ||
| 113 | -svg .node.selected rect, | ||
| 114 | -svg .node.selected circle { | ||
| 115 | - filter: url(#blue-glow); | ||
| 116 | -} | ||
| 117 | 115 | ||
| 118 | svg .link.inactive, | 116 | svg .link.inactive, |
| 119 | svg .port.inactive, | 117 | svg .port.inactive, | ... | ... |
| ... | @@ -32,7 +32,8 @@ | ... | @@ -32,7 +32,8 @@ |
| 32 | $.onos = function (options) { | 32 | $.onos = function (options) { |
| 33 | var uiApi, | 33 | var uiApi, |
| 34 | viewApi, | 34 | viewApi, |
| 35 | - navApi; | 35 | + navApi, |
| 36 | + libApi; | ||
| 36 | 37 | ||
| 37 | var defaultOptions = { | 38 | var defaultOptions = { |
| 38 | trace: false, | 39 | trace: false, |
| ... | @@ -331,6 +332,58 @@ | ... | @@ -331,6 +332,58 @@ |
| 331 | } | 332 | } |
| 332 | } | 333 | } |
| 333 | 334 | ||
| 335 | + var alerts = { | ||
| 336 | + open: false, | ||
| 337 | + count: 0 | ||
| 338 | + }; | ||
| 339 | + | ||
| 340 | + function createAlerts() { | ||
| 341 | + var al = d3.select('#alerts') | ||
| 342 | + .style('display', 'block'); | ||
| 343 | + al.append('span') | ||
| 344 | + .attr('class', 'close') | ||
| 345 | + .text('X') | ||
| 346 | + .on('click', closeAlerts); | ||
| 347 | + al.append('pre'); | ||
| 348 | + alerts.open = true; | ||
| 349 | + alerts.count = 0; | ||
| 350 | + } | ||
| 351 | + | ||
| 352 | + function closeAlerts() { | ||
| 353 | + d3.select('#alerts') | ||
| 354 | + .style('display', 'none'); | ||
| 355 | + d3.select('#alerts span').remove(); | ||
| 356 | + d3.select('#alerts pre').remove(); | ||
| 357 | + alerts.open = false; | ||
| 358 | + } | ||
| 359 | + | ||
| 360 | + function addAlert(msg) { | ||
| 361 | + var lines, | ||
| 362 | + oldContent; | ||
| 363 | + | ||
| 364 | + if (alerts.count) { | ||
| 365 | + oldContent = d3.select('#alerts pre').html(); | ||
| 366 | + } | ||
| 367 | + | ||
| 368 | + lines = msg.split('\n'); | ||
| 369 | + lines[0] += ' '; // spacing so we don't crowd 'X' | ||
| 370 | + lines = lines.join('\n'); | ||
| 371 | + | ||
| 372 | + if (oldContent) { | ||
| 373 | + lines += '\n----\n' + oldContent; | ||
| 374 | + } | ||
| 375 | + | ||
| 376 | + d3.select('#alerts pre').html(lines); | ||
| 377 | + alerts.count++; | ||
| 378 | + } | ||
| 379 | + | ||
| 380 | + function doAlert(msg) { | ||
| 381 | + if (!alerts.open) { | ||
| 382 | + createAlerts(); | ||
| 383 | + } | ||
| 384 | + addAlert(msg); | ||
| 385 | + } | ||
| 386 | + | ||
| 334 | function keyIn() { | 387 | function keyIn() { |
| 335 | var event = d3.event, | 388 | var event = d3.event, |
| 336 | keyCode = event.keyCode, | 389 | keyCode = event.keyCode, |
| ... | @@ -408,7 +461,8 @@ | ... | @@ -408,7 +461,8 @@ |
| 408 | uid: this.uid, | 461 | uid: this.uid, |
| 409 | setRadio: this.setRadio, | 462 | setRadio: this.setRadio, |
| 410 | setKeys: this.setKeys, | 463 | setKeys: this.setKeys, |
| 411 | - dataLoadError: this.dataLoadError | 464 | + dataLoadError: this.dataLoadError, |
| 465 | + alert: this.alert | ||
| 412 | } | 466 | } |
| 413 | }, | 467 | }, |
| 414 | 468 | ||
| ... | @@ -501,14 +555,20 @@ | ... | @@ -501,14 +555,20 @@ |
| 501 | return makeUid(this, id); | 555 | return makeUid(this, id); |
| 502 | }, | 556 | }, |
| 503 | 557 | ||
| 504 | - // TODO : implement custom dialogs (don't use alerts) | 558 | + // TODO : implement custom dialogs |
| 559 | + | ||
| 560 | + // Consider enhancing alert mechanism to handle multiples | ||
| 561 | + // as individually closable. | ||
| 562 | + alert: function (msg) { | ||
| 563 | + doAlert(msg); | ||
| 564 | + }, | ||
| 505 | 565 | ||
| 506 | dataLoadError: function (err, url) { | 566 | dataLoadError: function (err, url) { |
| 507 | var msg = 'Data Load Error\n\n' + | 567 | var msg = 'Data Load Error\n\n' + |
| 508 | err.status + ' -- ' + err.statusText + '\n\n' + | 568 | err.status + ' -- ' + err.statusText + '\n\n' + |
| 509 | 'relative-url: "' + url + '"\n\n' + | 569 | 'relative-url: "' + url + '"\n\n' + |
| 510 | 'complete-url: "' + err.responseURL + '"'; | 570 | 'complete-url: "' + err.responseURL + '"'; |
| 511 | - alert(msg); | 571 | + this.alert(msg); |
| 512 | } | 572 | } |
| 513 | 573 | ||
| 514 | // TODO: consider schedule, clearTimer, etc. | 574 | // TODO: consider schedule, clearTimer, etc. |
| ... | @@ -521,6 +581,12 @@ | ... | @@ -521,6 +581,12 @@ |
| 521 | // UI API | 581 | // UI API |
| 522 | 582 | ||
| 523 | uiApi = { | 583 | uiApi = { |
| 584 | + addLib: function (libName, api) { | ||
| 585 | + // TODO: validation of args | ||
| 586 | + libApi[libName] = api; | ||
| 587 | + }, | ||
| 588 | + | ||
| 589 | + // TODO: it remains to be seen whether we keep this style of docs | ||
| 524 | /** @api ui addView( vid, nid, cb ) | 590 | /** @api ui addView( vid, nid, cb ) |
| 525 | * Adds a view to the UI. | 591 | * Adds a view to the UI. |
| 526 | * <p> | 592 | * <p> |
| ... | @@ -590,6 +656,12 @@ | ... | @@ -590,6 +656,12 @@ |
| 590 | }; | 656 | }; |
| 591 | 657 | ||
| 592 | // .......................................................... | 658 | // .......................................................... |
| 659 | + // Library API | ||
| 660 | + libApi = { | ||
| 661 | + | ||
| 662 | + }; | ||
| 663 | + | ||
| 664 | + // .......................................................... | ||
| 593 | // Exported API | 665 | // Exported API |
| 594 | 666 | ||
| 595 | // function to be called from index.html to build the ONOS UI | 667 | // function to be called from index.html to build the ONOS UI |
| ... | @@ -623,7 +695,8 @@ | ... | @@ -623,7 +695,8 @@ |
| 623 | // export the api and build-UI function | 695 | // export the api and build-UI function |
| 624 | return { | 696 | return { |
| 625 | ui: uiApi, | 697 | ui: uiApi, |
| 626 | - view: viewApi, | 698 | + lib: libApi, |
| 699 | + //view: viewApi, | ||
| 627 | nav: navApi, | 700 | nav: navApi, |
| 628 | buildUi: buildOnosUi | 701 | buildUi: buildOnosUi |
| 629 | }; | 702 | }; | ... | ... |
| ... | @@ -24,12 +24,18 @@ svg #topo-bg { | ... | @@ -24,12 +24,18 @@ svg #topo-bg { |
| 24 | opacity: 0.5; | 24 | opacity: 0.5; |
| 25 | } | 25 | } |
| 26 | 26 | ||
| 27 | +/* NODES */ | ||
| 28 | + | ||
| 27 | svg .node.device { | 29 | svg .node.device { |
| 28 | stroke: none; | 30 | stroke: none; |
| 29 | stroke-width: 1.5px; | 31 | stroke-width: 1.5px; |
| 30 | cursor: pointer; | 32 | cursor: pointer; |
| 31 | } | 33 | } |
| 32 | 34 | ||
| 35 | +svg .node.device rect { | ||
| 36 | + stroke-width: 1.5px; | ||
| 37 | +} | ||
| 38 | + | ||
| 33 | svg .node.device.fixed rect { | 39 | svg .node.device.fixed rect { |
| 34 | stroke-width: 1.5; | 40 | stroke-width: 1.5; |
| 35 | stroke: #ccc; | 41 | stroke: #ccc; |
| ... | @@ -50,6 +56,17 @@ svg .node text { | ... | @@ -50,6 +56,17 @@ svg .node text { |
| 50 | pointer-events: none; | 56 | pointer-events: none; |
| 51 | } | 57 | } |
| 52 | 58 | ||
| 59 | +svg .node.selected rect, | ||
| 60 | +svg .node.selected circle { | ||
| 61 | + filter: url(#blue-glow); | ||
| 62 | +} | ||
| 63 | + | ||
| 64 | +/* LINKS */ | ||
| 65 | + | ||
| 66 | +svg .link { | ||
| 67 | + opacity: .7; | ||
| 68 | +} | ||
| 69 | + | ||
| 53 | /* for debugging */ | 70 | /* for debugging */ |
| 54 | svg .node circle.debug { | 71 | svg .node circle.debug { |
| 55 | fill: white; | 72 | fill: white; | ... | ... |
| ... | @@ -23,6 +23,9 @@ | ... | @@ -23,6 +23,9 @@ |
| 23 | (function (onos) { | 23 | (function (onos) { |
| 24 | 'use strict'; | 24 | 'use strict'; |
| 25 | 25 | ||
| 26 | + // shorter names for library APIs | ||
| 27 | + var d3u = onos.lib.d3util; | ||
| 28 | + | ||
| 26 | // configuration data | 29 | // configuration data |
| 27 | var config = { | 30 | var config = { |
| 28 | useLiveData: false, | 31 | useLiveData: false, |
| ... | @@ -61,6 +64,10 @@ | ... | @@ -61,6 +64,10 @@ |
| 61 | height: 14 | 64 | height: 14 |
| 62 | } | 65 | } |
| 63 | }, | 66 | }, |
| 67 | + topo: { | ||
| 68 | + linkInColor: '#66f', | ||
| 69 | + linkInWidth: 14 | ||
| 70 | + }, | ||
| 64 | icons: { | 71 | icons: { |
| 65 | w: 28, | 72 | w: 28, |
| 66 | h: 28, | 73 | h: 28, |
| ... | @@ -106,7 +113,9 @@ | ... | @@ -106,7 +113,9 @@ |
| 106 | // key bindings | 113 | // key bindings |
| 107 | var keyDispatch = { | 114 | var keyDispatch = { |
| 108 | space: injectTestEvent, // TODO: remove (testing only) | 115 | space: injectTestEvent, // TODO: remove (testing only) |
| 109 | - // M: testMe, // TODO: remove (testing only) | 116 | + S: injectStartupEvents, // TODO: remove (testing only) |
| 117 | + A: testAlert, // TODO: remove (testing only) | ||
| 118 | + M: testMe, // TODO: remove (testing only) | ||
| 110 | 119 | ||
| 111 | B: toggleBg, | 120 | B: toggleBg, |
| 112 | G: toggleLayout, | 121 | G: toggleLayout, |
| ... | @@ -141,7 +150,8 @@ | ... | @@ -141,7 +150,8 @@ |
| 141 | // For Debugging / Development | 150 | // For Debugging / Development |
| 142 | 151 | ||
| 143 | var eventPrefix = 'json/eventTest_', | 152 | var eventPrefix = 'json/eventTest_', |
| 144 | - eventNumber = 0; | 153 | + eventNumber = 0, |
| 154 | + alertNumber = 0; | ||
| 145 | 155 | ||
| 146 | function note(label, msg) { | 156 | function note(label, msg) { |
| 147 | console.log('NOTE: ' + label + ': ' + msg); | 157 | console.log('NOTE: ' + label + ': ' + msg); |
| ... | @@ -155,22 +165,12 @@ | ... | @@ -155,22 +165,12 @@ |
| 155 | // ============================== | 165 | // ============================== |
| 156 | // Key Callbacks | 166 | // Key Callbacks |
| 157 | 167 | ||
| 168 | + function testAlert(view) { | ||
| 169 | + alertNumber++; | ||
| 170 | + view.alert("Test me! -- " + alertNumber); | ||
| 171 | + } | ||
| 172 | + | ||
| 158 | function testMe(view) { | 173 | function testMe(view) { |
| 159 | - svg.append('line') | ||
| 160 | - .attr({ | ||
| 161 | - x1: 100, | ||
| 162 | - y1: 100, | ||
| 163 | - x2: 500, | ||
| 164 | - y2: 400, | ||
| 165 | - stroke: '#2f3', | ||
| 166 | - 'stroke-width': 8 | ||
| 167 | - }) | ||
| 168 | - .transition() | ||
| 169 | - .duration(1200) | ||
| 170 | - .attr({ | ||
| 171 | - stroke: '#666', | ||
| 172 | - 'stroke-width': 6 | ||
| 173 | - }); | ||
| 174 | } | 174 | } |
| 175 | 175 | ||
| 176 | function injectTestEvent(view) { | 176 | function injectTestEvent(view) { |
| ... | @@ -187,6 +187,13 @@ | ... | @@ -187,6 +187,13 @@ |
| 187 | }); | 187 | }); |
| 188 | } | 188 | } |
| 189 | 189 | ||
| 190 | + function injectStartupEvents(view) { | ||
| 191 | + var lastStartupEvent = 32; | ||
| 192 | + while (eventNumber < lastStartupEvent) { | ||
| 193 | + injectTestEvent(view); | ||
| 194 | + } | ||
| 195 | + } | ||
| 196 | + | ||
| 190 | function toggleBg() { | 197 | function toggleBg() { |
| 191 | var vis = bgImg.style('visibility'); | 198 | var vis = bgImg.style('visibility'); |
| 192 | bgImg.style('visibility', (vis === 'hidden') ? 'visible' : 'hidden'); | 199 | bgImg.style('visibility', (vis === 'hidden') ? 'visible' : 'hidden'); |
| ... | @@ -370,6 +377,11 @@ | ... | @@ -370,6 +377,11 @@ |
| 370 | return lnk; | 377 | return lnk; |
| 371 | } | 378 | } |
| 372 | 379 | ||
| 380 | + function linkWidth(w) { | ||
| 381 | + // w is number of links between nodes. Scale appropriately. | ||
| 382 | + return w * 1.2; | ||
| 383 | + } | ||
| 384 | + | ||
| 373 | function updateLinks() { | 385 | function updateLinks() { |
| 374 | link = linkG.selectAll('.link') | 386 | link = linkG.selectAll('.link') |
| 375 | .data(network.links, function (d) { return d.id; }); | 387 | .data(network.links, function (d) { return d.id; }); |
| ... | @@ -387,12 +399,12 @@ | ... | @@ -387,12 +399,12 @@ |
| 387 | y1: function (d) { return d.y1; }, | 399 | y1: function (d) { return d.y1; }, |
| 388 | x2: function (d) { return d.x2; }, | 400 | x2: function (d) { return d.x2; }, |
| 389 | y2: function (d) { return d.y2; }, | 401 | y2: function (d) { return d.y2; }, |
| 390 | - stroke: '#66f', | 402 | + stroke: config.topo.linkInColor, |
| 391 | - 'stroke-width': 10 | 403 | + 'stroke-width': config.topo.linkInWidth |
| 392 | }) | 404 | }) |
| 393 | .transition().duration(1000) | 405 | .transition().duration(1000) |
| 394 | .attr({ | 406 | .attr({ |
| 395 | - 'stroke-width': function (d) { return d.width; }, | 407 | + 'stroke-width': function (d) { return linkWidth(d.width); }, |
| 396 | stroke: '#666' // TODO: remove explicit stroke, rather... | 408 | stroke: '#666' // TODO: remove explicit stroke, rather... |
| 397 | }); | 409 | }); |
| 398 | 410 | ||
| ... | @@ -461,6 +473,10 @@ | ... | @@ -461,6 +473,10 @@ |
| 461 | return box; | 473 | return box; |
| 462 | } | 474 | } |
| 463 | 475 | ||
| 476 | + function mkSvgClass(d) { | ||
| 477 | + return d.fixed ? d.svgClass + ' fixed' : d.svgClass; | ||
| 478 | + } | ||
| 479 | + | ||
| 464 | function updateNodes() { | 480 | function updateNodes() { |
| 465 | node = nodeG.selectAll('.node') | 481 | node = nodeG.selectAll('.node') |
| 466 | .data(network.nodes, function (d) { return d.id; }); | 482 | .data(network.nodes, function (d) { return d.id; }); |
| ... | @@ -473,11 +489,11 @@ | ... | @@ -473,11 +489,11 @@ |
| 473 | .append('g') | 489 | .append('g') |
| 474 | .attr({ | 490 | .attr({ |
| 475 | id: function (d) { return safeId(d.id); }, | 491 | id: function (d) { return safeId(d.id); }, |
| 476 | - class: function (d) { return d.svgClass; }, | 492 | + class: mkSvgClass, |
| 477 | transform: function (d) { return translate(d.x, d.y); }, | 493 | transform: function (d) { return translate(d.x, d.y); }, |
| 478 | opacity: 0 | 494 | opacity: 0 |
| 479 | }) | 495 | }) |
| 480 | - //.call(network.drag) | 496 | + .call(network.drag) |
| 481 | //.on('mouseover', function (d) {}) | 497 | //.on('mouseover', function (d) {}) |
| 482 | //.on('mouseover', function (d) {}) | 498 | //.on('mouseover', function (d) {}) |
| 483 | .transition() | 499 | .transition() |
| ... | @@ -578,6 +594,9 @@ | ... | @@ -578,6 +594,9 @@ |
| 578 | svg = view.$div.append('svg'); | 594 | svg = view.$div.append('svg'); |
| 579 | setSize(svg, view); | 595 | setSize(svg, view); |
| 580 | 596 | ||
| 597 | + // add blue glow filter to svg layer | ||
| 598 | + d3u.appendGlow(svg); | ||
| 599 | + | ||
| 581 | // load the background image | 600 | // load the background image |
| 582 | bgImg = svg.append('svg:image') | 601 | bgImg = svg.append('svg:image') |
| 583 | .attr({ | 602 | .attr({ |
| ... | @@ -612,6 +631,20 @@ | ... | @@ -612,6 +631,20 @@ |
| 612 | return fcfg.charge[d.class] || -200; | 631 | return fcfg.charge[d.class] || -200; |
| 613 | } | 632 | } |
| 614 | 633 | ||
| 634 | + function selectCb(d, self) { | ||
| 635 | + // TODO: selectObject(d, self); | ||
| 636 | + } | ||
| 637 | + | ||
| 638 | + function atDragEnd(d, self) { | ||
| 639 | + // once we've finished moving, pin the node in position, | ||
| 640 | + // if it is a device (not a host) | ||
| 641 | + if (d.class === 'device') { | ||
| 642 | + d.fixed = true; | ||
| 643 | + d3.select(self).classed('fixed', true) | ||
| 644 | + // TODO: send new [x,y] back to server, via websocket. | ||
| 645 | + } | ||
| 646 | + } | ||
| 647 | + | ||
| 615 | // set up the force layout | 648 | // set up the force layout |
| 616 | network.force = d3.layout.force() | 649 | network.force = d3.layout.force() |
| 617 | .size(forceDim) | 650 | .size(forceDim) |
| ... | @@ -621,8 +654,9 @@ | ... | @@ -621,8 +654,9 @@ |
| 621 | .linkDistance(ldist) | 654 | .linkDistance(ldist) |
| 622 | .linkStrength(lstrg) | 655 | .linkStrength(lstrg) |
| 623 | .on('tick', tick); | 656 | .on('tick', tick); |
| 624 | - } | ||
| 625 | 657 | ||
| 658 | + network.drag = d3u.createDragBehavior(network.force, selectCb, atDragEnd); | ||
| 659 | + } | ||
| 626 | 660 | ||
| 627 | function load(view, ctx) { | 661 | function load(view, ctx) { |
| 628 | // cache the view token, so network topo functions can access it | 662 | // cache the view token, so network topo functions can access it | ... | ... |
-
Please register or login to post a comment