Topo2: Implemented Link Labels

JIRA-Tasks; ONOS-6387

Change-Id: I6d0292846349d73d6d274ae806d14736b2d3eb7c
This commit is contained in:
Steven Burrows 2017-05-08 17:31:08 -04:00 committed by Simon Hunt
parent 0ad49b129a
commit ca1a39c5d1
10 changed files with 164 additions and 63 deletions

View File

@ -293,35 +293,45 @@
/* TODO: Review for not-permitted links */ /* TODO: Review for not-permitted links */
#ov-topo2 svg .link.not-permitted { #ov-topo2 svg .link.not-permitted {
stroke: rgb(255,0,0); stroke: rgb(255,0,0);
stroke-width: 5.0;
stroke-dasharray: 8 4; stroke-dasharray: 8 4;
} }
#ov-topo2 svg .link.secondary { #ov-topo2 svg .link.secondary {
stroke-width: 3px;
stroke: rgba(0,153,51,0.5); stroke: rgba(0,153,51,0.5);
} }
#ov-topo2 svg .link.secondary.port-traffic-green {
stroke: rgb(0,153,51);
}
#ov-topo2 svg .link.secondary.port-traffic-yellow {
stroke: rgb(128,145,27);
}
#ov-topo2 svg .link.secondary.port-traffic-orange {
stroke: rgb(255, 137, 3);
}
#ov-topo2 svg .link.secondary.port-traffic-red {
stroke: rgb(183, 30, 21);
}
/* Port traffic color visualization for Kbps, Mbps, and Gbps */ /* Port traffic color visualization for Kbps, Mbps, and Gbps */
#ov-topo2 svg .link.secondary.port-traffic-Kbps { #ov-topo2 svg .link.secondary.port-traffic-Kbps {
stroke: rgb(0,153,51); stroke: rgb(0,153,51);
stroke-width: 5.0;
} }
#ov-topo2 svg .link.secondary.port-traffic-Mbps { #ov-topo2 svg .link.secondary.port-traffic-Mbps {
stroke: rgb(128,145,27); stroke: rgb(128,145,27);
stroke-width: 6.5;
} }
#ov-topo2 svg .link.secondary.port-traffic-Gbps { #ov-topo2 svg .link.secondary.port-traffic-Gbps {
stroke: rgb(255, 137, 3); stroke: rgb(255, 137, 3);
stroke-width: 8.0;
} }
#ov-topo2 svg .link.secondary.port-traffic-Gbps-choked { #ov-topo2 svg .link.secondary.port-traffic-Gbps-choked {
stroke: rgb(183, 30, 21); stroke: rgb(183, 30, 21);
stroke-width: 8.0;
} }

View File

@ -48,12 +48,14 @@
addModel: function (data) { addModel: function (data) {
if (Object.getPrototypeOf(data) !== Object.prototype) { if (Object.getPrototypeOf(data) !== Object.prototype) {
this.models.push(data); this.models.push(data);
data.collection = this;
this._byId[data.get('id')] = data; this._byId[data.get('id')] = data;
return data; return data;
} }
var CollectionModel = this.model; var CollectionModel = this.model;
var model = new CollectionModel(data, this); var model = new CollectionModel(data, this);
model.collection = this;
this.models.push(model); this.models.push(model);
this._byId[data.id] = model; this._byId[data.id] = model;
@ -96,6 +98,12 @@
filter: function (comparator) { filter: function (comparator) {
return _.filter(this.models, comparator); return _.filter(this.models, comparator);
}, },
empty: function () {
_.map(this.models, function (m) {
m.remove();
});
this._reset();
},
_reset: function () { _reset: function () {
this._byId = []; this._byId = [];
this.models = []; this.models = [];
@ -111,7 +119,6 @@
.factory('Topo2Collection', .factory('Topo2Collection',
['Topo2Model', 'FnService', ['Topo2Model', 'FnService',
function (_Model_, fn) { function (_Model_, fn) {
Collection.extend = fn.extend; Collection.extend = fn.extend;
Model = _Model_; Model = _Model_;
return Collection; return Collection;

View File

@ -27,7 +27,7 @@
'use strict'; 'use strict';
// injected refs // injected refs
var $log, wss, t2fs; var $log, wss, t2fs, t2ovs;
// internal state // internal state
var handlerMap, var handlerMap,
@ -42,7 +42,8 @@
topo2CurrentRegion: t2fs, topo2CurrentRegion: t2fs,
topo2PeerRegions: t2fs, topo2PeerRegions: t2fs,
topo2UiModelEvent: t2fs topo2UiModelEvent: t2fs,
topo2Highlights: t2ovs.showHighlights,
// Add further event names / module references as needed // Add further event names / module references as needed
}; };
@ -83,12 +84,13 @@
angular.module('ovTopo2') angular.module('ovTopo2')
.factory('Topo2EventService', [ .factory('Topo2EventService', [
'$log', 'WebSocketService', 'Topo2ForceService', '$log', 'WebSocketService', 'Topo2ForceService', 'Topo2OverlayService',
function (_$log_, _wss_, _t2fs_) { function (_$log_, _wss_, _t2fs_, _t2ovs_) {
$log = _$log_; $log = _$log_;
wss = _wss_; wss = _wss_;
t2fs = _t2fs_; t2fs = _t2fs_;
t2ovs = _t2ovs_;
// deferred creation of handler map, so module references are good // deferred creation of handler map, so module references are good
createHandlerMap(); createHandlerMap();

View File

@ -50,26 +50,39 @@
_iconG: {}, _iconG: {},
_labelG: {}, _labelG: {},
initialize: function (data, node, options) { initialize: function (data, node) {
this.parent = node; this.parent = node;
this.options = options || {};
t2zs.addZoomEventListener(this.setScale.bind(this)); t2zs.addZoomEventListener(this.setScale.bind(this));
this.beforeRender();
this.render(); this.render();
this.afterRender();
}, },
onChange: function (property, value, options) { onChange: function (property) {
if (property === 'x' || property === 'y') { if (property === 'x' || property === 'y') {
this._position(); this._position();
} }
if (property === 'label') {
var width = this._labelG.text.node().getBBox().width + 20,
height = this._labelG.text.node().getBBox().height + 10;
this._labelG.text.text(this.get('label'));
this._labelG.rect.attr({
width: width,
height: height
}).style({
transform: sus.translate(-(width/2) + 'px', -(height/2) + 'px')
});
}
}, },
setPosition: function () {}, setPosition: function () {},
setScale: function () {}, setScale: function () {},
applyStyles: function () { applyStyles: function () {
var styles = _.extend({}, defaultStyles, this.get('styles')); var styles = _.extend({}, defaultStyles, this.get('styles') || {});
if (this.get('text')) { if (this.get('label')) {
this._labelG.text.style(styles.label.text); this._labelG.text.style(styles.label.text);
this._labelG.rect.style(styles.label.rect); this._labelG.rect.style(styles.label.rect);
} }
@ -79,21 +92,17 @@
this._iconG.rect.style(styles.icon.rect); this._iconG.rect.style(styles.icon.rect);
} }
}, },
_position: function () { _position: function () {
this.el.style('transform', sus.translate(this.get('x') + 'px', this.el.style('transform', sus.translate(this.get('x') + 'px',
this.get('y') + 'px')); this.get('y') + 'px'));
}, },
labelDimensions: function () {
return this.content.node().getBBox();
},
renderText: function () { renderText: function () {
this._labelG.el = this.content.append('g') this._labelG.el = this.content.append('g')
.attr('class', 'label-group'); .attr('class', 'label-group');
this._labelG.rect = this._labelG.el.append('rect'); this._labelG.rect = this._labelG.el.append('rect');
this._labelG.text = this._labelG.el.append('text') this._labelG.text = this._labelG.el.append('text')
.text(this.get('text')) .text(this.get('label'))
.attr('y', '0.4em') .attr('y', '0.4em')
.style('text-anchor', 'middle'); .style('text-anchor', 'middle');
@ -127,6 +136,7 @@
transform: sus.translate(iconX, iconY) transform: sus.translate(iconX, iconY)
}); });
}, },
beforeRender: function () {},
render: function () { render: function () {
this.el = this.parent.append('g') this.el = this.parent.append('g')
.attr('class', 'topo2-label') .attr('class', 'topo2-label')
@ -146,6 +156,10 @@
this.applyStyles(); this.applyStyles();
this.setPosition(); this.setPosition();
this.setScale(); this.setScale();
},
afterRender: function () {},
remove: function () {
this.el.remove();
} }
}); });
} }

View File

@ -19,7 +19,6 @@
A collection of any type of label (Topo2Label, Topo2Badge, Topo2LinkLabel) A collection of any type of label (Topo2Label, Topo2Badge, Topo2LinkLabel)
*/ */
(function () { (function () {
var instance; var instance;
@ -32,12 +31,18 @@
var LabelCollection = Collection.extend({ var LabelCollection = Collection.extend({
initialize: function () { initialize: function () {
instance = this; instance = this;
},
addLabel: function (Model, label, targetNode, options) {
if (this._byId[label.id]) {
this.get(label.id).set(label);
} else {
var lab = new Model(label, targetNode, options)
this.add(lab);
}
} }
}); });
return instance || new LabelCollection(); return instance || new LabelCollection();
} }
]); ]);
})();
})();

View File

@ -22,7 +22,8 @@
(function () { (function () {
'use strict'; 'use strict';
var $log, Collection, Model, ts, sus, t2zs, t2vs, t2lps, fn, ps, t2mss; var $log, Collection, Model, ts, sus, t2zs, t2vs, t2lps,
fn, ps, t2mss, t2ts;
var linkLabelOffset = '0.35em'; var linkLabelOffset = '0.35em';
@ -134,7 +135,8 @@
enhanced: this.get('enhanced'), enhanced: this.get('enhanced'),
selected: this.get('selected'), selected: this.get('selected'),
suppressedmax: this.get('mastership') suppressedmax: this.get('mastership')
} },
(this.linkLabel) ? this.linkLabel.linkLabelCSSClass() : null
); );
}, },
expected: function () { expected: function () {
@ -154,6 +156,7 @@
// Update class names when the model changes // Update class names when the model changes
if (this.el) { if (this.el) {
this.el.attr('class', this.svgClassName()); this.el.attr('class', this.svgClassName());
this.setScale();
} }
}, },
enhance: function () { enhance: function () {
@ -239,7 +242,6 @@
}; };
}, },
setPosition: function () { setPosition: function () {
var multiline = this.get('multiline'); var multiline = this.get('multiline');
if (multiline) { if (multiline) {
var offsetAmt = this.amt(multiline.deviceLinks, multiline.index); var offsetAmt = this.amt(multiline.deviceLinks, multiline.index);
@ -256,6 +258,9 @@
this.el.attr(this.get('position')); this.el.attr(this.get('position'));
} }
if (this.linkLabel) {
this.linkLabel.setPosition();
}
}, },
updatePortPosition: function () { updatePortPosition: function () {
var sourcePos = this.locatePortLabel(1), var sourcePos = this.locatePortLabel(1),
@ -324,11 +329,39 @@
this.setVisibility(); this.setVisibility();
this.setScale(); this.setScale();
}, },
linkWidth: function () {
var width = widthRatio;
if (this.get('enhanced')) { width = 3.5; }
if (this.linkLabel) {
var scale = d3.scale.ordinal()
.rangeRoundPoints([4, 8]),
label = this.linkLabel.get('label').split(' ');
switch (t2ts.selectedTrafficOverlay()) {
case 'flowStatsBytes':
scale.domain(['KB', 'MB', 'GB']);
width = scale(label[1]);
break;
case 'portStatsBitSec':
scale.domain(['Kbps', 'Mbps', 'Gbps'])
width = scale(label[1]);
break;
case 'portStatsPktSec':
scale = d3.scale.linear()
.domain([1, 10, 100, 1000, 10000])
.range(d3.range(3.5, 9))
.clamp(true);
width = scale(parseInt(label[0]));
}
}
return width;
},
setScale: function () { setScale: function () {
if (!this.el) return; if (!this.el) return;
var linkWidthRatio = this.get('enhanced') ? 3.5 : widthRatio; var linkWidthRatio = this.linkWidth();
var width = linkScale(linkWidthRatio) / t2zs.scale(); var width = linkScale(linkWidthRatio) / t2zs.scale();
this.el.attr('stroke-width', width + 'px'); this.el.attr('stroke-width', width + 'px');
@ -342,6 +375,9 @@
this.setPosition(); this.setPosition();
if (this.linkLabel) {
this.linkLabel.setScale();
}
}, },
update: function () { update: function () {
if (this.get('enhanced')) { if (this.get('enhanced')) {
@ -390,9 +426,9 @@
'$log', 'Topo2Collection', 'Topo2Model', '$log', 'Topo2Collection', 'Topo2Model',
'ThemeService', 'SvgUtilService', 'Topo2ZoomService', 'ThemeService', 'SvgUtilService', 'Topo2ZoomService',
'Topo2ViewService', 'Topo2LinkPanelService', 'FnService', 'PrefsService', 'Topo2ViewService', 'Topo2LinkPanelService', 'FnService', 'PrefsService',
'Topo2MastershipService', 'Topo2MastershipService', 'Topo2TrafficService',
function (_$log_, _c_, _Model_, _ts_, _sus_, function (_$log_, _c_, _Model_, _ts_, _sus_,
_t2zs_, _t2vs_, _t2lps_, _fn_, _ps_, _t2mss_) { _t2zs_, _t2vs_, _t2lps_, _fn_, _ps_, _t2mss_, _t2ts_) {
$log = _$log_; $log = _$log_;
ts = _ts_; ts = _ts_;
@ -405,6 +441,7 @@
fn = _fn_; fn = _fn_;
ps = _ps_; ps = _ps_;
t2mss = _t2mss_; t2mss = _t2mss_;
t2ts = _t2ts_;
return { return {
createLinkCollection: createLinkCollection createLinkCollection: createLinkCollection

View File

@ -30,8 +30,21 @@
className: 'topo2-linklabel', className: 'topo2-linklabel',
maxHeight: 30, maxHeight: 30,
minHeight: 20, minHeight: 20,
initialize: function (label, dom, options) {
this.link = options.link;
this.parent = dom;
this.super = this.constructor.__super__;
this.super.initialize.apply(this, arguments);
},
onChange: function () {
this.link.onChange();
this.constructor.__super__.onChange.apply(this, arguments);
},
linkLabelCSSClass: function () {
return this.get('css') || '';
},
setPosition: function () { setPosition: function () {
var link = this.options.link; var link = this.link;
this.set({ this.set({
x: (link.source.x + link.target.x) / 2, x: (link.source.x + link.target.x) / 2,
y: (link.source.y + link.target.y) / 2 y: (link.source.y + link.target.y) / 2
@ -40,6 +53,14 @@
setScale: function () { setScale: function () {
this.content.style('transform', this.content.style('transform',
'scale(' + t2zs.adjustmentScale(20, 30) + ')'); 'scale(' + t2zs.adjustmentScale(20, 30) + ')');
},
beforeRender: function () {
this.link.linkLabel = this;
},
remove: function () {
this.link.linkLabel = null;
this.link.onChange();
this.constructor.__super__.remove.apply(this, arguments);
} }
}); });
} }

View File

@ -23,13 +23,8 @@ Visualization of the topology in an SVG layer, using a D3 Force Layout.
'use strict'; 'use strict';
function Model(attributes, collection) { function Model(attributes, collection) {
var attrs = attributes || {};
this.attributes = {}; this.attributes = {};
this.set(angular.extend({}, attributes || {}), { silent: true });
attrs = angular.extend({}, attrs);
this.set(attrs, { silent: true });
this.collection = collection;
this.initialize.apply(this, arguments); this.initialize.apply(this, arguments);
} }
@ -116,6 +111,11 @@ Visualization of the topology in an SVG layer, using a D3 Force Layout.
}, },
toJSON: function (options) { toJSON: function (options) {
return angular.copy(this.attributes); return angular.copy(this.attributes);
},
remove: function () {
if (this.collection) {
this.collection.remove(this);
}
} }
}; };

View File

@ -22,7 +22,7 @@
var t2os = 'Topo2OverlayService: '; var t2os = 'Topo2OverlayService: ';
// injected refs // injected refs
var $log, $timeout, fs, gs, wss, t2kcs, api; var $log, $timeout, fs, gs, wss, t2kcs, t2rs, t2lc, api, LinkLabel;
// internal state // internal state
var overlays = {}, var overlays = {},
@ -134,27 +134,20 @@
} }
function showHighlights(data) { function showHighlights(data) {
function doHighlight() {
_showHighlights(data);
}
// note: this allows the server-side event to add a manual delay
// before invoking the highlight... this was (originally) to
// allow for the re-creation of the DOM model, before trying
// to reference elements. For Topo2, there may be a better
// solution, making this piece of code redundant. Steven??
if (data.delay) {
$timeout(doHighlight, data.delay);
} else {
doHighlight();
}
}
function _showHighlights(data) {
// TODO: implement the highlighting .. see topoOverlay.js for example
$log.info('+++ TOPO 2 +++ show highlights', data); $log.info('+++ TOPO 2 +++ show highlights', data);
t2lc.empty();
var linkLabelsDOM = d3.select('.topo2-linkLabels');
_.each(data.links, function (link) {
// TODO: Inconsistent host id's (currentRegion and LinkLabel)
var id = link.id.replace('/None/0', '/None').replace('-', '~'),
lab = t2rs.getLink(id);
// TODO: There's a bug in backend where link id is in reverse
if (lab) {
t2lc.addLabel(LinkLabel, link, linkLabelsDOM, {
link: lab
});
}
});
} }
// ======================================================================== // ========================================================================
@ -162,15 +155,20 @@
angular.module('ovTopo2') angular.module('ovTopo2')
.factory('Topo2OverlayService', [ .factory('Topo2OverlayService', [
'$log', '$timeout', 'FnService', 'GlyphService', 'WebSocketService', '$log', '$timeout', 'FnService', 'GlyphService', 'WebSocketService',
'Topo2KeyCommandService', 'Topo2KeyCommandService', 'Topo2RegionService', 'Topo2LabelCollection',
'Topo2LinkLabel',
function (_$log_, _$timeout_, _fs_, _gs_, _wss_, _t2kcs_) { function (_$log_, _$timeout_, _fs_, _gs_, _wss_, _t2kcs_, _t2rs_,
_t2lc_, _t2ll_) {
$log = _$log_; $log = _$log_;
$timeout = _$timeout_; $timeout = _$timeout_;
fs = _fs_; fs = _fs_;
gs = _gs_; gs = _gs_;
wss = _wss_; wss = _wss_;
t2kcs = _t2kcs_; t2kcs = _t2kcs_;
t2rs = _t2rs_;
t2lc = _t2lc_;
LinkLabel = _t2ll_;
return { return {
register: register, register: register,

View File

@ -35,6 +35,7 @@
// internal state // internal state
var mode = null, var mode = null,
currentIndex = 0,
allIndex = 0; allIndex = 0;
// === ----------------------------------------------------- // === -----------------------------------------------------
@ -59,9 +60,14 @@
trafficType: allTrafficTypes[allIndex] trafficType: allTrafficTypes[allIndex]
}); });
flash.flash(allTrafficMsgs[allIndex]); flash.flash(allTrafficMsgs[allIndex]);
currentIndex = allIndex;
allIndex = (allIndex + 1) % 3; allIndex = (allIndex + 1) % 3;
} }
function selectedTrafficOverlay() {
return allTrafficTypes[currentIndex];
}
// === ----------------------------------------------------- // === -----------------------------------------------------
angular.module('ovTopo2') angular.module('ovTopo2')
@ -80,7 +86,8 @@
// invoked from toolbar overlay buttons or keystrokes // invoked from toolbar overlay buttons or keystrokes
cancelTraffic: cancelTraffic, cancelTraffic: cancelTraffic,
showAllTraffic: showAllTraffic showAllTraffic: showAllTraffic,
selectedTrafficOverlay: selectedTrafficOverlay
} }
} }
]); ]);