Simon Hunt
Committed by Gerrit Code Review

GUI -- Migrating the add/update device functionality to the Topology View. (WIP)

- still a lot of work to do.

Change-Id: I0453b7e2ec20a8a8149fd9d6440a13a3d43fbfd6
......@@ -70,3 +70,7 @@ svg.embeddedIcon .icon rect {
.dark svg.embeddedIcon .icon rect {
stroke: #ccc;
}
svg .svgIcon {
fill-rule: evenodd;
}
......
......@@ -26,8 +26,8 @@
cornerSize = vboxSize / 10,
viewBox = '0 0 ' + vboxSize + ' ' + vboxSize;
// maps icon id to the glyph id it uses.
// note: icon id maps to a CSS class for styling that icon
// Maps icon ID to the glyph ID it uses.
// NOTE: icon ID maps to a CSS class for styling that icon
var glyphMapping = {
deviceOnline: 'checkMark',
deviceOffline: 'xMark',
......@@ -36,6 +36,8 @@
tableColSortNone: '-'
};
function ensureIconLibDefs() {
var body = d3.select('body'),
svg = body.select('svg#IconLibDefs'),
......@@ -48,13 +50,6 @@
return svg.select('defs');
}
angular.module('onosSvg')
.factory('IconService', ['$log', 'FnService', 'GlyphService',
function (_$log_, _fs_, _gs_) {
$log = _$log_;
fs = _fs_;
gs = _gs_;
// div is a D3 selection of the <DIV> element into which icon should load
// iconCls is the CSS class used to identify the icon
// size is dimension of icon in pixels. Defaults to 20.
......@@ -103,9 +98,73 @@
loadIcon(div, iconCls, size, true);
}
// configuration for device and host icons in the topology view
var config = {
device: {
dim: 36,
rx: 4
},
host: {
radius: {
noGlyph: 9,
withGlyph: 14
},
glyphed: {
endstation: 1,
bgpSpeaker: 1,
router: 1
}
}
};
// Adds a device icon to the specified element, using the given glyph.
// Returns the D3 selection of the icon.
function addDeviceIcon(elem, glyphId) {
var cfg = config.device,
g = elem.append('g')
.attr('class', 'svgIcon deviceIcon');
g.append('rect').attr({
x: 0,
y: 0,
rx: cfg.rx,
width: cfg.dim,
height: cfg.dim
});
g.append('use').attr({
'xlink:href': '#' + glyphId,
width: cfg.dim,
height: cfg.dim
});
g.dim = cfg.dim;
return g;
}
function addHostIcon(elem, glyphId) {
// TODO:
}
// =========================
// === DEFINE THE MODULE
angular.module('onosSvg')
.factory('IconService', ['$log', 'FnService', 'GlyphService',
function (_$log_, _fs_, _gs_) {
$log = _$log_;
fs = _fs_;
gs = _gs_;
return {
loadIcon: loadIcon,
loadEmbeddedIcon: loadEmbeddedIcon
loadEmbeddedIcon: loadEmbeddedIcon,
addDeviceIcon: addDeviceIcon,
addHostIcon: addHostIcon,
iconConfig: function () { return config; }
};
}]);
......
......@@ -22,28 +22,25 @@
The Map Service provides a simple API for loading geographical maps into
an SVG layer. For example, as a background to the Topology View.
e.g. var ok = MapService.loadMapInto(svgLayer, '*continental-us');
e.g. var promise = MapService.loadMapInto(svgLayer, '*continental-us');
The Map Service makes use of the GeoDataService to load the required data
from the server and to create the appropriate geographical projection.
A promise is returned to the caller, which is resolved with the
map projection once created.
*/
(function () {
'use strict';
// injected references
var $log, fs, gds;
angular.module('onosSvg')
.factory('MapService', ['$log', 'FnService', 'GeoDataService',
function (_$log_, _fs_, _gds_) {
$log = _$log_;
fs = _fs_;
gds = _gds_;
var $log, $q, fs, gds;
function loadMapInto(mapLayer, id, opts) {
var promise = gds.fetchTopoData(id);
var promise = gds.fetchTopoData(id),
deferredProjection = $q.defer();
if (!promise) {
$log.warn('Failed to load map: ' + id);
return false;
......@@ -52,15 +49,26 @@
promise.then(function () {
var gen = gds.createPathGenerator(promise.topodata, opts);
deferredProjection.resolve(gen.settings.projection);
mapLayer.selectAll('path')
.data(gen.geodata.features)
.enter()
.append('path')
.attr('d', gen.pathgen);
});
return true;
return deferredProjection.promise;
}
angular.module('onosSvg')
.factory('MapService', ['$log', '$q', 'FnService', 'GeoDataService',
function (_$log_, _$q_, _fs_, _gds_) {
$log = _$log_;
$q = _$q_;
fs = _fs_;
gds = _gds_;
return {
loadMapInto: loadMapInto
};
......
......@@ -240,13 +240,18 @@
el.style('visibility', (b ? 'visible' : 'hidden'));
}
function safeId(s) {
return s.replace(/[^a-z0-9]/gi, '-');
}
return {
createDragBehavior: createDragBehavior,
loadGlow: loadGlow,
cat7: cat7,
translate: translate,
stripPx: stripPx,
makeVisible: makeVisible
makeVisible: makeVisible,
safeId: safeId
};
}]);
}());
......
......@@ -245,3 +245,94 @@
/* TODO: add blue glow */
/*filter: url(#blue-glow);*/
}
/* --- Topo Nodes --- */
#ov-topo svg .node {
cursor: pointer;
}
#ov-topo svg .node.selected rect,
#ov-topo svg .node.selected circle {
fill: #f90;
/* TODO: add blue glow filter */
/*filter: url(#blue-glow);*/
}
#ov-topo svg .node text {
pointer-events: none;
}
/* Device Nodes */
#ov-topo svg .node.device {
}
#ov-topo svg .node.device rect {
stroke-width: 1.5;
}
#ov-topo svg .node.device.fixed rect {
stroke-width: 1.5;
stroke: #ccc;
}
/* note: device is offline without the 'online' class */
#ov-topo svg .node.device {
fill: #777;
}
#ov-topo svg .node.device.online {
fill: #6e7fa3;
}
/* note: device is offline without the 'online' class */
#ov-topo svg .node.device text {
fill: #bbb;
font: 10pt sans-serif;
}
#ov-topo svg .node.device.online text {
fill: white;
}
#ov-topo svg .node.device .svgIcon rect {
fill: #aaa;
}
#ov-topo svg .node.device .svgIcon use {
fill: #777;
}
#ov-topo svg .node.device.selected .svgIcon rect {
fill: #f90;
}
#ov-topo svg .node.device.online .svgIcon rect {
fill: #ccc;
}
#ov-topo svg .node.device.online .svgIcon use {
fill: #000;
}
#ov-topo svg .node.device.online.selected .svgIcon rect {
fill: #f90;
}
/* Host Nodes */
#ov-topo svg .node.host {
stroke: #000;
}
#ov-topo svg .node.host text {
fill: #846;
stroke: none;
font: 9pt sans-serif;
}
svg .node.host circle {
stroke: #000;
fill: #edb;
}
......
......@@ -28,7 +28,7 @@
];
// references to injected services etc.
var $log, fs, ks, zs, gs, ms, sus, tfs;
var $log, fs, ks, zs, gs, ms, sus, tfs, tis;
// DOM elements
var ovtopo, svg, defs, zoomLayer, mapG, forceG, noDevsLayer;
......@@ -41,20 +41,61 @@
// --- Short Cut Keys ------------------------------------------------
var keyBindings = {
W: [logWarning, '(temp) log a warning'],
E: [logError, '(temp) log an error'],
R: [resetZoom, 'Reset pan / zoom']
//O: [toggleSummary, 'Toggle ONOS summary pane'],
I: [toggleInstances, 'Toggle ONOS instances pane'],
//D: [toggleDetails, 'Disable / enable details pane'],
//H: [toggleHosts, 'Toggle host visibility'],
//M: [toggleOffline, 'Toggle offline visibility'],
//B: [toggleBg, 'Toggle background image'],
//P: togglePorts,
//X: [toggleNodeLock, 'Lock / unlock node positions'],
//Z: [toggleOblique, 'Toggle oblique view (Experimental)'],
L: [cycleLabels, 'Cycle device labels'],
//U: [unpin, 'Unpin node (hover mouse over)'],
R: [resetZoom, 'Reset pan / zoom'],
//V: [showRelatedIntentsAction, 'Show all related intents'],
//rightArrow: [showNextIntentAction, 'Show next related intent'],
//leftArrow: [showPrevIntentAction, 'Show previous related intent'],
//W: [showSelectedIntentTrafficAction, 'Monitor traffic of selected intent'],
//A: [showAllTrafficAction, 'Monitor all traffic'],
//F: [showDeviceLinkFlowsAction, 'Show device link flows'],
//E: [equalizeMasters, 'Equalize mastership roles'],
//esc: handleEscape,
_helpFormat: [
['O', 'I', 'D', '-', 'H', 'M', 'B', 'P' ],
['X', 'Z', 'L', 'U', 'R' ],
['V', 'rightArrow', 'leftArrow', 'W', 'A', 'F', '-', 'E' ]
]
};
// -----------------
// these functions are necessarily temporary examples....
function logWarning() {
$log.warn('You have been warned!');
// mouse gestures
var gestures = [
['click', 'Select the item and show details'],
['shift-click', 'Toggle selection state'],
['drag', 'Reposition (and pin) device / host'],
['cmd-scroll', 'Zoom in / out'],
['cmd-drag', 'Pan']
];
function toggleInstances() {
if (tis.isVisible()) {
tis.hide();
} else {
tis.show();
}
function logError() {
$log.error('You are erroneous!');
tfs.updateDeviceColors();
}
function cycleLabels() {
$log.debug('Cycle Labels.....');
}
// -----------------
function resetZoom() {
zoomer.reset();
......@@ -83,7 +124,6 @@
function zoomCallback() {
var tr = zoomer.translate(),
sc = zoomer.scale();
$log.log('ZOOM: translate = ' + tr + ', scale = ' + sc);
// keep the map lines constant width while zooming
mapG.style('stroke-width', (2.0 / sc) + 'px');
......@@ -150,16 +190,19 @@
function setUpMap() {
mapG = zoomLayer.append('g').attr('id', 'topo-map');
//ms.loadMapInto(map, '*continental_us', {mapFillScale:0.5});
ms.loadMapInto(mapG, '*continental_us');
//showCallibrationPoints();
//return ms.loadMapInto(map, '*continental_us', {mapFillScale:0.5});
// returns a promise for the projection...
return ms.loadMapInto(mapG, '*continental_us');
}
// --- Force Layout --------------------------------------------------
function setUpForce() {
function setUpForce(xlink) {
forceG = zoomLayer.append('g').attr('id', 'topo-force');
tfs.initForce(forceG, svg.attr('width'), svg.attr('height'));
tfs.initForce(forceG, xlink, svg.attr('width'), svg.attr('height'));
}
// --- Controller Definition -----------------------------------------
......@@ -174,8 +217,12 @@
'TopoInstService',
function ($scope, _$log_, $loc, $timeout, _fs_, mast,
_ks_, _zs_, _gs_, _ms_, _sus_, tes, _tfs_, tps, tis) {
var self = this;
_ks_, _zs_, _gs_, _ms_, _sus_, tes, _tfs_, tps, _tis_) {
var self = this,
xlink = {
showNoDevs: showNoDevs
};
$log = _$log_;
fs = _fs_;
ks = _ks_;
......@@ -184,6 +231,7 @@
ms = _ms_;
sus = _sus_;
tfs = _tfs_;
tis = _tis_;
self.notifyResize = function () {
svgResized(fs.windowSize(mast.mastHeight()));
......@@ -207,8 +255,8 @@
setUpDefs();
setUpZoom();
setUpNoDevs();
setUpMap();
setUpForce();
xlink.projectionPromise = setUpMap();
setUpForce(xlink);
tis.initInst();
tps.initPanels();
......
......@@ -23,7 +23,7 @@
'use strict';
// injected refs
var $log, wss, wes, tps, tis;
var $log, wss, wes, tps, tis, tfs;
// internal state
var wsock;
......@@ -32,7 +32,9 @@
showSummary: showSummary,
addInstance: addInstance,
updateInstance: updateInstance,
removeInstance: removeInstance
removeInstance: removeInstance,
addDevice: addDevice,
updateDevice: updateDevice
// TODO: implement remaining handlers..
};
......@@ -63,6 +65,16 @@
tis.removeInstance(ev.payload);
}
function addDevice(ev) {
$log.debug(' **** Add Device **** ', ev.payload);
tfs.addDevice(ev.payload);
}
function updateDevice(ev) {
$log.debug(' **** Update Device **** ', ev.payload);
tfs.updateDevice(ev.payload);
}
// ==========================
var dispatcher = {
......@@ -100,14 +112,15 @@
angular.module('ovTopo')
.factory('TopoEventService',
['$log', '$location', 'WebSocketService', 'WsEventService',
'TopoPanelService', 'TopoInstService',
'TopoPanelService', 'TopoInstService', 'TopoForceService',
function (_$log_, $loc, _wss_, _wes_, _tps_, _tis_) {
function (_$log_, $loc, _wss_, _wes_, _tps_, _tis_, _tfs_) {
$log = _$log_;
wss = _wss_;
wes = _wes_;
tps = _tps_;
tis = _tis_;
tfs = _tfs_;
function bindDispatcher(TopoDomElementsPassedHere) {
// TODO: store refs to topo DOM elements...
......
......@@ -23,10 +23,29 @@
'use strict';
// injected refs
var $log, sus;
var $log, sus, is, ts, tis, xlink;
// configuration
var labelConfig = {
imgPad: 16,
padLR: 4,
padTB: 3,
marginLR: 3,
marginTB: 2,
port: {
gap: 3,
width: 18,
height: 14
}
};
var deviceIconConfig = {
xoff: -20,
yoff: -18
};
// internal state
var settings,
var settings, // merged default settings and options
force, // force layout object
drag, // drag behavior handler
network = {
......@@ -34,8 +53,10 @@
links: [],
lookup: {},
revLinkToKey: {}
};
},
projection, // background map projection
deviceLabelIndex = 0, // for device label cycling
hostLabelIndex = 0; // for host label cycling
// SVG elements;
var linkG, linkLabelG, nodeG;
......@@ -71,12 +92,517 @@
};
// ==========================
// === EVENT HANDLERS
function addDevice(data) {
var id = data.id,
d;
xlink.showNoDevs(false);
// although this is an add device event, if we already have the
// device, treat it as an update instead..
if (network.lookup[id]) {
updateDevice(data);
return;
}
d = createDeviceNode(data);
network.nodes.push(d);
network.lookup[id] = d;
$log.debug("Created new device.. ", d.id, d.x, d.y);
updateNodes();
fStart();
}
function updateDevice(data) {
var id = data.id,
d = network.lookup[id],
wasOnline;
if (d) {
wasOnline = d.online;
angular.extend(d, data);
if (positionNode(d, true)) {
sendUpdateMeta(d, true);
}
updateNodes();
if (wasOnline !== d.online) {
// TODO: re-instate link update, and offline visibility
//findAttachedLinks(d.id).forEach(restyleLinkElement);
//updateOfflineVisibility(d);
}
} else {
// TODO: decide whether we want to capture logic errors
//logicError('updateDevice lookup fail. ID = "' + id + '"');
}
}
function sendUpdateMeta(d, store) {
var metaUi = {},
ll;
// TODO: fix this code to send event to server...
//if (store) {
// ll = geoMapProj.invert([d.x, d.y]);
// metaUi = {
// x: d.x,
// y: d.y,
// lng: ll[0],
// lat: ll[1]
// };
//}
//d.metaUi = metaUi;
//sendMessage('updateMeta', {
// id: d.id,
// 'class': d.class,
// memento: metaUi
//});
}
function updateNodes() {
$log.debug('TODO updateNodes()...');
// TODO...
}
function fStart() {
$log.debug('TODO fStart()...');
// TODO...
}
function fResume() {
$log.debug('TODO fResume()...');
// TODO...
}
// ==========================
// === Devices and hosts - helper functions
function coordFromLngLat(loc) {
// Our hope is that the projection is installed before we start
// handling incoming nodes. But if not, we'll just return the origin.
return projection ? projection([loc.lng, loc.lat]) : [0, 0];
}
function positionNode(node, forUpdate) {
var meta = node.metaUi,
x = meta && meta.x,
y = meta && meta.y,
xy;
// If we have [x,y] already, use that...
if (x && y) {
node.fixed = true;
node.px = node.x = x;
node.py = node.y = y;
return;
}
var location = node.location,
coord;
if (location && location.type === 'latlng') {
coord = coordFromLngLat(location);
node.fixed = true;
node.px = node.x = coord[0];
node.py = node.y = coord[1];
return true;
}
// if this is a node update (not a node add).. skip randomizer
if (forUpdate) {
return;
}
// Note: Placing incoming unpinned nodes at exactly the same point
// (center of the view) causes them to explode outwards when
// the force layout kicks in. So, we spread them out a bit
// initially, to provide a more serene layout convergence.
// Additionally, if the node is a host, we place it near
// the device it is connected to.
function spread(s) {
return Math.floor((Math.random() * s) - s/2);
}
function randDim(dim) {
return dim / 2 + spread(dim * 0.7071);
}
function rand() {
return {
x: randDim(network.view.width()),
y: randDim(network.view.height())
};
}
function near(node) {
var min = 12,
dx = spread(12),
dy = spread(12);
return {
x: node.x + min + dx,
y: node.y + min + dy
};
}
function getDevice(cp) {
var d = network.lookup[cp.device];
return d || rand();
}
xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
angular.extend(node, xy);
}
function createDeviceNode(device) {
// start with the object as is
var node = device,
type = device.type,
svgCls = type ? 'node device ' + type : 'node device';
// Augment as needed...
node.class = 'device';
node.svgClass = device.online ? svgCls + ' online' : svgCls;
positionNode(node);
return node;
}
// ==========================
// === Devices and hosts - D3 rendering
// Returns the newly computed bounding box of the rectangle
function adjustRectToFitText(n) {
var text = n.select('text'),
box = text.node().getBBox(),
lab = labelConfig;
text.attr('text-anchor', 'middle')
.attr('y', '-0.8em')
.attr('x', lab.imgPad/2);
// 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 mkSvgClass(d) {
return d.fixed ? d.svgClass + ' fixed' : d.svgClass;
}
function hostLabel(d) {
var idx = (hostLabelIndex < d.labels.length) ? hostLabelIndex : 0;
return d.labels[idx];
}
function deviceLabel(d) {
var idx = (deviceLabelIndex < d.labels.length) ? deviceLabelIndex : 0;
return d.labels[idx];
}
function trimLabel(label) {
return (label && label.trim()) || '';
}
function emptyBox() {
return {
x: -2,
y: -2,
width: 4,
height: 4
};
}
function updateDeviceLabel(d) {
var label = trimLabel(deviceLabel(d)),
noLabel = !label,
node = d.el,
dim = is.iconConfig().device.dim,
devCfg = deviceIconConfig,
box, dx, dy;
node.select('text')
.text(label)
.style('opacity', 0)
.transition()
.style('opacity', 1);
if (noLabel) {
box = emptyBox();
dx = -dim/2;
dy = -dim/2;
} else {
box = adjustRectToFitText(node);
dx = box.x + devCfg.xoff;
dy = box.y + devCfg.yoff;
}
node.select('rect')
.transition()
.attr(box);
node.select('g.deviceIcon')
.transition()
.attr('transform', sus.translate(dx, dy));
}
function updateHostLabel(d) {
var label = trimLabel(hostLabel(d));
d.el.select('text').text(label);
}
function nodeMouseOver(m) {
// TODO
$log.debug("TODO nodeMouseOver()...", m);
}
function nodeMouseOut(m) {
// TODO
$log.debug("TODO nodeMouseOut()...", m);
}
function updateDeviceColors(d) {
if (d) {
setDeviceColor(d);
} else {
node.filter('.device').each(function (d) {
setDeviceColor(d);
});
}
}
var dCol = {
black: '#000',
paleblue: '#acf',
offwhite: '#ddd',
midgrey: '#888',
lightgrey: '#bbb',
orange: '#f90'
};
// note: these are the device icon colors without affinity
var dColTheme = {
light: {
online: {
glyph: dCol.black,
rect: dCol.paleblue
},
offline: {
glyph: dCol.midgrey,
rect: dCol.lightgrey
}
},
// TODO: theme
dark: {
online: {
glyph: dCol.black,
rect: dCol.paleblue
},
offline: {
glyph: dCol.midgrey,
rect: dCol.lightgrey
}
}
};
function devBaseColor(d) {
var o = d.online ? 'online' : 'offline';
return dColTheme[ts.theme()][o];
}
function setDeviceColor(d) {
var o = d.online,
s = d.el.classed('selected'),
c = devBaseColor(d),
a = instColor(d.master, o),
g, r,
icon = d.el.select('g.deviceIcon');
if (s) {
g = c.glyph;
r = dCol.orange;
} else if (tis.isVisible()) {
g = o ? a : c.glyph;
r = o ? dCol.offwhite : a;
} else {
g = c.glyph;
r = c.rect;
}
icon.select('use')
.style('fill', g);
icon.select('rect')
.style('fill', r);
}
function instColor(id, online) {
return sus.cat7().getColor(id, !online, ts.theme());
}
//============
function updateNodes() {
node = nodeG.selectAll('.node')
.data(network.nodes, function (d) { return d.id; });
// operate on existing nodes...
node.filter('.device').each(function (d) {
var node = d.el;
node.classed('online', d.online);
updateDeviceLabel(d);
positionNode(d, true);
});
node.filter('.host').each(function (d) {
updateHostLabel(d);
positionNode(d, true);
});
// operate on entering nodes:
var entering = node.enter()
.append('g')
.attr({
id: function (d) { return sus.safeId(d.id); },
class: mkSvgClass,
transform: function (d) { return sus.translate(d.x, d.y); },
opacity: 0
})
.call(drag)
.on('mouseover', nodeMouseOver)
.on('mouseout', nodeMouseOut)
.transition()
.attr('opacity', 1);
// augment device nodes...
entering.filter('.device').each(function (d) {
var node = d3.select(this),
glyphId = d.type || 'unknown',
label = trimLabel(deviceLabel(d)),
noLabel = !label,
box, dx, dy, icon;
// provide ref to element from backing data....
d.el = node;
node.append('rect').attr({ rx: 5, ry: 5 });
node.append('text').text(label).attr('dy', '1.1em');
box = adjustRectToFitText(node);
node.select('rect').attr(box);
icon = is.addDeviceIcon(node, glyphId);
d.iconDim = icon.dim;
if (noLabel) {
dx = -icon.dim/2;
dy = -icon.dim/2;
} else {
box = adjustRectToFitText(node);
dx = box.x + iconConfig.xoff;
dy = box.y + iconConfig.yoff;
}
icon.attr('transform', sus.translate(dx, dy));
});
// augment host nodes...
entering.filter('.host').each(function (d) {
var node = d3.select(this),
cfg = config.icons.host,
r = cfg.radius[d.type] || cfg.defaultRadius,
textDy = r + 10,
//TODO: iid = iconGlyphUrl(d),
_dummy;
// provide ref to element from backing data....
d.el = node;
//TODO: showHostVis(node);
node.append('circle').attr('r', r);
if (iid) {
//TODO: addHostIcon(node, r, iid);
}
node.append('text')
.text(hostLabel)
.attr('dy', textDy)
.attr('text-anchor', 'middle');
});
// operate on both existing and new nodes, if necessary
updateDeviceColors();
// operate on exiting nodes:
// Note that the node is removed after 2 seconds.
// Sub element animations should be shorter than 2 seconds.
var exiting = node.exit()
.transition()
.duration(2000)
.style('opacity', 0)
.remove();
// host node exits....
exiting.filter('.host').each(function (d) {
var node = d.el;
node.select('use')
.style('opacity', 0.5)
.transition()
.duration(800)
.style('opacity', 0);
node.select('text')
.style('opacity', 0.5)
.transition()
.duration(800)
.style('opacity', 0);
node.select('circle')
.style('stroke-fill', '#555')
.style('fill', '#888')
.style('opacity', 0.5)
.transition()
.duration(1500)
.attr('r', 0);
});
// device node exits....
exiting.filter('.device').each(function (d) {
var node = d.el;
node.select('use')
.style('opacity', 0.5)
.transition()
.duration(800)
.style('opacity', 0);
node.selectAll('rect')
.style('stroke-fill', '#555')
.style('fill', '#888')
.style('opacity', 0.5);
});
fResume();
}
// ==========================
// force layout tick function
function tick() {
}
// ==========================
// === MOUSE GESTURE HANDLERS
function selectCb() { }
function atDragEnd() {}
function dragEnabled() {}
......@@ -84,23 +610,38 @@
// ==========================
// Module definition
angular.module('ovTopo')
.factory('TopoForceService',
['$log', 'SvgUtilService',
['$log', 'SvgUtilService', 'IconService', 'ThemeService',
'TopoInstService',
function (_$log_, _sus_) {
function (_$log_, _sus_, _is_, _ts_, _tis_) {
$log = _$log_;
sus = _sus_;
is = _is_;
ts = _ts_;
tis = _tis_;
// forceG is the SVG group to display the force layout in
// xlink is the cross-link api from the main topo source file
// w, h are the initial dimensions of the SVG
// opts are, well, optional :)
function initForce(forceG, w, h, opts) {
function initForce(forceG, _xlink_, w, h, opts) {
$log.debug('initForce().. WxH = ' + w + 'x' + h);
xlink = _xlink_;
settings = angular.extend({}, defaultSettings, opts);
// when the projection promise is resolved, cache the projection
xlink.projectionPromise.then(
function (proj) {
projection = proj;
$log.debug('** We installed the projection: ', proj);
}
);
linkG = forceG.append('g').attr('id', 'topo-links');
linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels');
nodeG = forceG.append('g').attr('id', 'topo-nodes');
......@@ -127,12 +668,16 @@
function resize(dim) {
force.size([dim.width, dim.height]);
// Review -- do we need to nudge the layout ?
}
return {
initForce: initForce,
resize: resize
resize: resize,
updateDeviceColors: updateDeviceColors,
addDevice: addDevice,
updateDevice: updateDevice
};
}]);
}());
......
......@@ -325,7 +325,10 @@
destroyInst: destroyInst,
addInstance: addInstance,
updateInstance: updateInstance,
removeInstance: removeInstance
removeInstance: removeInstance,
isVisible: function () { return oiBox.isVisible(); },
show: function () { oiBox.show(); },
hide: function () { oiBox.hide(); }
};
}]);
}());
......