Fixed node geometry so that [x,y] is now at the center of the rectangle. (Added debug circles).
Added radio buttons in mast head.
Showing
3 changed files
with
143 additions
and
64 deletions
... | @@ -25,9 +25,11 @@ | ... | @@ -25,9 +25,11 @@ |
25 | --> | 25 | --> |
26 | <html> | 26 | <html> |
27 | <head> | 27 | <head> |
28 | + <meta charset="utf-8"> | ||
28 | <title>ONOS GUI</title> | 29 | <title>ONOS GUI</title> |
29 | 30 | ||
30 | - <script src="libs/d3.min.js"></script> | 31 | + <!--TODO: use the minified version of d3, once debugging is complete --> |
32 | + <script src="libs/d3.js"></script> | ||
31 | <script src="libs/jquery-2.1.1.min.js"></script> | 33 | <script src="libs/jquery-2.1.1.min.js"></script> |
32 | 34 | ||
33 | <link rel="stylesheet" href="base.css"> | 35 | <link rel="stylesheet" href="base.css"> |
... | @@ -42,10 +44,10 @@ | ... | @@ -42,10 +44,10 @@ |
42 | <div id="mast"> | 44 | <div id="mast"> |
43 | <img id="logo" src="img/onos-logo.png" width="60" height="38"> | 45 | <img id="logo" src="img/onos-logo.png" width="60" height="38"> |
44 | <span class="title">Open Network Operating System</span> | 46 | <span class="title">Open Network Operating System</span> |
45 | - <span class="right"> | 47 | + <span id="displayModes" class="right"> |
46 | - <span class="radio">[All Layers]</span> | 48 | + <span id="showAll" class="radio active">All Layers</span> |
47 | - <span class="radio">[Packet Only]</span> | 49 | + <span id="showPkt" class="radio">Packet Only</span> |
48 | - <span class="radio">[Optical Only]</span> | 50 | + <span id="showOpt" class="radio">Optical Only</span> |
49 | </span> | 51 | </span> |
50 | </div> | 52 | </div> |
51 | <div id="view"></div> | 53 | <div id="view"></div> | ... | ... |
... | @@ -31,7 +31,8 @@ | ... | @@ -31,7 +31,8 @@ |
31 | var config = { | 31 | var config = { |
32 | options: { | 32 | options: { |
33 | layering: true, | 33 | layering: true, |
34 | - collisionPrevention: true | 34 | + collisionPrevention: true, |
35 | + showNodeXY: true | ||
35 | }, | 36 | }, |
36 | XjsonUrl: 'rs/topology/graph', | 37 | XjsonUrl: 'rs/topology/graph', |
37 | jsonUrl: 'network.json', | 38 | jsonUrl: 'network.json', |
... | @@ -91,7 +92,8 @@ | ... | @@ -91,7 +92,8 @@ |
91 | view = {}, | 92 | view = {}, |
92 | network = {}, | 93 | network = {}, |
93 | selected = {}, | 94 | selected = {}, |
94 | - highlighted = null; | 95 | + highlighted = null, |
96 | + viewMode = 'showAll'; | ||
95 | 97 | ||
96 | 98 | ||
97 | function loadNetworkView() { | 99 | function loadNetworkView() { |
... | @@ -126,6 +128,21 @@ | ... | @@ -126,6 +128,21 @@ |
126 | }); | 128 | }); |
127 | 129 | ||
128 | $(window).on('resize', resize); | 130 | $(window).on('resize', resize); |
131 | + | ||
132 | + // set up radio button behavior | ||
133 | + d3.selectAll("#displayModes .radio").on('click', function() { | ||
134 | + var id = d3.select(this).attr("id"); | ||
135 | + if (id !== viewMode) { | ||
136 | + radioButton('displayModes', id); | ||
137 | + viewMode = id; | ||
138 | + alert("action: " + id); | ||
139 | + } | ||
140 | + }); | ||
141 | + } | ||
142 | + | ||
143 | + function radioButton(group, id) { | ||
144 | + d3.selectAll("#" + group + " .radio").classed("active", false); | ||
145 | + d3.select("#" + group + " #" + id).classed("active", true); | ||
129 | } | 146 | } |
130 | 147 | ||
131 | 148 | ||
... | @@ -263,8 +280,6 @@ | ... | @@ -263,8 +280,6 @@ |
263 | // + " scale(" + d3.event.scale + ")"); | 280 | // + " scale(" + d3.event.scale + ")"); |
264 | // } | 281 | // } |
265 | 282 | ||
266 | - // TODO: svg.append('defs') for markers? | ||
267 | - | ||
268 | // TODO: move glow/blur stuff to util script | 283 | // TODO: move glow/blur stuff to util script |
269 | var glow = network.svg.append('filter') | 284 | var glow = network.svg.append('filter') |
270 | .attr('x', '-50%') | 285 | .attr('x', '-50%') |
... | @@ -295,8 +310,7 @@ | ... | @@ -295,8 +310,7 @@ |
295 | // }); | 310 | // }); |
296 | 311 | ||
297 | 312 | ||
298 | - | 313 | + // add links to the display |
299 | - | ||
300 | network.link = network.svg.append('g').selectAll('.link') | 314 | network.link = network.svg.append('g').selectAll('.link') |
301 | .data(network.force.links(), function(d) {return d.id}) | 315 | .data(network.force.links(), function(d) {return d.id}) |
302 | .enter().append('line') | 316 | .enter().append('line') |
... | @@ -350,6 +364,7 @@ | ... | @@ -350,6 +364,7 @@ |
350 | }); | 364 | }); |
351 | 365 | ||
352 | 366 | ||
367 | + // add nodes to the display | ||
353 | network.node = network.svg.selectAll('.node') | 368 | network.node = network.svg.selectAll('.node') |
354 | .data(network.force.nodes(), function(d) {return d.id}) | 369 | .data(network.force.nodes(), function(d) {return d.id}) |
355 | .enter().append('g') | 370 | .enter().append('g') |
... | @@ -386,15 +401,19 @@ | ... | @@ -386,15 +401,19 @@ |
386 | }); | 401 | }); |
387 | 402 | ||
388 | network.nodeRect = network.node.append('rect') | 403 | network.nodeRect = network.node.append('rect') |
389 | - .attr('rx', 5) | 404 | + .attr({ |
390 | - .attr('ry', 5); | 405 | + rx: 5, |
406 | + ry: 5, | ||
407 | + width: 100, | ||
408 | + height: 12 | ||
409 | + }); | ||
391 | // note that width/height are adjusted to fit the label text | 410 | // note that width/height are adjusted to fit the label text |
392 | 411 | ||
393 | network.node.each(function(d) { | 412 | network.node.each(function(d) { |
394 | var node = d3.select(this), | 413 | var node = d3.select(this), |
395 | - rect = node.select('rect'), | 414 | + icon = iconUrl(d); |
396 | - icon = iconUrl(d), | 415 | + |
397 | - text = node.append('text') | 416 | + node.append('text') |
398 | // TODO: add label cycle behavior | 417 | // TODO: add label cycle behavior |
399 | .text(d.id) | 418 | .text(d.id) |
400 | .attr('dy', '1.1em'); | 419 | .attr('dy', '1.1em'); |
... | @@ -402,63 +421,85 @@ | ... | @@ -402,63 +421,85 @@ |
402 | if (icon) { | 421 | if (icon) { |
403 | var cfg = config.icons; | 422 | var cfg = config.icons; |
404 | node.append('svg:image') | 423 | node.append('svg:image') |
405 | - .attr('width', cfg.w) | 424 | + .attr({ |
406 | - .attr('height', cfg.h) | 425 | + width: cfg.w, |
407 | - .attr('xlink:href', icon); | 426 | + height: cfg.h, |
427 | + 'xlink:href': icon | ||
428 | + }); | ||
408 | // note, icon relative positioning (x,y) is done after we have | 429 | // note, icon relative positioning (x,y) is done after we have |
409 | // adjusted the bounds of the rectangle... | 430 | // adjusted the bounds of the rectangle... |
410 | } | 431 | } |
411 | 432 | ||
433 | + // for debugging... | ||
434 | + if (config.options.showNodeXY) { | ||
435 | + node.append('circle') | ||
436 | + .attr({ | ||
437 | + class: 'debug', | ||
438 | + cx: 0, | ||
439 | + cy: 0, | ||
440 | + r: '3px' | ||
441 | + }); | ||
442 | + } | ||
412 | }); | 443 | }); |
413 | 444 | ||
445 | + | ||
446 | + // returns the newly computed bounding box | ||
447 | + function adjustRectToFitText(n) { | ||
448 | + var text = n.select('text'), | ||
449 | + box = text.node().getBBox(), | ||
450 | + lab = config.labels; | ||
451 | + | ||
452 | + text.attr('text-anchor', 'middle') | ||
453 | + .attr('y', '-0.8em') | ||
454 | + .attr('x', lab.imgPad/2) | ||
455 | + ; | ||
456 | + | ||
457 | + // TODO: figure out how to access the data on selection | ||
458 | + console.log("\nadjust rect for " + n.data().id); | ||
459 | + console.log(box); | ||
460 | + | ||
461 | + // translate the bbox so that it is centered on [x,y] | ||
462 | + box.x = -box.width / 2; | ||
463 | + box.y = -box.height / 2; | ||
464 | + | ||
465 | + // add padding | ||
466 | + box.x -= (lab.padLR + lab.imgPad/2); | ||
467 | + box.width += lab.padLR * 2 + lab.imgPad; | ||
468 | + box.y -= lab.padTB; | ||
469 | + box.height += lab.padTB * 2; | ||
470 | + | ||
471 | + return box; | ||
472 | + } | ||
473 | + | ||
474 | + function boundsFromBox(box) { | ||
475 | + return { | ||
476 | + x1: box.x, | ||
477 | + y1: box.y, | ||
478 | + x2: box.x + box.width, | ||
479 | + y2: box.y + box.height | ||
480 | + }; | ||
481 | + } | ||
482 | + | ||
414 | // this function is scheduled to happen soon after the given thread ends | 483 | // this function is scheduled to happen soon after the given thread ends |
415 | setTimeout(function() { | 484 | setTimeout(function() { |
416 | network.node.each(function(d) { | 485 | network.node.each(function(d) { |
417 | // for every node, recompute size, padding, etc. so text fits | 486 | // for every node, recompute size, padding, etc. so text fits |
418 | var node = d3.select(this), | 487 | var node = d3.select(this), |
419 | - text = node.selectAll('text'), | 488 | + text = node.select('text'), |
420 | - bounds = {}, | 489 | + box = adjustRectToFitText(node), |
421 | - first = true; | 490 | + lab = config.labels; |
422 | - | ||
423 | - // NOTE: probably unnecessary code if we only have one line. | ||
424 | - text.each(function() { | ||
425 | - var box = this.getBBox(); | ||
426 | - if (first || box.x < bounds.x1) { | ||
427 | - bounds.x1 = box.x; | ||
428 | - } | ||
429 | - if (first || box.y < bounds.y1) { | ||
430 | - bounds.y1 = box.y; | ||
431 | - } | ||
432 | - if (first || box.x + box.width < bounds.x2) { | ||
433 | - bounds.x2 = box.x + box.width; | ||
434 | - } | ||
435 | - if (first || box.y + box.height < bounds.y2) { | ||
436 | - bounds.y2 = box.y + box.height; | ||
437 | - } | ||
438 | - first = false; | ||
439 | - }).attr('text-anchor', 'middle'); | ||
440 | - | ||
441 | - var lab = config.labels, | ||
442 | - oldWidth = bounds.x2 - bounds.x1; | ||
443 | - | ||
444 | - bounds.x1 -= oldWidth / 2; | ||
445 | - bounds.x2 -= oldWidth / 2; | ||
446 | - | ||
447 | - bounds.x1 -= (lab.padLR + lab.imgPad); | ||
448 | - bounds.y1 -= lab.padTB; | ||
449 | - bounds.x2 += lab.padLR; | ||
450 | - bounds.y2 += lab.padTB; | ||
451 | 491 | ||
492 | + // now make the computed adjustment | ||
452 | node.select('rect') | 493 | node.select('rect') |
453 | - .attr('x', bounds.x1) | 494 | + .attr(box); |
454 | - .attr('y', bounds.y1) | ||
455 | - .attr('width', bounds.x2 - bounds.x1) | ||
456 | - .attr('height', bounds.y2 - bounds.y1); | ||
457 | 495 | ||
458 | node.select('image') | 496 | node.select('image') |
459 | - .attr('x', bounds.x1 + config.icons.xoff) | 497 | + .attr('x', box.x + config.icons.xoff) |
460 | - .attr('y', bounds.y1 + config.icons.yoff); | 498 | + .attr('y', box.y + config.icons.yoff); |
499 | + | ||
500 | + var bounds = boundsFromBox(box); | ||
461 | 501 | ||
502 | + // todo: clean up extent and edge work.. | ||
462 | d.extent = { | 503 | d.extent = { |
463 | left: bounds.x1 - lab.marginLR, | 504 | left: bounds.x1 - lab.marginLR, |
464 | right: bounds.x2 + lab.marginLR, | 505 | right: bounds.x2 + lab.marginLR, |
... | @@ -473,7 +514,6 @@ | ... | @@ -473,7 +514,6 @@ |
473 | bottom : new geo.LineSegment(bounds.x1, bounds.y2, bounds.x2, bounds.y2) | 514 | bottom : new geo.LineSegment(bounds.x1, bounds.y2, bounds.x2, bounds.y2) |
474 | }; | 515 | }; |
475 | 516 | ||
476 | - // ==== | ||
477 | }); | 517 | }); |
478 | 518 | ||
479 | network.numTicks = 0; | 519 | network.numTicks = 0; | ... | ... |
... | @@ -48,6 +48,26 @@ span.right { | ... | @@ -48,6 +48,26 @@ span.right { |
48 | } | 48 | } |
49 | 49 | ||
50 | /* | 50 | /* |
51 | + * Radio Buttons | ||
52 | + */ | ||
53 | + | ||
54 | +span.radio { | ||
55 | + margin: 4px 0; | ||
56 | + border: 1px dotted #222; | ||
57 | + padding: 1px 6px; | ||
58 | + color: #eee; | ||
59 | + cursor: pointer; | ||
60 | +} | ||
61 | + | ||
62 | +span.radio.active { | ||
63 | + background-color: #bbb; | ||
64 | + border: 1px solid #eee; | ||
65 | + padding: 1px 6px; | ||
66 | + color: #666; | ||
67 | + font-weight: bold; | ||
68 | +} | ||
69 | + | ||
70 | +/* | ||
51 | * === DEBUGGING ====== | 71 | * === DEBUGGING ====== |
52 | */ | 72 | */ |
53 | svg { | 73 | svg { |
... | @@ -71,12 +91,6 @@ svg .link { | ... | @@ -71,12 +91,6 @@ svg .link { |
71 | -moz-transition: opacity 250ms; | 91 | -moz-transition: opacity 250ms; |
72 | } | 92 | } |
73 | 93 | ||
74 | -marker#end { | ||
75 | - fill: #666; | ||
76 | - stroke: #666; | ||
77 | - stroke-width: 1.5px; | ||
78 | -} | ||
79 | - | ||
80 | svg .node rect { | 94 | svg .node rect { |
81 | stroke-width: 1.5px; | 95 | stroke-width: 1.5px; |
82 | 96 | ||
... | @@ -88,6 +102,7 @@ svg .node rect { | ... | @@ -88,6 +102,7 @@ svg .node rect { |
88 | svg .node.device.roadm rect { | 102 | svg .node.device.roadm rect { |
89 | fill: #229; | 103 | fill: #229; |
90 | } | 104 | } |
105 | + | ||
91 | svg .node.device.switch rect { | 106 | svg .node.device.switch rect { |
92 | fill: #55f; | 107 | fill: #55f; |
93 | } | 108 | } |
... | @@ -102,6 +117,18 @@ svg .node text { | ... | @@ -102,6 +117,18 @@ svg .node text { |
102 | pointer-events: none; | 117 | pointer-events: none; |
103 | } | 118 | } |
104 | 119 | ||
120 | +/* for debugging */ | ||
121 | +svg .node circle.debug { | ||
122 | + fill: white; | ||
123 | + stroke: red; | ||
124 | +} | ||
125 | +svg .node rect.debug { | ||
126 | + fill: yellow; | ||
127 | + stroke: red; | ||
128 | + opacity: 0.35; | ||
129 | +} | ||
130 | + | ||
131 | + | ||
105 | svg .node.selected rect { | 132 | svg .node.selected rect { |
106 | filter: url(#blue-glow); | 133 | filter: url(#blue-glow); |
107 | } | 134 | } |
... | @@ -119,6 +146,16 @@ svg .node.inactive.selected image { | ... | @@ -119,6 +146,16 @@ svg .node.inactive.selected image { |
119 | opacity: .6; | 146 | opacity: .6; |
120 | } | 147 | } |
121 | 148 | ||
149 | +/* | ||
150 | + * === currently unused =============================================== | ||
151 | + */ | ||
152 | + | ||
153 | +svg marker#end { | ||
154 | + fill: #666; | ||
155 | + stroke: #666; | ||
156 | + stroke-width: 1.5px; | ||
157 | +} | ||
158 | + | ||
122 | svg .legend { | 159 | svg .legend { |
123 | position: fixed; | 160 | position: fixed; |
124 | } | 161 | } | ... | ... |
-
Please register or login to post a comment