Topo2: Compute nearest link by mouse position
Topo2: Deselect Nodes and Links on ESC command
Topo2: Added deselect methods to nodes
Topo2: Updated to new icon
Change-Id: Ia0aaa24e887d645123787f42bb1f847ef1de11b0
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2.js b/web/gui/src/main/webapp/app/view/topo2/topo2.js
index 79762cb..6845aa0 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2.js
@@ -196,7 +196,7 @@
// initialize the force layout, ready to render the topology
forceG = zoomLayer.append('g').attr('id', 'topo-force');
- t2fs.init(svg, forceG, uplink, dim);
+ t2fs.init(svg, forceG, uplink, dim, zoomer);
t2bcs.init();
t2kcs.init(t2fs);
t2is.initInst({ showMastership: t2fs.showMastership });
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Force.js b/web/gui/src/main/webapp/app/view/topo2/topo2Force.js
index b6ed850..852e0a5 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Force.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Force.js
@@ -27,21 +27,22 @@
wss;
var t2is, t2rs, t2ls, t2vs, t2bcs;
- var svg, forceG, uplink, dim, opts;
+ var svg, forceG, uplink, dim, opts, zoomer;
// D3 Selections
var node;
// ========================== Helper Functions
- function init(_svg_, _forceG_, _uplink_, _dim_, _opts_) {
+ function init(_svg_, _forceG_, _uplink_, _dim_, _zoomer_, _opts_) {
svg = _svg_;
forceG = _forceG_;
uplink = _uplink_;
dim = _dim_;
opts = _opts_;
+ zoomer = _zoomer_;
- t2ls.init(svg, forceG, uplink, dim, opts);
+ t2ls.init(svg, forceG, uplink, dim, zoomer, opts);
}
function destroy() {
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Host.js b/web/gui/src/main/webapp/app/view/topo2/topo2Host.js
index 2fa7beb..ee13581 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Host.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Host.js
@@ -79,7 +79,7 @@
nodeType: 'host',
icon: function () {
var type = this.get('type');
- return remappedDeviceTypes[type] || type || 'endstation';
+ return remappedDeviceTypes[type] || type || 'm_endstation';
},
label: function () {
var labelText = this.get('id'),
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2KeyCommands.js b/web/gui/src/main/webapp/app/view/topo2/topo2KeyCommands.js
index 2cc8570..02aa984 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2KeyCommands.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2KeyCommands.js
@@ -17,9 +17,7 @@
(function () {
// Injected Services
- var ks, flash, wss, t2ps, t2ms, ps, t2is, t2sp, t2vs;
-
- var t2fs;
+ var ks, flash, wss, t2ps, t2ms, ps, t2is, t2sp, t2vs, t2rs, t2fs;
// Commmands
var actionMap = {
@@ -56,13 +54,24 @@
}
function handleEscape() {
- if (t2ddp.isVisible()) {
- t2ddp.toggle();
- } else if (t2sp.isVisible()) {
- t2sp.toggle();
+
+ if (false) {
+ // TODO: Cancel show mastership
+ // TODO: Cancel Active overlay
+
+ } else if (t2rs.deselectAllNodes()) {
+ // else if we have node selections, deselect them all
+ // (work already done)
+ } else if (t2rs.deselectLink()) {
+ // else if we have a link selection, deselect it
+ // (work already done)
} else if (t2is.isVisible()) {
- t2is.toggle();
- }
+ // If the instance panel is visible, close it
+ t2is.toggle();
+ } else if (t2sp.isVisible()) {
+ // If the summary panel is visible, close it
+ t2sp.toggle();
+ }
}
var prefsState = {};
@@ -135,7 +144,9 @@
['KeyService', 'FlashService', 'WebSocketService', 'Topo2PrefsService',
'Topo2MapService', 'PrefsService', 'Topo2InstanceService',
'Topo2SummaryPanelService', 'Topo2DeviceDetailsPanel', 'Topo2ViewService',
- function (_ks_, _flash_, _wss_, _t2ps_, _t2ms_, _ps_, _t2is_, _t2sp_, _t2ddp_, _t2vs_) {
+ 'Topo2RegionService',
+ function (_ks_, _flash_, _wss_, _t2ps_, _t2ms_, _ps_, _t2is_, _t2sp_,
+ _t2ddp_, _t2vs_, _t2rs_) {
ks = _ks_;
flash = _flash_;
@@ -147,6 +158,7 @@
t2sp = _t2sp_;
t2ddp = _t2ddp_;
t2vs = _t2vs_;
+ t2rs = _t2rs_;
return {
init: init,
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js b/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js
index 7274761..6e5df6c 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Layout.js
@@ -25,7 +25,7 @@
var $log, wss, sus, t2rs, t2d3, t2vs, t2ss;
var linkG, linkLabelG, nodeG;
- var link, node;
+ var link, node, zoomer;
// default settings for force layout
var defaultSettings = {
@@ -73,12 +73,14 @@
};
// internal state
- var settings, // merged default settings and options
- force, // force layout object
- drag, // drag behavior handler
+ var settings, // merged default settings and options
+ force, // force layout object
+ drag, // drag behavior handler
+ previousNearestLink, // previous link to mouse position
nodeLock = false; // whether nodes can be dragged or not (locked)
- function init(_svg_, forceG, _uplink_, _dim_, opts) {
+
+ function init(_svg_, forceG, _uplink_, _dim_, _zoomer_, opts) {
$log.debug("Initialising Topology Layout");
settings = angular.extend({}, defaultSettings, opts);
@@ -92,6 +94,10 @@
link = linkG.selectAll('.link');
linkLabelG.selectAll('.linkLabel');
node = nodeG.selectAll('.node');
+
+ zoomer = _zoomer_;
+ _svg_.on('mousemove', mouseMoveHandler);
+ _svg_.on('click', mouseClickHandler);
}
function getDeviceChargeForType(node) {
@@ -325,6 +331,121 @@
force.start();
}
+ function mouseClickHandler() {
+
+ if (!d3.event.shiftKey) {
+ t2rs.deselectLink();
+ }
+
+ if (!t2ss.clickConsumed()) {
+ if (previousNearestLink) {
+ previousNearestLink.select();
+ }
+ }
+
+ }
+
+ // Select Links
+ function mouseMoveHandler() {
+ var mp = getLogicalMousePosition(this),
+ link = computeNearestLink(mp);
+
+ // link.enhance();
+ if (link) {
+ if (previousNearestLink && previousNearestLink != link) {
+ previousNearestLink.unenhance();
+ }
+ link.enhance();
+ } else {
+ if (previousNearestLink) {
+ previousNearestLink.unenhance();
+ }
+ }
+
+ previousNearestLink = link;
+ }
+
+
+ function getLogicalMousePosition(container) {
+ var m = d3.mouse(container),
+ sc = zoomer.scale(),
+ tr = 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 / zoomer.scale();
+ }
+
+ function computeNearestLink(mouse) {
+ var proximity = prox(30),
+ nearest = null,
+ minDist;
+
+ 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;
+ }
+
+ var links = t2rs.regionLinks();
+
+ if (links.length) {
+ minDist = proximity * 2;
+
+ links.forEach(function (d) {
+ var line = d.get('position'),
+ point,
+ hit,
+ dist;
+
+ // TODO: Reinstate when showHost() is implemented
+ // if (!api.showHosts() && d.type() === 'hostLink') {
+ // return; // skip hidden host links
+ // }
+
+ if (line) {
+ point = pdrop(line, mouse);
+ hit = lineHit(line, point, mouse);
+ if (hit) {
+ dist = mdist(point, mouse);
+ if (dist < minDist) {
+ minDist = dist;
+ nearest = d;
+ }
+ }
+ }
+ });
+ }
+
+ return nearest;
+ }
+
angular.module('ovTopo2')
.factory('Topo2LayoutService',
[
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Link.js b/web/gui/src/main/webapp/app/view/topo2/topo2Link.js
index b552a9e..d769631 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Link.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Link.js
@@ -157,9 +157,9 @@
var data = [],
point;
- angular.forEach(this.collection.models, function (link) {
- link.unenhance();
- });
+ // angular.forEach(this.collection.models, function (link) {
+ // link.unenhance();
+ // });
this.set('enhanced', true);
@@ -212,7 +212,13 @@
this.set('enhanced', false);
d3.select('#topo-portLabels').selectAll('.portLabel').remove();
},
+ getSelected: function () {
+ return this.collection.filter(function (m) {
+ return m.get('selected');
+ });
+ },
select: function () {
+
var ev = d3.event;
// TODO: if single selection clear selected devices, hosts, sub-regions
@@ -223,15 +229,16 @@
});
this.set('selected', !s);
+ this.showDetails();
- var selected = this.collection.filter(function (m) {
- return m.get('selected');
- });
-
- return selected;
+ return this.getSelected();
+ },
+ deselect: function () {
+ this.set('selected', false);
+ this.set('enhanced', false);
},
showDetails: function () {
- var selected = this.select(d3.event);
+ var selected = this.getSelected();
if (selected) {
t2lps.displayLink(this);
@@ -298,12 +305,6 @@
this.el = link;
this.restyleLinkElement();
- // TODO: Needs improving - originally this was calculated
- // from mouse position.
- this.el.on('mouseover', this.enhance.bind(this));
- this.el.on('mouseout', this.unenhance.bind(this));
- this.el.on('click', this.showDetails.bind(this));
-
if (this.get('type') === 'hostLink') {
// sus.visible(link, api.showHosts());
}
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2NodeModel.js b/web/gui/src/main/webapp/app/view/topo2/topo2NodeModel.js
index 18ad7cc..cf5bb15 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2NodeModel.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2NodeModel.js
@@ -101,6 +101,9 @@
return selected;
},
+ deselect: function () {
+ this.set('selected', false);
+ },
createNode: function () {
this.set('svgClass', this.svgClassName());
t2nps.positionNode(this);
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Region.js b/web/gui/src/main/webapp/app/view/topo2/topo2Region.js
index 73b76a5..8df63d6 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Region.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Region.js
@@ -23,11 +23,11 @@
'use strict';
// Injected Services
- var $log, t2sr, t2ds, t2hs, t2ls, t2zs;
+ var $log, t2sr, t2ds, t2hs, t2ls, t2zs, t2dps;
var Model;
// Internal
- var region;
+ var region
function init() {}
@@ -84,8 +84,8 @@
setTimeout(function () {
- var reigionPZ = regionPanZooms[region.get('id')];
- t2zs.panAndZoom(reigionPZ.translate, reigionPZ.scale);
+ var regionPZ = regionPanZooms[region.get('id')];
+ t2zs.panAndZoom(regionPZ.translate, regionPZ.scale);
}, 10);
$log.debug('Region: ', region);
@@ -127,13 +127,53 @@
return (region) ? region.get('links').models : [];
}
+ function deselectAllNodes() {
+
+ var selected = filterRegionNodes(function (node) {
+ return node.get('selected', true);
+ });
+
+ if (selected.length) {
+
+ selected.forEach(function (node) {
+ node.deselect();
+ });
+
+ t2dps().el.hide();
+ return true;
+ }
+
+ // TODO: close details panel
+
+ return false;
+ }
+
+ function deselectLink() {
+
+ var selected = _.filter(regionLinks(), function (link) {
+ return link.get('selected', true);
+ });
+
+ if (selected.length) {
+
+ selected.forEach(function (link) {
+ link.deselect();
+ });
+
+ t2dps().el.hide();
+ return true;
+ }
+
+ return false;
+ }
+
angular.module('ovTopo2')
.factory('Topo2RegionService',
['$log', 'Topo2Model',
'Topo2SubRegionService', 'Topo2DeviceService',
- 'Topo2HostService', 'Topo2LinkService', 'Topo2ZoomService',
+ 'Topo2HostService', 'Topo2LinkService', 'Topo2ZoomService', 'Topo2DetailsPanelService',
- function (_$log_, _Model_, _t2sr_, _t2ds_, _t2hs_, _t2ls_, _t2zs_) {
+ function (_$log_, _Model_, _t2sr_, _t2ds_, _t2hs_, _t2ls_, _t2zs_, _t2dps_) {
$log = _$log_;
Model = _Model_;
@@ -142,6 +182,7 @@
t2hs = _t2hs_;
t2ls = _t2ls_;
t2zs = _t2zs_;
+ t2dps = _t2dps_;
return {
init: init,
@@ -151,6 +192,9 @@
regionLinks: regionLinks,
filterRegionNodes: filterRegionNodes,
+ deselectAllNodes: deselectAllNodes,
+ deselectLink: deselectLink,
+
getSubRegions: t2sr.getSubRegions
};
}]);