Steven Burrows
Committed by Gerrit Code Review

Topo2Link - Fixed width of rectangle and centered text

Topo2Layout/Link - Added port number on link hover
Topo2Layout/Select - Added Drag functionality
Topo2SubRegion - Added onClick event to node
Topo2Device - Added Color Theme
TopoForce - Removed console.log

Change-Id: Icd85d92c8f3c5f96cb896068fe9375c250717f5f
......@@ -103,7 +103,6 @@
// === EVENT HANDLERS
function addDevice(data) {
console.log(data);
var id = data.id,
d;
......
......@@ -69,7 +69,7 @@
ps.setPrefs('topo_zoom', {tx:tr[0], ty:tr[1], sc:sc});
// keep the map lines constant width while zooming
// mapG.style('stroke-width', (2.0 / sc) + 'px');
mapG.style('stroke-width', (2.0 / sc) + 'px');
}
function setUpZoom() {
......@@ -108,8 +108,8 @@
// provides function calls back into this space
// showNoDevs: showNoDevs,
// projection: function () { return projection; },
// zoomLayer: function () { return zoomLayer; },
// zoomer: function () { return zoomer; },
zoomLayer: function () { return zoomLayer; },
zoomer: function () { return zoomer; },
// opacifyMap: opacifyMap,
// topoStartDone: topoStartDone
};
......
......@@ -30,6 +30,7 @@
// configuration
var devIconDim = 36,
labelPad = 10,
hostRadius = 14,
badgeConfig = {
radius: 12,
......@@ -43,7 +44,8 @@
i: 'badgeInfo',
w: 'badgeWarn',
e: 'badgeError'
};
},
deviceLabelIndex = 0;
function createDeviceCollection(data, region) {
......@@ -81,13 +83,25 @@
}
}
function deviceGlyphColor(d) {
// note: these are the device icon colors without affinity (no master)
var dColTheme = {
light: {
online: '#444444',
offline: '#cccccc'
},
dark: {
// TODO: theme
online: '#444444',
offline: '#cccccc'
}
};
function deviceGlyphColor(d) {
var o = this.node.online,
id = this.node.master, // TODO: This should be from node.master
otag = o ? 'online' : 'offline';
return o ? sus.cat7().getColor(id, 0, ts.theme())
: '#ff0000';
: dColTheme[ts.theme()][otag];
}
function setDeviceColor() {
......
......@@ -22,12 +22,12 @@
(function () {
'use strict';
var $log, sus, t2rs, t2d3, t2vs;
var $log, sus, t2rs, t2d3, t2vs, t2ss;
var linkG, linkLabelG, numLinkLabelsG, nodeG, portLabelG;
var uplink, linkG, linkLabelG, numLinkLabelsG, nodeG, portLabelG;
var link, linkLabel, node;
var nodes, links;
var nodes, links, highlightedLink;
var force;
......@@ -124,7 +124,7 @@
function init(_svg_, forceG, _uplink_, _dim_, opts) {
$log.debug("Initialising Topology Layout");
uplink = _uplink_;
settings = angular.extend({}, defaultSettings, opts);
linkG = forceG.append('g').attr('id', 'topo-links');
......@@ -147,6 +147,35 @@
.linkDistance(settings.linkDistance._def_)
.linkStrength(settings.linkStrength._def_)
.on('tick', tick);
drag = sus.createDragBehavior(force,
t2ss.selectObject, atDragEnd, dragEnabled, clickEnabled);
_svg_.on('mousemove', mouseMoveHandler)
}
function zoomingOrPanning(ev) {
return ev.metaKey || ev.altKey;
}
function atDragEnd(d) {
// once we've finished moving, pin the node in position
d.fixed = true;
d3.select(this).classed('fixed', true);
// TODO: sendUpdateMeta(d);
t2ss.clickConsumed(true);
}
// predicate that indicates when dragging is active
function dragEnabled() {
var ev = d3.event.sourceEvent;
// nodeLock means we aren't allowing nodes to be dragged...
return !nodeLock && !zoomingOrPanning(ev);
}
// predicate that indicates when clicking is active
function clickEnabled() {
return true;
}
function tick() {
......@@ -188,6 +217,7 @@
},
opacity: 0
})
.call(drag)
// .on('mouseover', tss.nodeMouseOver)
// .on('mouseout', tss.nodeMouseOut)
.transition()
......@@ -308,18 +338,140 @@
force.start();
}
// Mouse Events
function mouseMoveHandler() {
var mp = getLogicalMousePosition(this),
link = computeNearestLink(mp);
if (highlightedLink) {
highlightedLink.unenhance();
highlightedLink = null;
}
if (link) {
link.enhance();
highlightedLink = link;
}
}
// ======== ALGORITHM TO FIND LINK CLOSEST TO MOUSE ========
function getLogicalMousePosition(container) {
var m = d3.mouse(container),
sc = uplink.zoomer().scale(),
tr = uplink.zoomer().translate(),
mx = (m[0] - tr[0]) / sc,
my = (m[1] - tr[1]) / sc;
return {x: mx, y: my};
}
function sq(x) { return x * x; }
function mdist(p, m) {
return Math.sqrt(sq(p.x - m.x) + sq(p.y - m.y));
}
function prox(dist) {
return dist / uplink.zoomer().scale();
}
function computeNearestNode(mouse) {
var proximity = prox(30),
nearest = null,
minDist,
regionNodes = t2rs.regionNodes();
if (regionNodes.length) {
minDist = proximity * 2;
regionNodes.forEach(function (d) {
var dist;
if (!api.showHosts() && d.class === 'host') {
return; // skip hidden hosts
}
dist = mdist({x: d.x, y: d.y}, mouse);
if (dist < minDist && dist < proximity) {
minDist = dist;
nearest = d;
}
});
}
return nearest;
}
function computeNearestLink(mouse) {
var proximity = prox(30),
nearest = null,
minDist,
regionLinks = t2rs.regionLinks();
function pdrop(line, mouse) {
var x1 = line.x1,
y1 = line.y1,
x2 = line.x2,
y2 = line.y2,
x3 = mouse.x,
y3 = mouse.y,
k = ((y2-y1) * (x3-x1) - (x2-x1) * (y3-y1)) /
(sq(y2-y1) + sq(x2-x1)),
x4 = x3 - k * (y2-y1),
y4 = y3 + k * (x2-x1);
return {x:x4, y:y4};
}
function lineHit(line, p, m) {
if (p.x < line.x1 && p.x < line.x2) return false;
if (p.x > line.x1 && p.x > line.x2) return false;
if (p.y < line.y1 && p.y < line.y2) return false;
if (p.y > line.y1 && p.y > line.y2) return false;
// line intersects, but are we close enough?
return mdist(p, m) <= proximity;
}
if (regionLinks.length) {
minDist = proximity * 2;
regionLinks.forEach(function (d) {
// if (!api.showHosts() && d.type() === 'hostLink') {
// return; // skip hidden host links
// }
var line = d.get('position'),
point = pdrop(line, mouse),
hit = lineHit(line, point, mouse),
dist;
if (hit) {
dist = mdist(point, mouse);
if (dist < minDist) {
minDist = dist;
nearest = d;
}
}
});
}
return nearest;
}
angular.module('ovTopo2')
.factory('Topo2LayoutService',
[
'$log', 'SvgUtilService', 'Topo2RegionService',
'Topo2D3Service', 'Topo2ViewService',
'Topo2D3Service', 'Topo2ViewService', 'Topo2SelectService',
function (_$log_, _sus_, _t2rs_, _t2d3_, _t2vs_) {
function (_$log_, _sus_, _t2rs_, _t2d3_, _t2vs_, _t2ss_) {
$log = _$log_;
t2rs = _t2rs_;
t2d3 = _t2d3_;
t2vs = _t2vs_;
t2ss = _t2ss_;
sus = _sus_;
return {
......
......@@ -23,7 +23,9 @@
'use strict';
var $log;
var Collection, Model, region, ts;
var Collection, Model, region, ts, sus;
var linkLabelOffset = '0.35em';
var widthRatio = 1.4,
linkScale = d3.scale.linear()
......@@ -70,12 +72,26 @@
y2: 0
}
// functions to aggregate dual link state
// extra: link.extra
// extra: link.extra
});
this.set(attrs);
}
function rectAroundText(el) {
var text = el.select('text'),
box = text.node().getBBox();
// 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 -= 4;
box.width += 8;
return box;
}
function linkEndPoints(srcId, dstId) {
var sourceNode = this.region.findNodeById(srcId)
......@@ -106,13 +122,71 @@
return this.get('type');
},
expected: function () {
//TODO: original code is: (s && s.expected) && (t && t.expected);
// TODO: original code is: (s && s.expected) && (t && t.expected);
return true;
},
online: function () {
// TODO: remove next line
return true;
return both && (s && s.online) && (t && t.online);
},
enhance: function () {
var data = [],
point;
angular.forEach(this.collection.models, function (link) {
link.unenhance();
});
this.el.classed('enhanced', true);
point = this.locatePortLabel();
angular.extend(point, {
id: 'topo-port-tgt',
num: this.get('portB')
});
data.push(point);
var entering = d3.select('#topo-portLabels').selectAll('.portLabel')
.data(data).enter().append('g')
.classed('portLabel', true)
.attr('id', function (d) { return d.id; });
entering.each(function (d) {
var el = d3.select(this),
rect = el.append('rect'),
text = el.append('text').text(d.num);
rect.attr(rectAroundText(el))
.attr('rx', 2)
.attr('ry', 2);
text.attr('dy', linkLabelOffset)
.attr('text-anchor', 'middle');
el.attr('transform', sus.translate(d.x, d.y));
});
},
unenhance: function () {
this.el.classed('enhanced', false);
d3.select('#topo-portLabels').selectAll('.portLabel').remove();
},
locatePortLabel: function (link, src) {
var offset = 32,
pos = this.get('position'),
nearX = src ? pos.x1 : pos.x2,
nearY = src ? pos.y1 : pos.y2,
farX = src ? pos.x2 : pos.x1,
farY = src ? pos.y2 : pos.y1;
function dist(x, y) { return Math.sqrt(x*x + y*y); }
var dx = farX - nearX,
dy = farY - nearY,
k = offset / dist(dx, dy);
return {x: k * dx + nearX, y: k * dy + nearY};
},
restyleLinkElement: function (immediate) {
// this fn's job is to look at raw links and decide what svg classes
// need to be applied to the line element in the DOM
......@@ -144,9 +218,10 @@
.attr('stroke', linkConfig[th].baseColor);
}
},
onEnter: function (el) {
var link = d3.select(el);
var _this = this,
link = d3.select(el);
this.el = link;
this.restyleLinkElement();
......@@ -166,12 +241,13 @@
angular.module('ovTopo2')
.factory('Topo2LinkService',
['$log', 'Topo2Collection', 'Topo2Model', 'ThemeService',
['$log', 'Topo2Collection', 'Topo2Model', 'ThemeService', 'SvgUtilService',
function (_$log_, _Collection_, _Model_, _ts_) {
function (_$log_, _Collection_, _Model_, _ts_, _sus_) {
$log = _$log_;
ts = _ts_;
sus = _sus_;
Collection = _Collection_;
Model = _Model_;
......
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
ONOS GUI -- Topology Select Module.
*/
(function () {
'use strict';
// internal state
var hovered, selections, selectOrder, consumeClick;
function selectObject(obj) {
var el = this,
nodeEv = el && el.tagName === 'g',
ev = d3.event.sourceEvent || {},
n;
console.log(el, nodeEv, ev, n);
}
function clickConsumed(x) {
var cc = consumeClick;
consumeClick = !!x;
return cc;
}
angular.module('ovTopo2')
.factory('Topo2SelectService',
[
function () {
return {
selectObject: selectObject,
clickConsumed: clickConsumed
};
}
]);
})();
......
......@@ -22,7 +22,8 @@
(function () {
'use strict';
var Collection, Model, is, sus, ts, t2vs;
var wss, is, sus, ts, t2vs;
var Collection, Model;
var remappedDeviceTypes = {
virtual: 'cord'
......@@ -71,11 +72,12 @@
angular.module('ovTopo2')
.factory('Topo2SubRegionService',
['Topo2Collection', 'Topo2NodeModel', 'IconService', 'SvgUtilService',
['WebSocketService', 'Topo2Collection', 'Topo2NodeModel', 'IconService', 'SvgUtilService',
'ThemeService', 'Topo2ViewService',
function (_Collection_, _NodeModel_, _is_, _sus_, _ts_, classnames, _t2vs_) {
function (_wss_, _Collection_, _NodeModel_, _is_, _sus_, _ts_, classnames, _t2vs_) {
wss = _wss_;
t2vs = _t2vs_;
is = _is_;
sus = _sus_;
......@@ -89,6 +91,12 @@
},
nodeType: 'sub-region',
mapDeviceTypeToGlyph: mapDeviceTypeToGlyph,
onClick: function () {
wss.sendEvent('topo2navRegion', {
dir: 'down',
rid: this.get('id')
});
},
onEnter: function (el) {
var node = d3.select(el),
......@@ -97,6 +105,7 @@
glyph, labelWidth;
this.el = node;
this.el.on('click', this.onClick.bind(this));
// Label
var labelElements = this.addLabelElements(label);
......