Simon Hunt

Fixed node geometry so that [x,y] is now at the center of the rectangle. (Added debug circles).

Added radio buttons in mast head.
......@@ -25,9 +25,11 @@
-->
<html>
<head>
<meta charset="utf-8">
<title>ONOS GUI</title>
<script src="libs/d3.min.js"></script>
<!--TODO: use the minified version of d3, once debugging is complete -->
<script src="libs/d3.js"></script>
<script src="libs/jquery-2.1.1.min.js"></script>
<link rel="stylesheet" href="base.css">
......@@ -42,10 +44,10 @@
<div id="mast">
<img id="logo" src="img/onos-logo.png" width="60" height="38">
<span class="title">Open Network Operating System</span>
<span class="right">
<span class="radio">[All Layers]</span>
<span class="radio">[Packet Only]</span>
<span class="radio">[Optical Only]</span>
<span id="displayModes" class="right">
<span id="showAll" class="radio active">All Layers</span>
<span id="showPkt" class="radio">Packet Only</span>
<span id="showOpt" class="radio">Optical Only</span>
</span>
</div>
<div id="view"></div>
......
......@@ -31,7 +31,8 @@
var config = {
options: {
layering: true,
collisionPrevention: true
collisionPrevention: true,
showNodeXY: true
},
XjsonUrl: 'rs/topology/graph',
jsonUrl: 'network.json',
......@@ -91,7 +92,8 @@
view = {},
network = {},
selected = {},
highlighted = null;
highlighted = null,
viewMode = 'showAll';
function loadNetworkView() {
......@@ -126,6 +128,21 @@
});
$(window).on('resize', resize);
// set up radio button behavior
d3.selectAll("#displayModes .radio").on('click', function() {
var id = d3.select(this).attr("id");
if (id !== viewMode) {
radioButton('displayModes', id);
viewMode = id;
alert("action: " + id);
}
});
}
function radioButton(group, id) {
d3.selectAll("#" + group + " .radio").classed("active", false);
d3.select("#" + group + " #" + id).classed("active", true);
}
......@@ -263,8 +280,6 @@
// + " scale(" + d3.event.scale + ")");
// }
// TODO: svg.append('defs') for markers?
// TODO: move glow/blur stuff to util script
var glow = network.svg.append('filter')
.attr('x', '-50%')
......@@ -295,8 +310,7 @@
// });
// add links to the display
network.link = network.svg.append('g').selectAll('.link')
.data(network.force.links(), function(d) {return d.id})
.enter().append('line')
......@@ -350,6 +364,7 @@
});
// add nodes to the display
network.node = network.svg.selectAll('.node')
.data(network.force.nodes(), function(d) {return d.id})
.enter().append('g')
......@@ -386,79 +401,105 @@
});
network.nodeRect = network.node.append('rect')
.attr('rx', 5)
.attr('ry', 5);
// note that width/height are adjusted to fit the label text
.attr({
rx: 5,
ry: 5,
width: 100,
height: 12
});
// note that width/height are adjusted to fit the label text
network.node.each(function(d) {
var node = d3.select(this),
rect = node.select('rect'),
icon = iconUrl(d),
text = node.append('text')
// TODO: add label cycle behavior
.text(d.id)
.attr('dy', '1.1em');
icon = iconUrl(d);
node.append('text')
// TODO: add label cycle behavior
.text(d.id)
.attr('dy', '1.1em');
if (icon) {
var cfg = config.icons;
node.append('svg:image')
.attr('width', cfg.w)
.attr('height', cfg.h)
.attr('xlink:href', icon);
.attr({
width: cfg.w,
height: cfg.h,
'xlink:href': icon
});
// note, icon relative positioning (x,y) is done after we have
// adjusted the bounds of the rectangle...
}
// for debugging...
if (config.options.showNodeXY) {
node.append('circle')
.attr({
class: 'debug',
cx: 0,
cy: 0,
r: '3px'
});
}
});
// returns the newly computed bounding box
function adjustRectToFitText(n) {
var text = n.select('text'),
box = text.node().getBBox(),
lab = config.labels;
text.attr('text-anchor', 'middle')
.attr('y', '-0.8em')
.attr('x', lab.imgPad/2)
;
// TODO: figure out how to access the data on selection
console.log("\nadjust rect for " + n.data().id);
console.log(box);
// translate the bbox so that it is centered on [x,y]
box.x = -box.width / 2;
box.y = -box.height / 2;
// add padding
box.x -= (lab.padLR + lab.imgPad/2);
box.width += lab.padLR * 2 + lab.imgPad;
box.y -= lab.padTB;
box.height += lab.padTB * 2;
return box;
}
function boundsFromBox(box) {
return {
x1: box.x,
y1: box.y,
x2: box.x + box.width,
y2: box.y + box.height
};
}
// this function is scheduled to happen soon after the given thread ends
setTimeout(function() {
network.node.each(function(d) {
// for every node, recompute size, padding, etc. so text fits
var node = d3.select(this),
text = node.selectAll('text'),
bounds = {},
first = true;
// NOTE: probably unnecessary code if we only have one line.
text.each(function() {
var box = this.getBBox();
if (first || box.x < bounds.x1) {
bounds.x1 = box.x;
}
if (first || box.y < bounds.y1) {
bounds.y1 = box.y;
}
if (first || box.x + box.width < bounds.x2) {
bounds.x2 = box.x + box.width;
}
if (first || box.y + box.height < bounds.y2) {
bounds.y2 = box.y + box.height;
}
first = false;
}).attr('text-anchor', 'middle');
var lab = config.labels,
oldWidth = bounds.x2 - bounds.x1;
bounds.x1 -= oldWidth / 2;
bounds.x2 -= oldWidth / 2;
bounds.x1 -= (lab.padLR + lab.imgPad);
bounds.y1 -= lab.padTB;
bounds.x2 += lab.padLR;
bounds.y2 += lab.padTB;
text = node.select('text'),
box = adjustRectToFitText(node),
lab = config.labels;
// now make the computed adjustment
node.select('rect')
.attr('x', bounds.x1)
.attr('y', bounds.y1)
.attr('width', bounds.x2 - bounds.x1)
.attr('height', bounds.y2 - bounds.y1);
.attr(box);
node.select('image')
.attr('x', bounds.x1 + config.icons.xoff)
.attr('y', bounds.y1 + config.icons.yoff);
.attr('x', box.x + config.icons.xoff)
.attr('y', box.y + config.icons.yoff);
var bounds = boundsFromBox(box);
// todo: clean up extent and edge work..
d.extent = {
left: bounds.x1 - lab.marginLR,
right: bounds.x2 + lab.marginLR,
......@@ -473,7 +514,6 @@
bottom : new geo.LineSegment(bounds.x1, bounds.y2, bounds.x2, bounds.y2)
};
// ====
});
network.numTicks = 0;
......
......@@ -48,6 +48,26 @@ span.right {
}
/*
* Radio Buttons
*/
span.radio {
margin: 4px 0;
border: 1px dotted #222;
padding: 1px 6px;
color: #eee;
cursor: pointer;
}
span.radio.active {
background-color: #bbb;
border: 1px solid #eee;
padding: 1px 6px;
color: #666;
font-weight: bold;
}
/*
* === DEBUGGING ======
*/
svg {
......@@ -71,12 +91,6 @@ svg .link {
-moz-transition: opacity 250ms;
}
marker#end {
fill: #666;
stroke: #666;
stroke-width: 1.5px;
}
svg .node rect {
stroke-width: 1.5px;
......@@ -88,6 +102,7 @@ svg .node rect {
svg .node.device.roadm rect {
fill: #229;
}
svg .node.device.switch rect {
fill: #55f;
}
......@@ -102,6 +117,18 @@ svg .node text {
pointer-events: none;
}
/* for debugging */
svg .node circle.debug {
fill: white;
stroke: red;
}
svg .node rect.debug {
fill: yellow;
stroke: red;
opacity: 0.35;
}
svg .node.selected rect {
filter: url(#blue-glow);
}
......@@ -119,6 +146,16 @@ svg .node.inactive.selected image {
opacity: .6;
}
/*
* === currently unused ===============================================
*/
svg marker#end {
fill: #666;
stroke: #666;
stroke-width: 1.5px;
}
svg .legend {
position: fixed;
}
......